⚡ Introduce a new high-performing standalone chat API (#1200)
Closes #1154 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added authentication functionality for user sessions in chat API. - Introduced chat-related API endpoints for starting, previewing, and continuing chat sessions, and streaming messages. - Implemented WhatsApp API webhook handling for receiving and processing messages. - Added environment variable `NEXT_PUBLIC_CHAT_API_URL` for chat API URL configuration. - **Bug Fixes** - Adjusted file upload logic to correctly determine the API host. - Fixed message streaming URL in chat integration with OpenAI. - **Documentation** - Updated guides for creating blocks, local installation, self-hosting, and deployment to use `bun` instead of `pnpm`. - **Refactor** - Refactored chat API functionalities to use modular architecture. - Simplified client log saving and session update functionalities by using external functions. - Transitioned package management and workflow commands to use `bun`. - **Chores** - Switched to `bun` for package management in Dockerfiles and GitHub workflows. - Added new Dockerfile for chat API service setup with Bun framework. - Updated `.prettierignore` and documentation with new commands. - **Style** - No visible changes to end-users. - **Tests** - No visible changes to end-users. - **Revert** - No reverts in this release. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
130
packages/bot-engine/apiHandlers/getMessageStream.ts
Normal file
130
packages/bot-engine/apiHandlers/getMessageStream.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
|
||||
import { ChatCompletionOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai'
|
||||
import { OpenAI } from 'openai'
|
||||
import { decryptV2 } from '@typebot.io/lib/api/encryption/decryptV2'
|
||||
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
|
||||
import { ReadOnlyVariableStore } from '@typebot.io/forge'
|
||||
import {
|
||||
ParseVariablesOptions,
|
||||
parseVariables,
|
||||
} from '@typebot.io/variables/parseVariables'
|
||||
import { getOpenAIChatCompletionStream } from './legacy/getOpenAIChatCompletionStream'
|
||||
import { getCredentials } from '../queries/getCredentials'
|
||||
import { getSession } from '../queries/getSession'
|
||||
import { getBlockById } from '@typebot.io/schemas/helpers'
|
||||
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
|
||||
|
||||
type Props = {
|
||||
sessionId: string
|
||||
messages: OpenAI.Chat.ChatCompletionMessage[] | undefined
|
||||
}
|
||||
|
||||
export const getMessageStream = async ({ sessionId, messages }: Props) => {
|
||||
const session = await getSession(sessionId)
|
||||
|
||||
if (!session?.state || !session.state.currentBlockId)
|
||||
return { status: 404, message: 'Could not find session' }
|
||||
|
||||
const { group, block } = getBlockById(
|
||||
session.state.currentBlockId,
|
||||
session.state.typebotsQueue[0].typebot.groups
|
||||
)
|
||||
if (!block || !group)
|
||||
return {
|
||||
status: 404,
|
||||
message: 'Could not find block or group',
|
||||
}
|
||||
|
||||
if (!('options' in block))
|
||||
return {
|
||||
status: 400,
|
||||
message: 'This block does not have options',
|
||||
}
|
||||
|
||||
if (block.type === IntegrationBlockType.OPEN_AI && messages) {
|
||||
try {
|
||||
const stream = await getOpenAIChatCompletionStream(
|
||||
session.state,
|
||||
block.options as ChatCompletionOpenAIOptions,
|
||||
messages
|
||||
)
|
||||
if (!stream)
|
||||
return {
|
||||
status: 500,
|
||||
message: 'Could not create stream',
|
||||
}
|
||||
|
||||
return { stream }
|
||||
} catch (error) {
|
||||
if (error instanceof OpenAI.APIError) {
|
||||
const { message } = error
|
||||
return {
|
||||
status: 500,
|
||||
message,
|
||||
}
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isForgedBlockType(block.type))
|
||||
return {
|
||||
status: 400,
|
||||
message: 'This block does not have a stream function',
|
||||
}
|
||||
|
||||
const blockDef = forgedBlocks[block.type]
|
||||
const action = blockDef?.actions.find((a) => a.name === block.options?.action)
|
||||
|
||||
if (!action || !action.run?.stream)
|
||||
return {
|
||||
status: 400,
|
||||
message: 'This block does not have a stream function',
|
||||
}
|
||||
|
||||
try {
|
||||
if (!block.options.credentialsId)
|
||||
return { status: 404, message: 'Could not find credentials' }
|
||||
const credentials = await getCredentials(block.options.credentialsId)
|
||||
if (!credentials)
|
||||
return { status: 404, message: 'Could not find credentials' }
|
||||
const decryptedCredentials = await decryptV2(
|
||||
credentials.data,
|
||||
credentials.iv
|
||||
)
|
||||
const variables: ReadOnlyVariableStore = {
|
||||
list: () => session.state.typebotsQueue[0].typebot.variables,
|
||||
get: (id: string) => {
|
||||
const variable = session.state.typebotsQueue[0].typebot.variables.find(
|
||||
(variable) => variable.id === id
|
||||
)
|
||||
return variable?.value
|
||||
},
|
||||
parse: (text: string, params?: ParseVariablesOptions) =>
|
||||
parseVariables(
|
||||
session.state.typebotsQueue[0].typebot.variables,
|
||||
params
|
||||
)(text),
|
||||
}
|
||||
const stream = await action.run.stream.run({
|
||||
credentials: decryptedCredentials,
|
||||
options: block.options,
|
||||
variables,
|
||||
})
|
||||
if (!stream) return { status: 500, message: 'Could not create stream' }
|
||||
|
||||
return { stream }
|
||||
} catch (error) {
|
||||
if (error instanceof OpenAI.APIError) {
|
||||
const { message } = error
|
||||
return {
|
||||
status: 500,
|
||||
message,
|
||||
}
|
||||
}
|
||||
return {
|
||||
status: 500,
|
||||
message: 'Could not create stream',
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user