2
0

Add OpenAI block

Also migrate credentials to tRPC

Closes #253
This commit is contained in:
Baptiste Arnaud
2023-03-09 08:46:36 +01:00
parent 97cfdfe79f
commit ff04edf139
86 changed files with 2583 additions and 1055 deletions

View File

@ -5,7 +5,7 @@ import {
PaymentInputOptions,
PaymentInputRuntimeOptions,
SessionState,
StripeCredentialsData,
StripeCredentials,
} from 'models'
import Stripe from 'stripe'
import { decrypt } from 'utils/api/encryption'
@ -82,12 +82,12 @@ const createStripePaymentIntent =
const getStripeInfo = async (
credentialsId: string
): Promise<StripeCredentialsData | undefined> => {
): Promise<StripeCredentials['data'] | undefined> => {
const credentials = await prisma.credentials.findUnique({
where: { id: credentialsId },
})
if (!credentials) return
return decrypt(credentials.data, credentials.iv) as StripeCredentialsData
return decrypt(credentials.data, credentials.iv) as StripeCredentials['data']
}
// https://stripe.com/docs/currencies#zero-decimal

View File

@ -0,0 +1,85 @@
import { ExecuteIntegrationResponse } from '@/features/chat/types'
import { parseVariables, updateVariables } from '@/features/variables/utils'
import prisma from '@/lib/prisma'
import { SessionState, VariableWithUnknowValue } from 'models'
import {
ChatCompletionOpenAIOptions,
OpenAICredentials,
} from 'models/features/blocks/integrations/openai'
import { OpenAIApi, Configuration, ChatCompletionRequestMessage } from 'openai'
import { isDefined, byId, isNotEmpty } from 'utils'
import { decrypt } from 'utils/api/encryption'
export const createChatCompletionOpenAI = async (
state: SessionState,
{
outgoingEdgeId,
options,
}: { outgoingEdgeId?: string; options: ChatCompletionOpenAIOptions }
): Promise<ExecuteIntegrationResponse> => {
const {
typebot: { variables },
} = state
if (!options.credentialsId) return { outgoingEdgeId }
const credentials = await prisma.credentials.findUnique({
where: {
id: options.credentialsId,
},
})
if (!credentials) return { outgoingEdgeId }
const { apiKey } = decrypt(
credentials.data,
credentials.iv
) as OpenAICredentials['data']
const configuration = new Configuration({
apiKey,
})
const openai = new OpenAIApi(configuration)
const {
data: { choices, usage },
} = await openai.createChatCompletion({
model: options.model,
messages: options.messages
.map((message) => ({
role: message.role,
content: parseVariables(variables)(message.content),
}))
.filter(
(message) => isNotEmpty(message.role) && isNotEmpty(message.content)
) as ChatCompletionRequestMessage[],
})
const messageContent = choices[0].message?.content
const totalTokens = usage?.total_tokens
if (!messageContent) {
return { outgoingEdgeId }
}
const newVariables = options.responseMapping.reduce<
VariableWithUnknowValue[]
>((newVariables, mapping) => {
const existingVariable = variables.find(byId(mapping.variableId))
if (!existingVariable) return newVariables
if (mapping.valueToExtract === 'Message content') {
newVariables.push({
...existingVariable,
value: messageContent,
})
}
if (mapping.valueToExtract === 'Total tokens' && isDefined(totalTokens)) {
newVariables.push({
...existingVariable,
value: totalTokens,
})
}
return newVariables
}, [])
if (newVariables.length > 0) {
const newSessionState = await updateVariables(state)(newVariables)
return {
outgoingEdgeId,
newSessionState,
}
}
return {
outgoingEdgeId,
}
}

View File

@ -0,0 +1,20 @@
import { ExecuteIntegrationResponse } from '@/features/chat/types'
import { SessionState } from 'models'
import { OpenAIBlock } from 'models/features/blocks/integrations/openai'
import { createChatCompletionOpenAI } from './createChatCompletionOpenAI'
export const executeOpenAIBlock = async (
state: SessionState,
block: OpenAIBlock
): Promise<ExecuteIntegrationResponse> => {
switch (block.options.task) {
case 'Create chat completion':
return createChatCompletionOpenAI(state, {
options: block.options,
outgoingEdgeId: block.outgoingEdgeId,
})
case 'Create image':
case undefined:
return { outgoingEdgeId: block.outgoingEdgeId }
}
}

View File

@ -10,7 +10,7 @@ import {
SendEmailBlock,
SendEmailOptions,
SessionState,
SmtpCredentialsData,
SmtpCredentials,
} from 'models'
import { createTransport } from 'nodemailer'
import Mail from 'nodemailer/lib/mailer'
@ -161,7 +161,7 @@ const sendEmail = async ({
const getEmailInfo = async (
credentialsId: string
): Promise<SmtpCredentialsData | undefined> => {
): Promise<SmtpCredentials['data'] | undefined> => {
if (credentialsId === 'default')
return {
host: defaultTransportOptions.host,
@ -175,7 +175,7 @@ const getEmailInfo = async (
where: { id: credentialsId },
})
if (!credentials) return
return decrypt(credentials.data, credentials.iv) as SmtpCredentialsData
return decrypt(credentials.data, credentials.iv) as SmtpCredentials['data']
}
const getEmailBody = async ({

View File

@ -1,11 +1,11 @@
import test, { expect } from '@playwright/test'
import { createSmtpCredentials } from '../../../../test/utils/databaseActions'
import { createId } from '@paralleldrive/cuid2'
import { SmtpCredentialsData } from 'models'
import { importTypebotInDatabase } from 'utils/playwright/databaseActions'
import { getTestAsset } from '@/test/utils/playwright'
import { SmtpCredentials } from 'models'
export const mockSmtpCredentials: SmtpCredentialsData = {
export const mockSmtpCredentials: SmtpCredentials['data'] = {
from: {
email: 'sunny.cremin66@ethereal.email',
name: 'Sunny Cremin',

View File

@ -1,6 +1,7 @@
import { executeChatwootBlock } from '@/features/blocks/integrations/chatwoot/api'
import { executeGoogleAnalyticsBlock } from '@/features/blocks/integrations/googleAnalytics/api'
import { executeGoogleSheetBlock } from '@/features/blocks/integrations/googleSheets/api'
import { executeOpenAIBlock } from '@/features/blocks/integrations/openai/executeOpenAIBlock'
import { executeSendEmailBlock } from '@/features/blocks/integrations/sendEmail/api'
import { executeWebhookBlock } from '@/features/blocks/integrations/webhook/api'
import { IntegrationBlock, IntegrationBlockType, SessionState } from 'models'
@ -23,5 +24,7 @@ export const executeIntegration =
case IntegrationBlockType.MAKE_COM:
case IntegrationBlockType.PABBLY_CONNECT:
return executeWebhookBlock(state, block)
case IntegrationBlockType.OPEN_AI:
return executeOpenAIBlock(state, block)
}
}