🐛 New sendMessage version for the new parser
Make sure old client still communicate with old parser
This commit is contained in:
@ -142,8 +142,8 @@ test.describe.parallel('Google sheets integration', () => {
|
|||||||
.locator('input[placeholder="Type your email..."]')
|
.locator('input[placeholder="Type your email..."]')
|
||||||
.press('Enter')
|
.press('Enter')
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('typebot-standard').locator('text=Your name is:')
|
page.locator('typebot-standard').locator('text=Georges2')
|
||||||
).toHaveText(`Your name is: Georges2 Last name`)
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ export const ApiPreviewInstructions = (props: StackProps) => {
|
|||||||
lang={'shell'}
|
lang={'shell'}
|
||||||
value={`${parseApiHost(
|
value={`${parseApiHost(
|
||||||
typebot?.customDomain
|
typebot?.customDomain
|
||||||
)}/api/v1/sendMessage`}
|
)}/api/v2/sendMessage`}
|
||||||
/>
|
/>
|
||||||
<Text>with the following JSON body:</Text>
|
<Text>with the following JSON body:</Text>
|
||||||
<CodeEditor isReadOnly lang={'json'} value={startParamsBody} />
|
<CodeEditor isReadOnly lang={'json'} value={startParamsBody} />
|
||||||
@ -82,7 +82,7 @@ export const ApiPreviewInstructions = (props: StackProps) => {
|
|||||||
lang={'shell'}
|
lang={'shell'}
|
||||||
value={`${parseApiHost(
|
value={`${parseApiHost(
|
||||||
typebot?.customDomain
|
typebot?.customDomain
|
||||||
)}/api/v1/sendMessage`}
|
)}/api/v2/sendMessage`}
|
||||||
/>
|
/>
|
||||||
<Text>With the following JSON body:</Text>
|
<Text>With the following JSON body:</Text>
|
||||||
<CodeEditor isReadOnly lang={'json'} value={replyBody} />
|
<CodeEditor isReadOnly lang={'json'} value={replyBody} />
|
||||||
|
@ -61,7 +61,7 @@ export const ApiModal = ({
|
|||||||
lang={'shell'}
|
lang={'shell'}
|
||||||
value={`${parseApiHost(
|
value={`${parseApiHost(
|
||||||
typebot?.customDomain
|
typebot?.customDomain
|
||||||
)}/api/v1/sendMessage`}
|
)}/api/v2/sendMessage`}
|
||||||
/>
|
/>
|
||||||
<Text>with the following JSON body:</Text>
|
<Text>with the following JSON body:</Text>
|
||||||
<CodeEditor isReadOnly lang={'json'} value={startParamsBody} />
|
<CodeEditor isReadOnly lang={'json'} value={startParamsBody} />
|
||||||
@ -81,7 +81,7 @@ export const ApiModal = ({
|
|||||||
lang={'shell'}
|
lang={'shell'}
|
||||||
value={`${parseApiHost(
|
value={`${parseApiHost(
|
||||||
typebot?.customDomain
|
typebot?.customDomain
|
||||||
)}/api/v1/sendMessage`}
|
)}/api/v2/sendMessage`}
|
||||||
/>
|
/>
|
||||||
<Text>With the following JSON body:</Text>
|
<Text>With the following JSON body:</Text>
|
||||||
<CodeEditor isReadOnly lang={'json'} value={replyBody} />
|
<CodeEditor isReadOnly lang={'json'} value={replyBody} />
|
||||||
|
@ -43,7 +43,7 @@ export const parseReactBotProps = ({ typebot, apiHost }: BotProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const typebotImportCode = isCloudProdInstance()
|
export const typebotImportCode = isCloudProdInstance()
|
||||||
? `import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'`
|
? `import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'`
|
||||||
: `import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@${packageJson.version}/dist/web.js'`
|
: `import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@${packageJson.version}/dist/web.js'`
|
||||||
|
|
||||||
export const parseInlineScript = (script: string) =>
|
export const parseInlineScript = (script: string) =>
|
||||||
|
@ -91,6 +91,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
|||||||
|
|
||||||
const { newSessionState, messages, input, clientSideActions, logs } =
|
const { newSessionState, messages, input, clientSideActions, logs } =
|
||||||
await startSession({
|
await startSession({
|
||||||
|
version: 2,
|
||||||
message: undefined,
|
message: undefined,
|
||||||
startParams: {
|
startParams: {
|
||||||
isOnlyRegistering: !canSendDirectMessagesToUser,
|
isOnlyRegistering: !canSendDirectMessagesToUser,
|
||||||
|
@ -12,7 +12,7 @@ There, you can change the container dimensions. Here is a code example:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initStandard({
|
Typebot.initStandard({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
@ -32,7 +32,7 @@ Here is an example:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initPopup({
|
Typebot.initPopup({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
@ -72,7 +72,7 @@ If you have different bots on the same page you will have to make them distinct
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initStandard({
|
Typebot.initStandard({
|
||||||
id: 'bot1'
|
id: 'bot1'
|
||||||
@ -104,7 +104,7 @@ Here is an example:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initBubble({
|
Typebot.initBubble({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
|
@ -22,7 +22,7 @@ It should look like:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initPopup({
|
Typebot.initPopup({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
"openapi": "3.0.3",
|
"openapi": "3.0.3",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Chat API",
|
"title": "Chat API",
|
||||||
"version": "1.0.0"
|
"version": "2.0.0"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"url": "https://typebot.io/api/v1"
|
"url": "https://typebot.io/api/v2"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"/sendMessage": {
|
"/sendMessage": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "sendMessage",
|
"operationId": "sendMessageV2",
|
||||||
"summary": "Send a message",
|
"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.",
|
"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.",
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
|
@ -12,7 +12,7 @@ import { continueBotFlow } from '@typebot.io/bot-engine/continueBotFlow'
|
|||||||
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
|
import { parseDynamicTheme } from '@typebot.io/bot-engine/parseDynamicTheme'
|
||||||
import { isDefined } from '@typebot.io/lib/utils'
|
import { isDefined } from '@typebot.io/lib/utils'
|
||||||
|
|
||||||
export const sendMessage = publicProcedure
|
export const sendMessageV1 = publicProcedure
|
||||||
.meta({
|
.meta({
|
||||||
openapi: {
|
openapi: {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -57,7 +57,12 @@ export const sendMessage = publicProcedure
|
|||||||
logs,
|
logs,
|
||||||
clientSideActions,
|
clientSideActions,
|
||||||
newSessionState,
|
newSessionState,
|
||||||
} = await startSession({ startParams, userId: user?.id, message })
|
} = await startSession({
|
||||||
|
version: 1,
|
||||||
|
startParams,
|
||||||
|
userId: user?.id,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
|
||||||
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
||||||
|
|
||||||
@ -98,7 +103,7 @@ export const sendMessage = publicProcedure
|
|||||||
newSessionState,
|
newSessionState,
|
||||||
logs,
|
logs,
|
||||||
lastMessageNewFormat,
|
lastMessageNewFormat,
|
||||||
} = await continueBotFlow(session.state)(message)
|
} = await continueBotFlow(message, { version: 1, state: session.state })
|
||||||
|
|
||||||
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
||||||
|
|
131
apps/viewer/src/features/chat/api/sendMessageV2.ts
Normal file
131
apps/viewer/src/features/chat/api/sendMessageV2.ts
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
@ -1,11 +1,11 @@
|
|||||||
import { generateOpenApiDocument } from 'trpc-openapi'
|
import { generateOpenApiDocument } from 'trpc-openapi'
|
||||||
import { writeFileSync } from 'fs'
|
import { writeFileSync } from 'fs'
|
||||||
import { appRouter } from './routers/v1/_app'
|
import { appRouter } from './routers/appRouterV2'
|
||||||
|
|
||||||
const openApiDocument = generateOpenApiDocument(appRouter, {
|
const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||||
title: 'Chat API',
|
title: 'Chat API',
|
||||||
version: '1.0.0',
|
version: '2.0.0',
|
||||||
baseUrl: 'https://typebot.io/api/v1',
|
baseUrl: 'https://typebot.io/api/v2',
|
||||||
docsUrl: 'https://docs.typebot.io/api',
|
docsUrl: 'https://docs.typebot.io/api',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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 { whatsAppRouter } from '@/features/whatsapp/api/router'
|
||||||
import { router } from '../../trpc'
|
import { router } from '../trpc'
|
||||||
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
|
import { updateTypebotInSession } from '@/features/chat/api/updateTypebotInSession'
|
||||||
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
|
import { getUploadUrl } from '@/features/fileUpload/api/deprecated/getUploadUrl'
|
||||||
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
|
import { generateUploadUrl } from '@/features/fileUpload/api/generateUploadUrl'
|
||||||
|
|
||||||
export const appRouter = router({
|
export const appRouter = router({
|
||||||
sendMessage,
|
sendMessageV1,
|
||||||
getUploadUrl,
|
getUploadUrl,
|
||||||
generateUploadUrl,
|
generateUploadUrl,
|
||||||
updateTypebotInSession,
|
updateTypebotInSession,
|
16
apps/viewer/src/helpers/server/routers/appRouterV2.ts
Normal file
16
apps/viewer/src/helpers/server/routers/appRouterV2.ts
Normal 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
|
@ -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 { captureException } from '@sentry/nextjs'
|
||||||
import { createOpenApiNextHandler } from 'trpc-openapi'
|
import { createOpenApiNextHandler } from 'trpc-openapi'
|
||||||
import cors from 'nextjs-cors'
|
import cors from 'nextjs-cors'
|
||||||
|
23
apps/viewer/src/pages/api/v2/[...trpc].ts
Normal file
23
apps/viewer/src/pages/api/v2/[...trpc].ts
Normal 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
|
@ -42,7 +42,7 @@ test('API chat execution should work on preview bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Start the chat', async () => {
|
await test.step('Start the chat', async () => {
|
||||||
const { sessionId, messages, input, resultId } = await (
|
const { sessionId, messages, input, resultId } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: {
|
data: {
|
||||||
startParams: {
|
startParams: {
|
||||||
typebot: typebotId,
|
typebot: typebotId,
|
||||||
@ -83,7 +83,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Start the chat', async () => {
|
await test.step('Start the chat', async () => {
|
||||||
const { sessionId, messages, input, resultId } = await (
|
const { sessionId, messages, input, resultId } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: {
|
data: {
|
||||||
startParams: {
|
startParams: {
|
||||||
typebot: publicId,
|
typebot: publicId,
|
||||||
@ -111,12 +111,30 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer Name question', async () => {
|
await test.step('Answer Name question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: 'John', sessionId: chatSessionId },
|
data: { message: 'John', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
expect(messages[0].content.richText).toStrictEqual([
|
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(messages[1].content.url).toMatch(new RegExp('giphy.com', 'gm'))
|
||||||
expect(input.type).toBe('number input')
|
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 () => {
|
await test.step('Answer Age question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: '24', sessionId: chatSessionId },
|
data: { message: '24', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).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' },
|
{ children: [{ text: 'Ok, you are an adult then 😁' }], type: 'p' },
|
||||||
])
|
])
|
||||||
expect(messages[1].content.richText).toStrictEqual([
|
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([
|
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 () => {
|
await test.step('Answer Rating question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: '8', sessionId: chatSessionId },
|
data: { message: '8', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).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 () => {
|
await test.step('Answer Email question with wrong input', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: 'invalid email', sessionId: chatSessionId },
|
data: { message: 'invalid email', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).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 () => {
|
await test.step('Answer Email question with valid input', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: 'typebot@email.com', sessionId: chatSessionId },
|
data: { message: 'typebot@email.com', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).json()
|
||||||
@ -189,7 +225,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
|||||||
|
|
||||||
await test.step('Answer URL question', async () => {
|
await test.step('Answer URL question', async () => {
|
||||||
const { messages, input } = await (
|
const { messages, input } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: 'https://typebot.io', sessionId: chatSessionId },
|
data: { message: 'https://typebot.io', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).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 () => {
|
await test.step('Answer Buttons question with invalid choice', async () => {
|
||||||
const { messages } = await (
|
const { messages } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: { message: 'Yes', sessionId: chatSessionId },
|
data: { message: 'Yes', sessionId: chatSessionId },
|
||||||
})
|
})
|
||||||
).json()
|
).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 () => {
|
await test.step('Starting with a message when typebot starts with input should proceed', async () => {
|
||||||
const { messages } = await (
|
const { messages } = await (
|
||||||
await request.post(`/api/v1/sendMessage`, {
|
await request.post(`/api/v2/sendMessage`, {
|
||||||
data: {
|
data: {
|
||||||
message: 'Hey',
|
message: 'Hey',
|
||||||
startParams: {
|
startParams: {
|
||||||
|
@ -12,7 +12,7 @@ test('should correctly be injected', async ({ page }) => {
|
|||||||
await page.goto(`/${typebotId}-public`)
|
await page.goto(`/${typebotId}-public`)
|
||||||
await expect(page.locator('text="Your name is"')).toBeVisible()
|
await expect(page.locator('text="Your name is"')).toBeVisible()
|
||||||
await page.goto(`/${typebotId}-public?Name=Baptiste&Email=email@test.com`)
|
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(
|
await expect(page.getByPlaceholder('Type your email...')).toHaveValue(
|
||||||
'email@test.com'
|
'email@test.com'
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { generateOpenApiDocument } from 'trpc-openapi'
|
import { generateOpenApiDocument } from 'trpc-openapi'
|
||||||
import { writeFileSync } from 'fs'
|
import { writeFileSync } from 'fs'
|
||||||
import { appRouter } from '@/helpers/server/routers/v1/_app'
|
import { appRouter } from '@/helpers/server/routers/appRouterV2'
|
||||||
|
|
||||||
const openApiDocument = generateOpenApiDocument(appRouter, {
|
const openApiDocument = generateOpenApiDocument(appRouter, {
|
||||||
title: 'Chat API',
|
title: 'Chat API',
|
||||||
version: '1.0.0',
|
version: '2.0.0',
|
||||||
baseUrl: 'https://typebot.io/api/v1',
|
baseUrl: 'https://typebot.io/api/v2',
|
||||||
docsUrl: 'https://docs.typebot.io/api',
|
docsUrl: 'https://docs.typebot.io/api',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
{
|
{
|
||||||
"functions": {
|
"functions": {
|
||||||
|
"src/pages/api/v2/[...trpc].ts": {
|
||||||
|
"maxDuration": 150
|
||||||
|
},
|
||||||
"src/pages/api/v1/[...trpc].ts": {
|
"src/pages/api/v1/[...trpc].ts": {
|
||||||
"maxDuration": 150
|
"maxDuration": 150
|
||||||
},
|
},
|
||||||
|
@ -31,128 +31,102 @@ import { updateVariablesInSession } from './variables/updateVariablesInSession'
|
|||||||
import { startBotFlow } from './startBotFlow'
|
import { startBotFlow } from './startBotFlow'
|
||||||
import { TRPCError } from '@trpc/server'
|
import { TRPCError } from '@trpc/server'
|
||||||
|
|
||||||
export const continueBotFlow =
|
type Params = {
|
||||||
(state: SessionState) =>
|
version: 1 | 2
|
||||||
async (
|
state: SessionState
|
||||||
reply?: string
|
}
|
||||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
export const continueBotFlow = async (
|
||||||
let newSessionState = { ...state }
|
reply: string | undefined,
|
||||||
|
{ state, version }: Params
|
||||||
|
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||||
|
let newSessionState = { ...state }
|
||||||
|
|
||||||
if (!newSessionState.currentBlock) return startBotFlow(state)
|
if (!newSessionState.currentBlock) return startBotFlow({ state, version })
|
||||||
|
|
||||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||||
(group) => group.id === state.currentBlock?.groupId
|
(group) => group.id === state.currentBlock?.groupId
|
||||||
|
)
|
||||||
|
const blockIndex =
|
||||||
|
group?.blocks.findIndex(
|
||||||
|
(block) => block.id === state.currentBlock?.blockId
|
||||||
|
) ?? -1
|
||||||
|
|
||||||
|
const block = blockIndex >= 0 ? group?.blocks[blockIndex ?? 0] : null
|
||||||
|
|
||||||
|
if (!block || !group)
|
||||||
|
throw new TRPCError({
|
||||||
|
code: 'INTERNAL_SERVER_ERROR',
|
||||||
|
message: 'Group / block not found',
|
||||||
|
})
|
||||||
|
|
||||||
|
if (block.type === LogicBlockType.SET_VARIABLE) {
|
||||||
|
const existingVariable = state.typebotsQueue[0].typebot.variables.find(
|
||||||
|
byId(block.options.variableId)
|
||||||
)
|
)
|
||||||
const blockIndex =
|
if (existingVariable && reply) {
|
||||||
group?.blocks.findIndex(
|
const newVariable = {
|
||||||
(block) => block.id === state.currentBlock?.blockId
|
...existingVariable,
|
||||||
) ?? -1
|
value: safeJsonParse(reply),
|
||||||
|
|
||||||
const block = blockIndex >= 0 ? group?.blocks[blockIndex ?? 0] : null
|
|
||||||
|
|
||||||
if (!block || !group)
|
|
||||||
throw new TRPCError({
|
|
||||||
code: 'INTERNAL_SERVER_ERROR',
|
|
||||||
message: 'Group / block not found',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (block.type === LogicBlockType.SET_VARIABLE) {
|
|
||||||
const existingVariable = state.typebotsQueue[0].typebot.variables.find(
|
|
||||||
byId(block.options.variableId)
|
|
||||||
)
|
|
||||||
if (existingVariable && reply) {
|
|
||||||
const newVariable = {
|
|
||||||
...existingVariable,
|
|
||||||
value: safeJsonParse(reply),
|
|
||||||
}
|
|
||||||
newSessionState = updateVariablesInSession(state)([newVariable])
|
|
||||||
}
|
}
|
||||||
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
newSessionState = updateVariablesInSession(state)([newVariable])
|
||||||
const result = resumeWebhookExecution({
|
}
|
||||||
state,
|
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
||||||
block,
|
const result = resumeWebhookExecution({
|
||||||
response: JSON.parse(reply),
|
state,
|
||||||
})
|
block,
|
||||||
if (result.newSessionState) newSessionState = result.newSessionState
|
response: JSON.parse(reply),
|
||||||
} else if (
|
})
|
||||||
block.type === IntegrationBlockType.OPEN_AI &&
|
if (result.newSessionState) newSessionState = result.newSessionState
|
||||||
block.options.task === 'Create chat completion'
|
} else if (
|
||||||
) {
|
block.type === IntegrationBlockType.OPEN_AI &&
|
||||||
if (reply) {
|
block.options.task === 'Create chat completion'
|
||||||
const result = await resumeChatCompletion(state, {
|
) {
|
||||||
options: block.options,
|
if (reply) {
|
||||||
outgoingEdgeId: block.outgoingEdgeId,
|
const result = await resumeChatCompletion(state, {
|
||||||
})(reply)
|
options: block.options,
|
||||||
newSessionState = result.newSessionState
|
outgoingEdgeId: block.outgoingEdgeId,
|
||||||
|
})(reply)
|
||||||
|
newSessionState = result.newSessionState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedReply: string | undefined
|
||||||
|
|
||||||
|
if (isInputBlock(block)) {
|
||||||
|
const parsedReplyResult = parseReply(newSessionState)(reply, block)
|
||||||
|
|
||||||
|
if (parsedReplyResult.status === 'fail')
|
||||||
|
return {
|
||||||
|
...(await parseRetryMessage(newSessionState)(block)),
|
||||||
|
newSessionState,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let formattedReply: string | undefined
|
|
||||||
|
|
||||||
if (isInputBlock(block)) {
|
|
||||||
const parsedReplyResult = parseReply(newSessionState)(reply, block)
|
|
||||||
|
|
||||||
if (parsedReplyResult.status === 'fail')
|
|
||||||
return {
|
|
||||||
...(await parseRetryMessage(newSessionState)(block)),
|
|
||||||
newSessionState,
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedReply =
|
|
||||||
'reply' in parsedReplyResult ? parsedReplyResult.reply : undefined
|
|
||||||
const nextEdgeId = getOutgoingEdgeId(newSessionState)(
|
|
||||||
block,
|
|
||||||
formattedReply
|
|
||||||
)
|
|
||||||
const itemId = nextEdgeId
|
|
||||||
? newSessionState.typebotsQueue[0].typebot.edges.find(byId(nextEdgeId))
|
|
||||||
?.from.itemId
|
|
||||||
: undefined
|
|
||||||
newSessionState = await processAndSaveAnswer(
|
|
||||||
state,
|
|
||||||
block,
|
|
||||||
itemId
|
|
||||||
)(formattedReply)
|
|
||||||
}
|
|
||||||
|
|
||||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
|
||||||
|
|
||||||
|
formattedReply =
|
||||||
|
'reply' in parsedReplyResult ? parsedReplyResult.reply : undefined
|
||||||
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
||||||
|
const itemId = nextEdgeId
|
||||||
|
? newSessionState.typebotsQueue[0].typebot.edges.find(byId(nextEdgeId))
|
||||||
|
?.from.itemId
|
||||||
|
: undefined
|
||||||
|
newSessionState = await processAndSaveAnswer(
|
||||||
|
state,
|
||||||
|
block,
|
||||||
|
itemId
|
||||||
|
)(formattedReply)
|
||||||
|
}
|
||||||
|
|
||||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||||
const chatReply = await executeGroup(newSessionState)({
|
|
||||||
|
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
||||||
|
|
||||||
|
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||||
|
const chatReply = await executeGroup(
|
||||||
|
{
|
||||||
...group,
|
...group,
|
||||||
blocks: group.blocks.slice(blockIndex + 1),
|
blocks: group.blocks.slice(blockIndex + 1),
|
||||||
})
|
},
|
||||||
return {
|
{ version, state: newSessionState }
|
||||||
...chatReply,
|
)
|
||||||
lastMessageNewFormat:
|
|
||||||
formattedReply !== reply ? formattedReply : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nextEdgeId && state.typebotsQueue.length === 1)
|
|
||||||
return {
|
|
||||||
messages: [],
|
|
||||||
newSessionState,
|
|
||||||
lastMessageNewFormat:
|
|
||||||
formattedReply !== reply ? formattedReply : undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
|
|
||||||
|
|
||||||
newSessionState = nextGroup.newSessionState
|
|
||||||
|
|
||||||
if (!nextGroup.group)
|
|
||||||
return {
|
|
||||||
messages: [],
|
|
||||||
newSessionState,
|
|
||||||
lastMessageNewFormat:
|
|
||||||
formattedReply !== reply ? formattedReply : undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
const chatReply = await executeGroup(newSessionState)(nextGroup.group)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...chatReply,
|
...chatReply,
|
||||||
lastMessageNewFormat:
|
lastMessageNewFormat:
|
||||||
@ -160,6 +134,37 @@ export const continueBotFlow =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nextEdgeId && state.typebotsQueue.length === 1)
|
||||||
|
return {
|
||||||
|
messages: [],
|
||||||
|
newSessionState,
|
||||||
|
lastMessageNewFormat:
|
||||||
|
formattedReply !== reply ? formattedReply : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
|
||||||
|
|
||||||
|
newSessionState = nextGroup.newSessionState
|
||||||
|
|
||||||
|
if (!nextGroup.group)
|
||||||
|
return {
|
||||||
|
messages: [],
|
||||||
|
newSessionState,
|
||||||
|
lastMessageNewFormat:
|
||||||
|
formattedReply !== reply ? formattedReply : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatReply = await executeGroup(nextGroup.group, {
|
||||||
|
version,
|
||||||
|
state: newSessionState,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
...chatReply,
|
||||||
|
lastMessageNewFormat: formattedReply !== reply ? formattedReply : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const processAndSaveAnswer =
|
const processAndSaveAnswer =
|
||||||
(state: SessionState, block: InputBlock, itemId?: string) =>
|
(state: SessionState, block: InputBlock, itemId?: string) =>
|
||||||
async (reply: string | undefined): Promise<SessionState> => {
|
async (reply: string | undefined): Promise<SessionState> => {
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
BubbleBlock,
|
|
||||||
BubbleBlockType,
|
|
||||||
ChatReply,
|
ChatReply,
|
||||||
Group,
|
Group,
|
||||||
InputBlock,
|
InputBlock,
|
||||||
InputBlockType,
|
InputBlockType,
|
||||||
RuntimeOptions,
|
RuntimeOptions,
|
||||||
SessionState,
|
SessionState,
|
||||||
Variable,
|
|
||||||
} from '@typebot.io/schemas'
|
} from '@typebot.io/schemas'
|
||||||
import {
|
import {
|
||||||
isBubbleBlock,
|
isBubbleBlock,
|
||||||
isEmpty,
|
|
||||||
isInputBlock,
|
isInputBlock,
|
||||||
isIntegrationBlock,
|
isIntegrationBlock,
|
||||||
isLogicBlock,
|
isLogicBlock,
|
||||||
@ -26,49 +22,85 @@ import { injectVariableValuesInPictureChoiceBlock } from './blocks/inputs/pictur
|
|||||||
import { getPrefilledInputValue } from './getPrefilledValue'
|
import { getPrefilledInputValue } from './getPrefilledValue'
|
||||||
import { parseDateInput } from './blocks/inputs/date/parseDateInput'
|
import { parseDateInput } from './blocks/inputs/date/parseDateInput'
|
||||||
import { deepParseVariables } from './variables/deepParseVariables'
|
import { deepParseVariables } from './variables/deepParseVariables'
|
||||||
import { parseVideoUrl } from '@typebot.io/lib/parseVideoUrl'
|
import { parseBubbleBlock } from './parseBubbleBlock'
|
||||||
import { TDescendant, createPlateEditor } from '@udecode/plate-common'
|
|
||||||
import {
|
|
||||||
createDeserializeMdPlugin,
|
|
||||||
deserializeMd,
|
|
||||||
} from '@udecode/plate-serializer-md'
|
|
||||||
import { getVariablesToParseInfoInText } from './variables/parseVariables'
|
|
||||||
|
|
||||||
export const executeGroup =
|
type ContextProps = {
|
||||||
(
|
version: 1 | 2
|
||||||
state: SessionState,
|
state: SessionState
|
||||||
currentReply?: ChatReply,
|
currentReply?: ChatReply
|
||||||
currentLastBubbleId?: string
|
currentLastBubbleId?: string
|
||||||
) =>
|
}
|
||||||
async (
|
|
||||||
group: Group
|
|
||||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
|
||||||
const messages: ChatReply['messages'] = currentReply?.messages ?? []
|
|
||||||
let clientSideActions: ChatReply['clientSideActions'] =
|
|
||||||
currentReply?.clientSideActions
|
|
||||||
let logs: ChatReply['logs'] = currentReply?.logs
|
|
||||||
let nextEdgeId = null
|
|
||||||
let lastBubbleBlockId: string | undefined = currentLastBubbleId
|
|
||||||
|
|
||||||
let newSessionState = state
|
export const executeGroup = async (
|
||||||
|
group: Group,
|
||||||
|
{ version, state, currentReply, currentLastBubbleId }: ContextProps
|
||||||
|
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||||
|
const messages: ChatReply['messages'] = currentReply?.messages ?? []
|
||||||
|
let clientSideActions: ChatReply['clientSideActions'] =
|
||||||
|
currentReply?.clientSideActions
|
||||||
|
let logs: ChatReply['logs'] = currentReply?.logs
|
||||||
|
let nextEdgeId = null
|
||||||
|
let lastBubbleBlockId: string | undefined = currentLastBubbleId
|
||||||
|
|
||||||
for (const block of group.blocks) {
|
let newSessionState = state
|
||||||
nextEdgeId = block.outgoingEdgeId
|
|
||||||
|
|
||||||
if (isBubbleBlock(block)) {
|
for (const block of group.blocks) {
|
||||||
messages.push(
|
nextEdgeId = block.outgoingEdgeId
|
||||||
parseBubbleBlock(newSessionState.typebotsQueue[0].typebot.variables)(
|
|
||||||
block
|
if (isBubbleBlock(block)) {
|
||||||
)
|
messages.push(
|
||||||
)
|
parseBubbleBlock(block, {
|
||||||
lastBubbleBlockId = block.id
|
version,
|
||||||
continue
|
variables: newSessionState.typebotsQueue[0].typebot.variables,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
lastBubbleBlockId = block.id
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInputBlock(block))
|
||||||
|
return {
|
||||||
|
messages,
|
||||||
|
input: await parseInput(newSessionState)(block),
|
||||||
|
newSessionState: {
|
||||||
|
...newSessionState,
|
||||||
|
currentBlock: {
|
||||||
|
groupId: group.id,
|
||||||
|
blockId: block.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
clientSideActions,
|
||||||
|
logs,
|
||||||
}
|
}
|
||||||
|
const executionResponse = isLogicBlock(block)
|
||||||
|
? await executeLogic(newSessionState)(block)
|
||||||
|
: isIntegrationBlock(block)
|
||||||
|
? await executeIntegration(newSessionState)(block)
|
||||||
|
: null
|
||||||
|
|
||||||
if (isInputBlock(block))
|
if (!executionResponse) continue
|
||||||
|
if (executionResponse.logs)
|
||||||
|
logs = [...(logs ?? []), ...executionResponse.logs]
|
||||||
|
if (executionResponse.newSessionState)
|
||||||
|
newSessionState = executionResponse.newSessionState
|
||||||
|
if (
|
||||||
|
'clientSideActions' in executionResponse &&
|
||||||
|
executionResponse.clientSideActions
|
||||||
|
) {
|
||||||
|
clientSideActions = [
|
||||||
|
...(clientSideActions ?? []),
|
||||||
|
...executionResponse.clientSideActions.map((action) => ({
|
||||||
|
...action,
|
||||||
|
lastBubbleBlockId,
|
||||||
|
})),
|
||||||
|
]
|
||||||
|
if (
|
||||||
|
executionResponse.clientSideActions?.find(
|
||||||
|
(action) => action.expectsDedicatedReply
|
||||||
|
)
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
input: await parseInput(newSessionState)(block),
|
|
||||||
newSessionState: {
|
newSessionState: {
|
||||||
...newSessionState,
|
...newSessionState,
|
||||||
currentBlock: {
|
currentBlock: {
|
||||||
@ -79,78 +111,38 @@ export const executeGroup =
|
|||||||
clientSideActions,
|
clientSideActions,
|
||||||
logs,
|
logs,
|
||||||
}
|
}
|
||||||
const executionResponse = isLogicBlock(block)
|
|
||||||
? await executeLogic(newSessionState)(block)
|
|
||||||
: isIntegrationBlock(block)
|
|
||||||
? await executeIntegration(newSessionState)(block)
|
|
||||||
: null
|
|
||||||
|
|
||||||
if (!executionResponse) continue
|
|
||||||
if (executionResponse.logs)
|
|
||||||
logs = [...(logs ?? []), ...executionResponse.logs]
|
|
||||||
if (executionResponse.newSessionState)
|
|
||||||
newSessionState = executionResponse.newSessionState
|
|
||||||
if (
|
|
||||||
'clientSideActions' in executionResponse &&
|
|
||||||
executionResponse.clientSideActions
|
|
||||||
) {
|
|
||||||
clientSideActions = [
|
|
||||||
...(clientSideActions ?? []),
|
|
||||||
...executionResponse.clientSideActions.map((action) => ({
|
|
||||||
...action,
|
|
||||||
lastBubbleBlockId,
|
|
||||||
})),
|
|
||||||
]
|
|
||||||
if (
|
|
||||||
executionResponse.clientSideActions?.find(
|
|
||||||
(action) => action.expectsDedicatedReply
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
newSessionState: {
|
|
||||||
...newSessionState,
|
|
||||||
currentBlock: {
|
|
||||||
groupId: group.id,
|
|
||||||
blockId: block.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
clientSideActions,
|
|
||||||
logs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (executionResponse.outgoingEdgeId) {
|
|
||||||
nextEdgeId = executionResponse.outgoingEdgeId
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nextEdgeId && newSessionState.typebotsQueue.length === 1)
|
if (executionResponse.outgoingEdgeId) {
|
||||||
return { messages, newSessionState, clientSideActions, logs }
|
nextEdgeId = executionResponse.outgoingEdgeId
|
||||||
|
break
|
||||||
const nextGroup = await getNextGroup(newSessionState)(
|
|
||||||
nextEdgeId ?? undefined
|
|
||||||
)
|
|
||||||
|
|
||||||
newSessionState = nextGroup.newSessionState
|
|
||||||
|
|
||||||
if (!nextGroup.group) {
|
|
||||||
return { messages, newSessionState, clientSideActions, logs }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return executeGroup(
|
|
||||||
newSessionState,
|
|
||||||
{
|
|
||||||
messages,
|
|
||||||
clientSideActions,
|
|
||||||
logs,
|
|
||||||
},
|
|
||||||
lastBubbleBlockId
|
|
||||||
)(nextGroup.group)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!nextEdgeId && newSessionState.typebotsQueue.length === 1)
|
||||||
|
return { messages, newSessionState, clientSideActions, logs }
|
||||||
|
|
||||||
|
const nextGroup = await getNextGroup(newSessionState)(nextEdgeId ?? undefined)
|
||||||
|
|
||||||
|
newSessionState = nextGroup.newSessionState
|
||||||
|
|
||||||
|
if (!nextGroup.group) {
|
||||||
|
return { messages, newSessionState, clientSideActions, logs }
|
||||||
|
}
|
||||||
|
|
||||||
|
return executeGroup(nextGroup.group, {
|
||||||
|
version,
|
||||||
|
state: newSessionState,
|
||||||
|
currentReply: {
|
||||||
|
messages,
|
||||||
|
clientSideActions,
|
||||||
|
logs,
|
||||||
|
},
|
||||||
|
currentLastBubbleId: lastBubbleBlockId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const computeRuntimeOptions =
|
const computeRuntimeOptions =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
(block: InputBlock): Promise<RuntimeOptions> | undefined => {
|
(block: InputBlock): Promise<RuntimeOptions> | undefined => {
|
||||||
@ -161,136 +153,6 @@ const computeRuntimeOptions =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseBubbleBlock =
|
|
||||||
(variables: Variable[]) =>
|
|
||||||
(block: BubbleBlock): ChatReply['messages'][0] => {
|
|
||||||
switch (block.type) {
|
|
||||||
case BubbleBlockType.TEXT: {
|
|
||||||
return {
|
|
||||||
...block,
|
|
||||||
content: {
|
|
||||||
...block.content,
|
|
||||||
richText: parseVariablesInRichText(
|
|
||||||
block.content.richText,
|
|
||||||
variables
|
|
||||||
),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case BubbleBlockType.EMBED: {
|
|
||||||
const message = deepParseVariables(variables)(block)
|
|
||||||
return {
|
|
||||||
...message,
|
|
||||||
content: {
|
|
||||||
...message.content,
|
|
||||||
height:
|
|
||||||
typeof message.content.height === 'string'
|
|
||||||
? parseFloat(message.content.height)
|
|
||||||
: message.content.height,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case BubbleBlockType.VIDEO: {
|
|
||||||
const parsedContent = deepParseVariables(variables)(block.content)
|
|
||||||
return {
|
|
||||||
...block,
|
|
||||||
content: parsedContent.url ? parseVideoUrl(parsedContent.url) : {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return deepParseVariables(variables)(block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseVariablesInRichText = (
|
|
||||||
elements: TDescendant[],
|
|
||||||
variables: Variable[]
|
|
||||||
): TDescendant[] => {
|
|
||||||
const parsedElements: TDescendant[] = []
|
|
||||||
for (const element of elements) {
|
|
||||||
if ('text' in element) {
|
|
||||||
const text = element.text as string
|
|
||||||
if (isEmpty(text)) {
|
|
||||||
parsedElements.push(element)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const variablesInText = getVariablesToParseInfoInText(text, variables)
|
|
||||||
if (variablesInText.length === 0) {
|
|
||||||
parsedElements.push(element)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for (const variableInText of variablesInText) {
|
|
||||||
const textBeforeVariable = text.substring(0, variableInText.startIndex)
|
|
||||||
const textAfterVariable = text.substring(variableInText.endIndex)
|
|
||||||
const isStandaloneElement =
|
|
||||||
isEmpty(textBeforeVariable) && isEmpty(textAfterVariable)
|
|
||||||
const variableElements = convertMarkdownToRichText(
|
|
||||||
isStandaloneElement
|
|
||||||
? variableInText.value
|
|
||||||
: variableInText.value.replace(/[\n]+/g, ' ')
|
|
||||||
)
|
|
||||||
const variableElementsWithStyling = variableElements.map(
|
|
||||||
(variableElement) => ({
|
|
||||||
...variableElement,
|
|
||||||
children: [
|
|
||||||
...(variableElement.children as TDescendant[]).map((child) => ({
|
|
||||||
...element,
|
|
||||||
...child,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
)
|
|
||||||
if (isStandaloneElement) {
|
|
||||||
parsedElements.push(...variableElementsWithStyling)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const children: TDescendant[] = []
|
|
||||||
if (isNotEmpty(textBeforeVariable))
|
|
||||||
children.push({
|
|
||||||
...element,
|
|
||||||
text: textBeforeVariable,
|
|
||||||
})
|
|
||||||
children.push({
|
|
||||||
type: 'inline-variable',
|
|
||||||
children: variableElementsWithStyling,
|
|
||||||
})
|
|
||||||
if (isNotEmpty(textAfterVariable))
|
|
||||||
children.push({
|
|
||||||
...element,
|
|
||||||
text: textAfterVariable,
|
|
||||||
})
|
|
||||||
parsedElements.push(...children)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const type =
|
|
||||||
element.children.length === 1 &&
|
|
||||||
'text' in element.children[0] &&
|
|
||||||
(element.children[0].text as string).startsWith('{{') &&
|
|
||||||
(element.children[0].text as string).endsWith('}}')
|
|
||||||
? 'variable'
|
|
||||||
: element.type
|
|
||||||
|
|
||||||
parsedElements.push({
|
|
||||||
...element,
|
|
||||||
type,
|
|
||||||
children: parseVariablesInRichText(
|
|
||||||
element.children as TDescendant[],
|
|
||||||
variables
|
|
||||||
),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return parsedElements
|
|
||||||
}
|
|
||||||
|
|
||||||
const convertMarkdownToRichText = (text: string): TDescendant[] => {
|
|
||||||
const plugins = [createDeserializeMdPlugin()]
|
|
||||||
//@ts-ignore
|
|
||||||
return deserializeMd(createPlateEditor({ plugins }), text)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const parseInput =
|
export const parseInput =
|
||||||
(state: SessionState) =>
|
(state: SessionState) =>
|
||||||
async (block: InputBlock): Promise<ChatReply['input']> => {
|
async (block: InputBlock): Promise<ChatReply['input']> => {
|
||||||
|
186
packages/bot-engine/parseBubbleBlock.ts
Normal file
186
packages/bot-engine/parseBubbleBlock.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import { parseVideoUrl } from '@typebot.io/lib/parseVideoUrl'
|
||||||
|
import {
|
||||||
|
BubbleBlock,
|
||||||
|
Variable,
|
||||||
|
ChatReply,
|
||||||
|
BubbleBlockType,
|
||||||
|
} from '@typebot.io/schemas'
|
||||||
|
import { deepParseVariables } from './variables/deepParseVariables'
|
||||||
|
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
|
||||||
|
import { getVariablesToParseInfoInText } from './variables/parseVariables'
|
||||||
|
import { TDescendant, createPlateEditor } from '@udecode/plate-common'
|
||||||
|
import {
|
||||||
|
createDeserializeMdPlugin,
|
||||||
|
deserializeMd,
|
||||||
|
} from '@udecode/plate-serializer-md'
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
version: 1 | 2
|
||||||
|
variables: Variable[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseBubbleBlock = (
|
||||||
|
block: BubbleBlock,
|
||||||
|
{ version, variables }: Params
|
||||||
|
): ChatReply['messages'][0] => {
|
||||||
|
switch (block.type) {
|
||||||
|
case BubbleBlockType.TEXT: {
|
||||||
|
if (version === 1)
|
||||||
|
return deepParseVariables(
|
||||||
|
variables,
|
||||||
|
{},
|
||||||
|
{ takeLatestIfList: true }
|
||||||
|
)(block)
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
content: {
|
||||||
|
...block.content,
|
||||||
|
richText: parseVariablesInRichText(block.content.richText, {
|
||||||
|
variables,
|
||||||
|
takeLatestIfList: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case BubbleBlockType.EMBED: {
|
||||||
|
const message = deepParseVariables(variables)(block)
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
content: {
|
||||||
|
...message.content,
|
||||||
|
height:
|
||||||
|
typeof message.content.height === 'string'
|
||||||
|
? parseFloat(message.content.height)
|
||||||
|
: message.content.height,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case BubbleBlockType.VIDEO: {
|
||||||
|
const parsedContent = deepParseVariables(variables)(block.content)
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
content: parsedContent.url ? parseVideoUrl(parsedContent.url) : {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return deepParseVariables(variables)(block)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseVariablesInRichText = (
|
||||||
|
elements: TDescendant[],
|
||||||
|
{
|
||||||
|
variables,
|
||||||
|
takeLatestIfList,
|
||||||
|
}: { variables: Variable[]; takeLatestIfList?: boolean }
|
||||||
|
): TDescendant[] => {
|
||||||
|
const parsedElements: TDescendant[] = []
|
||||||
|
for (const element of elements) {
|
||||||
|
if ('text' in element) {
|
||||||
|
const text = element.text as string
|
||||||
|
if (isEmpty(text)) {
|
||||||
|
parsedElements.push(element)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const variablesInText = getVariablesToParseInfoInText(text, {
|
||||||
|
variables,
|
||||||
|
takeLatestIfList,
|
||||||
|
})
|
||||||
|
if (variablesInText.length === 0) {
|
||||||
|
parsedElements.push(element)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let lastTextEndIndex = 0
|
||||||
|
let index = -1
|
||||||
|
for (const variableInText of variablesInText) {
|
||||||
|
index += 1
|
||||||
|
const textBeforeVariable = text.substring(
|
||||||
|
lastTextEndIndex,
|
||||||
|
variableInText.startIndex
|
||||||
|
)
|
||||||
|
const textAfterVariable =
|
||||||
|
index === variablesInText.length - 1
|
||||||
|
? text.substring(variableInText.endIndex)
|
||||||
|
: undefined
|
||||||
|
lastTextEndIndex = variableInText.endIndex
|
||||||
|
const isStandaloneElement =
|
||||||
|
isEmpty(textBeforeVariable) && isEmpty(textAfterVariable)
|
||||||
|
const variableElements = convertMarkdownToRichText(
|
||||||
|
isStandaloneElement
|
||||||
|
? variableInText.value
|
||||||
|
: variableInText.value.replace(/[\n]+/g, ' ')
|
||||||
|
)
|
||||||
|
const variableElementsWithStyling = applyElementStyleToDescendants(
|
||||||
|
variableElements,
|
||||||
|
{
|
||||||
|
bold: element.bold,
|
||||||
|
italic: element.italic,
|
||||||
|
underline: element.underline,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isStandaloneElement) {
|
||||||
|
parsedElements.push(...variableElementsWithStyling)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const children: TDescendant[] = []
|
||||||
|
if (isNotEmpty(textBeforeVariable))
|
||||||
|
children.push({
|
||||||
|
...element,
|
||||||
|
text: textBeforeVariable,
|
||||||
|
})
|
||||||
|
children.push({
|
||||||
|
type: 'inline-variable',
|
||||||
|
children: variableElementsWithStyling,
|
||||||
|
})
|
||||||
|
if (isNotEmpty(textAfterVariable))
|
||||||
|
children.push({
|
||||||
|
...element,
|
||||||
|
text: textAfterVariable,
|
||||||
|
})
|
||||||
|
parsedElements.push(...children)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const type =
|
||||||
|
element.children.length === 1 &&
|
||||||
|
'text' in element.children[0] &&
|
||||||
|
(element.children[0].text as string).startsWith('{{') &&
|
||||||
|
(element.children[0].text as string).endsWith('}}')
|
||||||
|
? 'variable'
|
||||||
|
: element.type
|
||||||
|
|
||||||
|
parsedElements.push({
|
||||||
|
...element,
|
||||||
|
type,
|
||||||
|
children: parseVariablesInRichText(element.children as TDescendant[], {
|
||||||
|
variables,
|
||||||
|
takeLatestIfList,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return parsedElements
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyElementStyleToDescendants = (
|
||||||
|
variableElements: TDescendant[],
|
||||||
|
styles: { bold: unknown; italic: unknown; underline: unknown }
|
||||||
|
): TDescendant[] =>
|
||||||
|
variableElements.map((variableElement) => {
|
||||||
|
if ('text' in variableElement) return { ...styles, ...variableElement }
|
||||||
|
return {
|
||||||
|
...variableElement,
|
||||||
|
children: applyElementStyleToDescendants(
|
||||||
|
variableElement.children,
|
||||||
|
styles
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const convertMarkdownToRichText = (text: string): TDescendant[] => {
|
||||||
|
const plugins = [createDeserializeMdPlugin()]
|
||||||
|
return deserializeMd(createPlateEditor({ plugins }) as unknown as any, text)
|
||||||
|
}
|
@ -3,10 +3,17 @@ import { ChatReply, SessionState } from '@typebot.io/schemas'
|
|||||||
import { executeGroup } from './executeGroup'
|
import { executeGroup } from './executeGroup'
|
||||||
import { getNextGroup } from './getNextGroup'
|
import { getNextGroup } from './getNextGroup'
|
||||||
|
|
||||||
export const startBotFlow = async (
|
type Props = {
|
||||||
state: SessionState,
|
version: 1 | 2
|
||||||
|
state: SessionState
|
||||||
startGroupId?: string
|
startGroupId?: string
|
||||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
}
|
||||||
|
|
||||||
|
export const startBotFlow = async ({
|
||||||
|
version,
|
||||||
|
state,
|
||||||
|
startGroupId,
|
||||||
|
}: Props): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||||
let newSessionState = state
|
let newSessionState = state
|
||||||
if (startGroupId) {
|
if (startGroupId) {
|
||||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||||
@ -17,7 +24,7 @@ export const startBotFlow = async (
|
|||||||
code: 'BAD_REQUEST',
|
code: 'BAD_REQUEST',
|
||||||
message: "startGroupId doesn't exist",
|
message: "startGroupId doesn't exist",
|
||||||
})
|
})
|
||||||
return executeGroup(newSessionState)(group)
|
return executeGroup(group, { version, state: newSessionState })
|
||||||
}
|
}
|
||||||
const firstEdgeId =
|
const firstEdgeId =
|
||||||
newSessionState.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
newSessionState.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
||||||
@ -25,5 +32,5 @@ export const startBotFlow = async (
|
|||||||
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
||||||
newSessionState = nextGroup.newSessionState
|
newSessionState = nextGroup.newSessionState
|
||||||
if (!nextGroup.group) return { messages: [], newSessionState }
|
if (!nextGroup.group) return { messages: [], newSessionState }
|
||||||
return executeGroup(newSessionState)(nextGroup.group)
|
return executeGroup(nextGroup.group, { version, state: newSessionState })
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import { upsertResult } from './queries/upsertResult'
|
|||||||
import { continueBotFlow } from './continueBotFlow'
|
import { continueBotFlow } from './continueBotFlow'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
version: 1 | 2
|
||||||
message: string | undefined
|
message: string | undefined
|
||||||
startParams: StartParams
|
startParams: StartParams
|
||||||
userId: string | undefined
|
userId: string | undefined
|
||||||
@ -38,6 +39,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const startSession = async ({
|
export const startSession = async ({
|
||||||
|
version,
|
||||||
message,
|
message,
|
||||||
startParams,
|
startParams,
|
||||||
userId,
|
userId,
|
||||||
@ -135,7 +137,11 @@ export const startSession = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let chatReply = await startBotFlow(initialState, startParams.startGroupId)
|
let chatReply = await startBotFlow({
|
||||||
|
version,
|
||||||
|
state: initialState,
|
||||||
|
startGroupId: startParams.startGroupId,
|
||||||
|
})
|
||||||
|
|
||||||
// If params has message and first block is an input block, we can directly continue the bot flow
|
// If params has message and first block is an input block, we can directly continue the bot flow
|
||||||
if (message) {
|
if (message) {
|
||||||
@ -154,10 +160,13 @@ export const startSession = async ({
|
|||||||
resultId,
|
resultId,
|
||||||
typebot: newSessionState.typebotsQueue[0].typebot,
|
typebot: newSessionState.typebotsQueue[0].typebot,
|
||||||
})
|
})
|
||||||
chatReply = await continueBotFlow({
|
chatReply = await continueBotFlow(message, {
|
||||||
...newSessionState,
|
version,
|
||||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
state: {
|
||||||
})(message)
|
...newSessionState,
|
||||||
|
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,10 @@ type VariableToParseInformation = {
|
|||||||
|
|
||||||
export const getVariablesToParseInfoInText = (
|
export const getVariablesToParseInfoInText = (
|
||||||
text: string,
|
text: string,
|
||||||
variables: Variable[]
|
{
|
||||||
|
variables,
|
||||||
|
takeLatestIfList,
|
||||||
|
}: { variables: Variable[]; takeLatestIfList?: boolean }
|
||||||
): VariableToParseInformation[] => {
|
): VariableToParseInformation[] => {
|
||||||
const pattern = /\{\{([^{}]+)\}\}|(\$)\{\{([^{}]+)\}\}/g
|
const pattern = /\{\{([^{}]+)\}\}|(\$)\{\{([^{}]+)\}\}/g
|
||||||
const variablesToParseInfo: VariableToParseInformation[] = []
|
const variablesToParseInfo: VariableToParseInformation[] = []
|
||||||
@ -78,7 +81,12 @@ export const getVariablesToParseInfoInText = (
|
|||||||
startIndex: match.index,
|
startIndex: match.index,
|
||||||
endIndex: match.index + match[0].length,
|
endIndex: match.index + match[0].length,
|
||||||
textToReplace: match[0],
|
textToReplace: match[0],
|
||||||
value: safeStringify(variable?.value) ?? '',
|
value:
|
||||||
|
safeStringify(
|
||||||
|
takeLatestIfList && Array.isArray(variable?.value)
|
||||||
|
? variable?.value[variable?.value.length - 1]
|
||||||
|
: variable?.value
|
||||||
|
) ?? '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return variablesToParseInfo
|
return variablesToParseInfo
|
||||||
|
@ -64,9 +64,10 @@ export const resumeWhatsAppFlow = async ({
|
|||||||
|
|
||||||
const resumeResponse =
|
const resumeResponse =
|
||||||
session && !isSessionExpired
|
session && !isSessionExpired
|
||||||
? await continueBotFlow({ ...session.state, whatsApp: { contact } })(
|
? await continueBotFlow(messageContent, {
|
||||||
messageContent
|
version: 2,
|
||||||
)
|
state: { ...session.state, whatsApp: { contact } },
|
||||||
|
})
|
||||||
: workspaceId
|
: workspaceId
|
||||||
? await startWhatsAppSession({
|
? await startWhatsAppSession({
|
||||||
incomingMessage: messageContent,
|
incomingMessage: messageContent,
|
||||||
|
@ -51,7 +51,7 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
const result = await executeClientSideAction({ to, credentials })(action)
|
const result = await executeClientSideAction({ to, credentials })(action)
|
||||||
if (!result) continue
|
if (!result) continue
|
||||||
const { input, newSessionState, messages, clientSideActions } =
|
const { input, newSessionState, messages, clientSideActions } =
|
||||||
await continueBotFlow(state)(result.replyToSend)
|
await continueBotFlow(result.replyToSend, { version: 2, state })
|
||||||
|
|
||||||
return sendChatReplyToWhatsApp({
|
return sendChatReplyToWhatsApp({
|
||||||
to,
|
to,
|
||||||
@ -95,7 +95,7 @@ export const sendChatReplyToWhatsApp = async ({
|
|||||||
)
|
)
|
||||||
if (!result) continue
|
if (!result) continue
|
||||||
const { input, newSessionState, messages, clientSideActions } =
|
const { input, newSessionState, messages, clientSideActions } =
|
||||||
await continueBotFlow(state)(result.replyToSend)
|
await continueBotFlow(result.replyToSend, { version: 2, state })
|
||||||
|
|
||||||
return sendChatReplyToWhatsApp({
|
return sendChatReplyToWhatsApp({
|
||||||
to,
|
to,
|
||||||
|
@ -78,6 +78,7 @@ export const startWhatsAppSession = async ({
|
|||||||
defaultSessionExpiryTimeout
|
defaultSessionExpiryTimeout
|
||||||
|
|
||||||
return startSession({
|
return startSession({
|
||||||
|
version: 2,
|
||||||
message: incomingMessage,
|
message: incomingMessage,
|
||||||
startParams: {
|
startParams: {
|
||||||
typebot: publicTypebot.typebot.publicId as string,
|
typebot: publicTypebot.typebot.publicId as string,
|
||||||
|
@ -16,7 +16,7 @@ npm install @typebot.io/js
|
|||||||
|
|
||||||
```
|
```
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initStandard({
|
Typebot.initStandard({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
@ -34,7 +34,7 @@ There, you can change the container dimensions. Here is a code example:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initStandard({
|
Typebot.initStandard({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
@ -54,7 +54,7 @@ Here is an example:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initPopup({
|
Typebot.initPopup({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
@ -96,7 +96,7 @@ Here is an example:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js'
|
import Typebot from 'https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js'
|
||||||
|
|
||||||
Typebot.initBubble({
|
Typebot.initBubble({
|
||||||
typebot: 'my-typebot',
|
typebot: 'my-typebot',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/js",
|
"name": "@typebot.io/js",
|
||||||
"version": "0.1.34",
|
"version": "0.2.0",
|
||||||
"description": "Javascript library to display typebots on your website",
|
"description": "Javascript library to display typebots on your website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
@ -32,7 +32,7 @@ export async function getInitialChatReplyQuery({
|
|||||||
if (paymentInProgressState) removePaymentInProgressFromStorage()
|
if (paymentInProgressState) removePaymentInProgressFromStorage()
|
||||||
const { data, error } = await sendRequest<InitialChatReply>({
|
const { data, error } = await sendRequest<InitialChatReply>({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sendMessage`,
|
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v2/sendMessage`,
|
||||||
body: {
|
body: {
|
||||||
startParams: paymentInProgressState
|
startParams: paymentInProgressState
|
||||||
? undefined
|
? undefined
|
||||||
|
@ -8,6 +8,6 @@ export const sendMessageQuery = ({
|
|||||||
}: SendMessageInput & { apiHost?: string }) =>
|
}: SendMessageInput & { apiHost?: string }) =>
|
||||||
sendRequest<ChatReply>({
|
sendRequest<ChatReply>({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sendMessage`,
|
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v2/sendMessage`,
|
||||||
body,
|
body,
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/nextjs",
|
"name": "@typebot.io/nextjs",
|
||||||
"version": "0.1.34",
|
"version": "0.2.0",
|
||||||
"description": "Convenient library to display typebots on your Next.js website",
|
"description": "Convenient library to display typebots on your Next.js website",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@typebot.io/react",
|
"name": "@typebot.io/react",
|
||||||
"version": "0.1.34",
|
"version": "0.2.0",
|
||||||
"description": "Convenient library to display typebots on your React app",
|
"description": "Convenient library to display typebots on your React app",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
@ -40,7 +40,7 @@ class Typebot_Public
|
|||||||
|
|
||||||
function typebot_script()
|
function typebot_script()
|
||||||
{
|
{
|
||||||
echo '<script type="module">import Typebot from "https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js";';
|
echo '<script type="module">import Typebot from "https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js";';
|
||||||
if (
|
if (
|
||||||
get_option('excluded_pages') !== null &&
|
get_option('excluded_pages') !== null &&
|
||||||
get_option('excluded_pages') !== ''
|
get_option('excluded_pages') !== ''
|
||||||
@ -91,7 +91,7 @@ class Typebot_Public
|
|||||||
|
|
||||||
public function add_typebot_container($attributes = [])
|
public function add_typebot_container($attributes = [])
|
||||||
{
|
{
|
||||||
$lib_url = "https://cdn.jsdelivr.net/npm/@typebot.io/js@0.1/dist/web.js";
|
$lib_url = "https://cdn.jsdelivr.net/npm/@typebot.io/js@0.2/dist/web.js";
|
||||||
$width = '100%';
|
$width = '100%';
|
||||||
$height = '500px';
|
$height = '500px';
|
||||||
$api_host = 'https://viewer.typebot.io';
|
$api_host = 'https://viewer.typebot.io';
|
||||||
|
Reference in New Issue
Block a user