🐛 New sendMessage version for the new parser

Make sure old client still communicate with old parser
This commit is contained in:
Baptiste Arnaud
2023-10-06 10:14:26 +02:00
parent 6f3e9e9251
commit 3838ac9c3f
35 changed files with 710 additions and 416 deletions

View File

@@ -12,7 +12,7 @@ import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow'
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
import { isDefined } from '@typebot.io/lib/utils'
export const sendMessage = publicProcedure
export const sendMessageV1 = publicProcedure
.meta({
openapi: {
method: 'POST',
@@ -57,7 +57,12 @@ export const sendMessage = publicProcedure
logs,
clientSideActions,
newSessionState,
} = await startSession({ startParams, userId: user?.id, message })
} = await startSession({
version: 1,
startParams,
userId: user?.id,
message,
})
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
@@ -98,7 +103,7 @@ export const sendMessage = publicProcedure
newSessionState,
logs,
lastMessageNewFormat,
} = await continueBotFlow(session.state)(message)
} = await continueBotFlow(message, { version: 1, state: session.state })
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs

View File

@@ -0,0 +1,131 @@
import { publicProcedure } from '@/helpers/server/trpc'
import {
chatReplySchema,
sendMessageInputSchema,
} from '@typebot.io/schemas/features/chat/schema'
import { TRPCError } from '@trpc/server'
import { getSession } from '@typebot.io/bot-engine/queries/getSession'
import { startSession } from '@typebot.io/bot-engine/startSession'
import { saveStateToDatabase } from '@typebot.io/bot-engine/saveStateToDatabase'
import { restartSession } from '@typebot.io/bot-engine/queries/restartSession'
import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow'
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
import { isDefined } from '@typebot.io/lib/utils'
export const sendMessageV2 = publicProcedure
.meta({
openapi: {
method: 'POST',
path: '/sendMessage',
summary: 'Send a message',
description:
'To initiate a chat, do not provide a `sessionId` nor a `message`.\n\nContinue the conversation by providing the `sessionId` and the `message` that should answer the previous question.\n\nSet the `isPreview` option to `true` to chat with the non-published version of the typebot.',
},
})
.input(sendMessageInputSchema)
.output(chatReplySchema)
.mutation(
async ({
input: { sessionId, message, startParams, clientLogs },
ctx: { user },
}) => {
const session = sessionId ? await getSession(sessionId) : null
const isSessionExpired =
session &&
isDefined(session.state.expiryTimeout) &&
session.updatedAt.getTime() + session.state.expiryTimeout < Date.now()
if (isSessionExpired)
throw new TRPCError({
code: 'NOT_FOUND',
message: 'Session expired. You need to start a new session.',
})
if (!session) {
if (!startParams)
throw new TRPCError({
code: 'BAD_REQUEST',
message: 'Missing startParams',
})
const {
typebot,
messages,
input,
resultId,
dynamicTheme,
logs,
clientSideActions,
newSessionState,
} = await startSession({
version: 2,
startParams,
userId: user?.id,
message,
})
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
const session = startParams?.isOnlyRegistering
? await restartSession({
state: newSessionState,
})
: await saveStateToDatabase({
session: {
state: newSessionState,
},
input,
logs: allLogs,
clientSideActions,
})
return {
sessionId: session.id,
typebot: typebot
? {
id: typebot.id,
theme: typebot.theme,
settings: typebot.settings,
}
: undefined,
messages,
input,
resultId,
dynamicTheme,
logs,
clientSideActions,
}
} else {
const {
messages,
input,
clientSideActions,
newSessionState,
logs,
lastMessageNewFormat,
} = await continueBotFlow(message, { version: 2, state: session.state })
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
if (newSessionState)
await saveStateToDatabase({
session: {
id: session.id,
state: newSessionState,
},
input,
logs: allLogs,
clientSideActions,
})
return {
messages,
input,
clientSideActions,
dynamicTheme: parseDynamicTheme(newSessionState),
logs,
lastMessageNewFormat,
}
}
}
)

View File

@@ -1,11 +1,11 @@
import { generateOpenApiDocument } from 'trpc-openapi'
import { writeFileSync } from 'fs'
import { appRouter } from './routers/v1/_app'
import { appRouter } from './routers/appRouterV2'
const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'Chat API',
version: '1.0.0',
baseUrl: 'https://typebot.io/api/v1',
version: '2.0.0',
baseUrl: 'https://typebot.io/api/v2',
docsUrl: 'https://docs.typebot.io/api',
})

View File

