2023-08-24 07:48:30 +02:00
|
|
|
import {
|
|
|
|
Block,
|
2023-10-06 16:34:10 +02:00
|
|
|
Credentials,
|
2023-08-24 07:48:30 +02:00
|
|
|
SessionState,
|
|
|
|
TypebotInSession,
|
|
|
|
} from '@typebot.io/schemas'
|
2023-03-09 08:46:36 +01:00
|
|
|
import {
|
|
|
|
ChatCompletionOpenAIOptions,
|
|
|
|
OpenAICredentials,
|
2023-03-15 08:35:16 +01:00
|
|
|
} from '@typebot.io/schemas/features/blocks/integrations/openai'
|
2023-07-19 11:20:44 +02:00
|
|
|
import { byId, isEmpty } from '@typebot.io/lib'
|
2023-10-06 16:34:10 +02:00
|
|
|
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
|
2023-05-25 10:32:35 +02:00
|
|
|
import { resumeChatCompletion } from './resumeChatCompletion'
|
2023-06-16 16:50:23 +02:00
|
|
|
import { parseChatCompletionMessages } from './parseChatCompletionMessages'
|
|
|
|
import { executeChatCompletionOpenAIRequest } from './executeChatCompletionOpenAIRequest'
|
2023-09-20 15:26:52 +02:00
|
|
|
import { isPlaneteScale } from '@typebot.io/lib/isPlanetScale'
|
|
|
|
import prisma from '@typebot.io/lib/prisma'
|
2023-12-13 10:22:02 +01:00
|
|
|
import { ExecuteIntegrationResponse } from '../../../../types'
|
|
|
|
import { parseVariableNumber } from '@typebot.io/variables/parseVariableNumber'
|
|
|
|
import { updateVariablesInSession } from '@typebot.io/variables/updateVariablesInSession'
|
2023-11-08 15:34:16 +01:00
|
|
|
import {
|
|
|
|
chatCompletionMessageRoles,
|
|
|
|
defaultOpenAIOptions,
|
|
|
|
} from '@typebot.io/schemas/features/blocks/integrations/openai/constants'
|
|
|
|
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
|
2023-03-09 08:46:36 +01:00
|
|
|
|
|
|
|
export const createChatCompletionOpenAI = async (
|
|
|
|
state: SessionState,
|
|
|
|
{
|
|
|
|
outgoingEdgeId,
|
|
|
|
options,
|
2023-07-19 11:20:44 +02:00
|
|
|
blockId,
|
|
|
|
}: {
|
|
|
|
outgoingEdgeId?: string
|
|
|
|
options: ChatCompletionOpenAIOptions
|
|
|
|
blockId: string
|
|
|
|
}
|
2023-03-09 08:46:36 +01:00
|
|
|
): Promise<ExecuteIntegrationResponse> => {
|
2023-03-13 16:28:08 +01:00
|
|
|
let newSessionState = state
|
2023-04-27 11:21:32 +02:00
|
|
|
const noCredentialsError = {
|
|
|
|
status: 'error',
|
|
|
|
description: 'Make sure to select an OpenAI account',
|
|
|
|
}
|
2023-11-08 15:34:16 +01:00
|
|
|
|
2023-03-15 17:47:05 +01:00
|
|
|
if (!options.credentialsId) {
|
2023-04-27 11:21:32 +02:00
|
|
|
return {
|
|
|
|
outgoingEdgeId,
|
|
|
|
logs: [noCredentialsError],
|
|
|
|
}
|
2023-03-15 17:47:05 +01:00
|
|
|
}
|
2023-03-09 08:46:36 +01:00
|
|
|
const credentials = await prisma.credentials.findUnique({
|
|
|
|
where: {
|
|
|
|
id: options.credentialsId,
|
|
|
|
},
|
|
|
|
})
|
2023-03-15 17:47:05 +01:00
|
|
|
if (!credentials) {
|
|
|
|
console.error('Could not find credentials in database')
|
2023-04-27 11:21:32 +02:00
|
|
|
return { outgoingEdgeId, logs: [noCredentialsError] }
|
2023-03-15 17:47:05 +01:00
|
|
|
}
|
2023-05-25 10:32:35 +02:00
|
|
|
const { apiKey } = (await decrypt(
|
2023-03-09 08:46:36 +01:00
|
|
|
credentials.data,
|
|
|
|
credentials.iv
|
2023-05-25 10:32:35 +02:00
|
|
|
)) as OpenAICredentials['data']
|
2023-08-24 07:48:30 +02:00
|
|
|
|
|
|
|
const { typebot } = newSessionState.typebotsQueue[0]
|
|
|
|
|
2023-06-16 16:50:23 +02:00
|
|
|
const { variablesTransformedToList, messages } = parseChatCompletionMessages(
|
2023-08-24 07:48:30 +02:00
|
|
|
typebot.variables
|
2023-03-15 17:47:05 +01:00
|
|
|
)(options.messages)
|
2023-03-13 16:28:08 +01:00
|
|
|
if (variablesTransformedToList.length > 0)
|
2023-09-20 15:26:52 +02:00
|
|
|
newSessionState = updateVariablesInSession(state)(
|
|
|
|
variablesTransformedToList
|
|
|
|
)
|
2023-03-15 17:47:05 +01:00
|
|
|
|
2023-08-24 07:48:30 +02:00
|
|
|
const temperature = parseVariableNumber(typebot.variables)(
|
2023-03-20 17:26:21 +01:00
|
|
|
options.advancedSettings?.temperature
|
|
|
|
)
|
|
|
|
|
2023-10-09 11:30:51 +02:00
|
|
|
const assistantMessageVariableName = typebot.variables.find(
|
|
|
|
(variable) =>
|
2023-11-08 15:34:16 +01:00
|
|
|
options.responseMapping?.find(
|
2023-10-09 11:30:51 +02:00
|
|
|
(m) => m.valueToExtract === 'Message content'
|
|
|
|
)?.variableId === variable.id
|
|
|
|
)?.name
|
|
|
|
|
2023-06-16 16:50:23 +02:00
|
|
|
if (
|
2023-08-29 10:01:28 +02:00
|
|
|
newSessionState.isStreamEnabled &&
|
2023-10-09 11:30:51 +02:00
|
|
|
!newSessionState.whatsApp &&
|
|
|
|
isNextBubbleMessageWithAssistantMessage(typebot)(
|
|
|
|
blockId,
|
|
|
|
assistantMessageVariableName
|
2024-03-21 10:23:23 +01:00
|
|
|
) &&
|
|
|
|
!process.env.VERCEL_ENV
|
2023-07-19 11:20:44 +02:00
|
|
|
) {
|
2023-03-13 16:28:08 +01:00
|
|
|
return {
|
2023-07-12 12:28:58 +02:00
|
|
|
clientSideActions: [
|
|
|
|
{
|
2023-12-22 09:13:53 +01:00
|
|
|
type: 'streamOpenAiChatCompletion',
|
2023-07-12 12:28:58 +02:00
|
|
|
streamOpenAiChatCompletion: {
|
|
|
|
messages: messages as {
|
|
|
|
content?: string
|
|
|
|
role: (typeof chatCompletionMessageRoles)[number]
|
|
|
|
}[],
|
|
|
|
},
|
2023-09-04 14:52:16 +02:00
|
|
|
expectsDedicatedReply: true,
|
2023-07-12 12:28:58 +02:00
|
|
|
},
|
|
|
|
],
|
2023-03-13 16:28:08 +01:00
|
|
|
outgoingEdgeId,
|
|
|
|
newSessionState,
|
|
|
|
}
|
2023-07-19 11:20:44 +02:00
|
|
|
}
|
|
|
|
|
2023-10-06 14:22:38 +02:00
|
|
|
const { chatCompletion, logs } = await executeChatCompletionOpenAIRequest({
|
2023-06-16 16:50:23 +02:00
|
|
|
apiKey,
|
|
|
|
messages,
|
2023-11-08 15:34:16 +01:00
|
|
|
model: options.model ?? defaultOpenAIOptions.model,
|
2023-06-16 16:50:23 +02:00
|
|
|
temperature,
|
2023-09-01 16:19:59 +02:00
|
|
|
baseUrl: options.baseUrl,
|
|
|
|
apiVersion: options.apiVersion,
|
2023-06-16 16:50:23 +02:00
|
|
|
})
|
2023-10-06 14:22:38 +02:00
|
|
|
if (!chatCompletion)
|
2023-03-13 16:28:08 +01:00
|
|
|
return {
|
2023-12-08 13:43:58 +00:00
|
|
|
startTimeShouldBeUpdated: true,
|
2023-06-16 16:50:23 +02:00
|
|
|
outgoingEdgeId,
|
|
|
|
logs,
|
2023-03-13 16:28:08 +01:00
|
|
|
}
|
2023-10-06 14:22:38 +02:00
|
|
|
const messageContent = chatCompletion.choices.at(0)?.message?.content
|
|
|
|
const totalTokens = chatCompletion.usage?.total_tokens
|
2023-06-16 16:50:23 +02:00
|
|
|
if (isEmpty(messageContent)) {
|
2023-10-06 14:22:38 +02:00
|
|
|
console.error('OpenAI block returned empty message', chatCompletion.choices)
|
2023-12-08 13:43:58 +00:00
|
|
|
return { outgoingEdgeId, newSessionState, startTimeShouldBeUpdated: true }
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
...(await resumeChatCompletion(newSessionState, {
|
|
|
|
options,
|
|
|
|
outgoingEdgeId,
|
|
|
|
logs,
|
|
|
|
})(messageContent, totalTokens)),
|
|
|
|
startTimeShouldBeUpdated: true,
|
2023-03-13 16:28:08 +01:00
|
|
|
}
|
2023-06-16 16:50:23 +02:00
|
|
|
}
|
2023-07-19 11:20:44 +02:00
|
|
|
|
|
|
|
const isNextBubbleMessageWithAssistantMessage =
|
2023-08-24 07:48:30 +02:00
|
|
|
(typebot: TypebotInSession) =>
|
2023-07-19 11:20:44 +02:00
|
|
|
(blockId: string, assistantVariableName?: string): boolean => {
|
|
|
|
if (!assistantVariableName) return false
|
|
|
|
const nextBlock = getNextBlock(typebot)(blockId)
|
|
|
|
if (!nextBlock) return false
|
|
|
|
return (
|
|
|
|
nextBlock.type === BubbleBlockType.TEXT &&
|
2023-11-08 15:34:16 +01:00
|
|
|
(nextBlock.content?.richText?.length ?? 0) > 0 &&
|
|
|
|
nextBlock.content?.richText?.at(0)?.children.at(0).text ===
|
2023-07-19 11:20:44 +02:00
|
|
|
`{{${assistantVariableName}}}`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const getNextBlock =
|
2023-08-24 07:48:30 +02:00
|
|
|
(typebot: TypebotInSession) =>
|
2023-07-19 11:20:44 +02:00
|
|
|
(blockId: string): Block | undefined => {
|
|
|
|
const group = typebot.groups.find((group) =>
|
|
|
|
group.blocks.find(byId(blockId))
|
|
|
|
)
|
|
|
|
if (!group) return
|
|
|
|
const blockIndex = group.blocks.findIndex(byId(blockId))
|
|
|
|
const nextBlockInGroup = group.blocks.at(blockIndex + 1)
|
|
|
|
if (nextBlockInGroup) return nextBlockInGroup
|
|
|
|
const outgoingEdgeId = group.blocks.at(blockIndex)?.outgoingEdgeId
|
|
|
|
if (!outgoingEdgeId) return
|
|
|
|
const outgoingEdge = typebot.edges.find(byId(outgoingEdgeId))
|
|
|
|
if (!outgoingEdge) return
|
|
|
|
const connectedGroup = typebot.groups.find(byId(outgoingEdge?.to.groupId))
|
|
|
|
if (!connectedGroup) return
|
|
|
|
return outgoingEdge.to.blockId
|
|
|
|
? connectedGroup.blocks.find(
|
|
|
|
(block) => block.id === outgoingEdge.to.blockId
|
|
|
|
)
|
|
|
|
: connectedGroup?.blocks.at(0)
|
|
|
|
}
|
2023-10-06 16:34:10 +02:00
|
|
|
|
|
|
|
const isCredentialsV2 = (credentials: Pick<Credentials, 'iv'>) =>
|
|
|
|
credentials.iv.length === 24
|