2
0

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:
Baptiste Arnaud
2024-03-21 10:23:23 +01:00
committed by GitHub
parent 5b9176708c
commit 2fcf83c529
51 changed files with 1446 additions and 494 deletions

View 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',
}
}
}