@@ -1,12 +1,12 @@
import { sendMessage } from '@/features/chat/api/sendMessage'
import { sendMessageV1 } from '@/features/chat/api/sendMessageV1'
import { whatsAppRouter } from '@/features/whatsapp/api/router'
import { router } from '../../trpc'
import { router } from '../trpc'
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
export const appRouter = router({
sendMessage,
sendMessageV1,
getUploadUrl,
generateUploadUrl,
updateTypebotInSession,

View File

@@ -0,0 +1,16 @@
import { sendMessageV2 } from '@/features/chat/api/sendMessageV2'
import { whatsAppRouter } from '@/features/whatsapp/api/router'
import { router } from '../trpc'
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
export const appRouter = router({
sendMessageV2,
getUploadUrl,
generateUploadUrl,
updateTypebotInSession,
whatsAppRouter,
})
export type AppRouter = typeof appRouter

View File

@@ -1,4 +1,4 @@
import { appRouter } from '@/helpers/server/routers/v1/_app'
import { appRouter } from '@/helpers/server/routers/appRouterV1'
import { captureException } from '@sentry/nextjs'
import { createOpenApiNextHandler } from 'trpc-openapi'
import cors from 'nextjs-cors'

View File

@@ -0,0 +1,23 @@
import { appRouter } from '@/helpers/server/routers/appRouterV2'
import { captureException } from '@sentry/nextjs'
import { createOpenApiNextHandler } from 'trpc-openapi'
import cors from 'nextjs-cors'
import { NextApiRequest, NextApiResponse } from 'next'
import { createContext } from '@/helpers/server/context'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res)
return createOpenApiNextHandler({
router: appRouter,
createContext,
onError({ error }) {
if (error.code === 'INTERNAL_SERVER_ERROR') {
captureException(error)
console.error('Something went wrong', error)
}
},
})(req, res)
}
export default handler

View File

@@ -42,7 +42,7 @@ test('API chat execution should work on preview bot', async ({ request }) => {
await test.step('Start the chat', async () => {
const { sessionId, messages, input, resultId } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: {
startParams: {
typebot: typebotId,
@@ -83,7 +83,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Start the chat', async () => {
const { sessionId, messages, input, resultId } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: {
startParams: {
typebot: publicId,
@@ -111,12 +111,30 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer Name question', async () => {
const { messages, input } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: 'John', sessionId: chatSessionId },
})
).json()
expect(messages[0].content.richText).toStrictEqual([
{ children: [{ text: 'Nice to meet you John' }], type: 'p' },
{
type: 'p',
children: [
{ text: 'Nice to meet you ' },
{
type: 'inline-variable',
children: [
{
type: 'p',
children: [
{
text: 'John',
},
],
},
],
},
],
},
])
expect(messages[1].content.url).toMatch(new RegExp('giphy.com', 'gm'))
expect(input.type).toBe('number input')
@@ -124,7 +142,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer Age question', async () => {
const { messages, input } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: '24', sessionId: chatSessionId },
})
).json()
@@ -132,7 +150,25 @@ test('API chat execution should work on published bot', async ({ request }) => {
{ children: [{ text: 'Ok, you are an adult then 😁' }], type: 'p' },
])
expect(messages[1].content.richText).toStrictEqual([
{ children: [{ text: 'My magic number is 42' }], type: 'p' },
{
children: [
{ text: 'My magic number is ' },
{
type: 'inline-variable',
children: [
{
type: 'p',
children: [
{
text: '42',
},
],
},
],
},
],
type: 'p',
},
])
expect(messages[2].content.richText).toStrictEqual([
{
@@ -145,7 +181,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer Rating question', async () => {
const { messages, input } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: '8', sessionId: chatSessionId },
})
).json()
@@ -160,7 +196,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer Email question with wrong input', async () => {
const { messages, input } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: 'invalid email', sessionId: chatSessionId },
})
).json()
@@ -179,7 +215,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer Email question with valid input', async () => {
const { messages, input } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: 'typebot@email.com', sessionId: chatSessionId },
})
).json()
@@ -189,7 +225,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer URL question', async () => {
const { messages, input } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: 'https://typebot.io', sessionId: chatSessionId },
})
).json()
@@ -199,7 +235,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
await test.step('Answer Buttons question with invalid choice', async () => {
const { messages } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: { message: 'Yes', sessionId: chatSessionId },
})
).json()
@@ -227,7 +263,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
})
await test.step('Starting with a message when typebot starts with input should proceed', async () => {
const { messages } = await (
await request.post(`/api/v1/sendMessage`, {
await request.post(`/api/v2/sendMessage`, {
data: {
message: 'Hey',
startParams: {

View File

@@ -12,7 +12,7 @@ test('should correctly be injected', async ({ page }) => {
await page.goto(`/${typebotId}-public`)
await expect(page.locator('text="Your name is"')).toBeVisible()
await page.goto(`/${typebotId}-public?Name=Baptiste&Email=email@test.com`)
await expect(page.locator('text="Your name is Baptiste"')).toBeVisible()
await expect(page.locator('text="Baptiste"')).toBeVisible()
await expect(page.getByPlaceholder('Type your email...')).toHaveValue(
'email@test.com'
)

View File

@@ -1,11 +1,11 @@
import { generateOpenApiDocument } from 'trpc-openapi'
import { writeFileSync } from 'fs'
import { appRouter } from '@/helpers/server/routers/v1/_app'
import { appRouter } from '@/helpers/server/routers/appRouterV2'
const openApiDocument = generateOpenApiDocument(appRouter, {
title: 'Chat API',
version: '1.0.0',
baseUrl: 'https://typebot.io/api/v1',
version: '2.0.0',
baseUrl: 'https://typebot.io/api/v2',
docsUrl: 'https://docs.typebot.io/api',
})