♻️ (api) Auto start bot if starting with input
Closes #877, closes #884
This commit is contained in:
@ -103,7 +103,7 @@ test.describe.parallel('Google sheets integration', () => {
|
||||
await page.click('text=Select an operation')
|
||||
await page.click('text=Get data from sheet')
|
||||
|
||||
await page.getByRole('button', { name: 'Rows to filter' }).click()
|
||||
await page.getByRole('button', { name: 'Select row(s)' }).click()
|
||||
await page.getByRole('button', { name: 'Add filter rule' }).click()
|
||||
await page.click('text=Select a column')
|
||||
await page.click('button >> text="Email"')
|
||||
|
@ -34,7 +34,7 @@ test.describe('Send email block', () => {
|
||||
)
|
||||
await page.fill('[placeholder="John Smith"]', 'John Smith')
|
||||
await page.fill('[placeholder="mail.provider.com"]', env.SMTP_HOST)
|
||||
await page.fill('[placeholder="user@provider.com"]', env.SMTP_USERNAME)
|
||||
await page.getByLabel('Username').fill(env.SMTP_USERNAME)
|
||||
await page.fill('[type="password"]', env.SMTP_PASSWORD)
|
||||
await page.fill('input[role="spinbutton"]', env.SMTP_PORT.toString())
|
||||
await expect(createButton).toBeEnabled()
|
||||
|
@ -57,7 +57,9 @@ test.describe('Starter workspace', () => {
|
||||
},
|
||||
])
|
||||
await page.goto(`/typebots/${typebotId}/share`)
|
||||
await expect(page.locator('[data-testid="pro-lock-tag"]')).toBeVisible()
|
||||
await expect(
|
||||
page.locator('[data-testid="pro-lock-tag"]').nth(0)
|
||||
).toBeVisible()
|
||||
await page.click('text=Add my domain')
|
||||
await expect(
|
||||
page.locator(
|
||||
|
@ -91,6 +91,7 @@ export const startWhatsAppPreview = authenticatedProcedure
|
||||
|
||||
const { newSessionState, messages, input, clientSideActions, logs } =
|
||||
await startSession({
|
||||
message: undefined,
|
||||
startParams: {
|
||||
isOnlyRegistering: !canSendDirectMessagesToUser,
|
||||
typebot: typebotId,
|
||||
|
@ -57,7 +57,7 @@ export const sendMessage = publicProcedure
|
||||
logs,
|
||||
clientSideActions,
|
||||
newSessionState,
|
||||
} = await startSession({ startParams, userId: user?.id })
|
||||
} = await startSession({ startParams, userId: user?.id, message })
|
||||
|
||||
const allLogs = clientLogs ? [...(logs ?? []), ...clientLogs] : logs
|
||||
|
||||
|
102
apps/viewer/src/test/assets/typebots/chat/startingWithInput.json
Normal file
102
apps/viewer/src/test/assets/typebots/chat/startingWithInput.json
Normal file
@ -0,0 +1,102 @@
|
||||
{
|
||||
"version": "5",
|
||||
"id": "clnbugp6a00011ackz0k3zfkp",
|
||||
"name": "My typebot",
|
||||
"groups": [
|
||||
{
|
||||
"id": "k2nokn9v0zyhae0wqcxsbqa7",
|
||||
"title": "Start",
|
||||
"graphCoordinates": { "x": 0, "y": 0 },
|
||||
"blocks": [
|
||||
{
|
||||
"id": "sx4xmdbosubnxkhcg6x521p1",
|
||||
"groupId": "k2nokn9v0zyhae0wqcxsbqa7",
|
||||
"outgoingEdgeId": "fj2ga89lctnuwcdsshwtxmhp",
|
||||
"type": "start",
|
||||
"label": "Start"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "g8kdars2ahr3cyz2qf1f7w4i",
|
||||
"title": "Group #1",
|
||||
"graphCoordinates": { "x": 235, "y": 170 },
|
||||
"blocks": [
|
||||
{
|
||||
"id": "prh6snup7cbmoxtf5vox8kjw",
|
||||
"groupId": "g8kdars2ahr3cyz2qf1f7w4i",
|
||||
"type": "text input",
|
||||
"options": {
|
||||
"labels": {
|
||||
"placeholder": "Type your answer...",
|
||||
"button": "Send"
|
||||
},
|
||||
"isLong": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dpyyb38amnwwl4q461el2uf6",
|
||||
"groupId": "g8kdars2ahr3cyz2qf1f7w4i",
|
||||
"type": "text",
|
||||
"content": {
|
||||
"richText": [
|
||||
{ "type": "p", "children": [{ "text": "That's nice!" }] }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "fj2ga89lctnuwcdsshwtxmhp",
|
||||
"from": {
|
||||
"groupId": "k2nokn9v0zyhae0wqcxsbqa7",
|
||||
"blockId": "sx4xmdbosubnxkhcg6x521p1"
|
||||
},
|
||||
"to": { "groupId": "g8kdars2ahr3cyz2qf1f7w4i" }
|
||||
}
|
||||
],
|
||||
"variables": [],
|
||||
"theme": {
|
||||
"general": {
|
||||
"font": "Open Sans",
|
||||
"background": { "type": "Color", "content": "#ffffff" }
|
||||
},
|
||||
"chat": {
|
||||
"hostAvatar": { "isEnabled": true },
|
||||
"hostBubbles": { "backgroundColor": "#F7F8FF", "color": "#303235" },
|
||||
"guestBubbles": { "backgroundColor": "#FF8E21", "color": "#FFFFFF" },
|
||||
"buttons": { "backgroundColor": "#0042DA", "color": "#FFFFFF" },
|
||||
"inputs": {
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"color": "#303235",
|
||||
"placeholderColor": "#9095A0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedThemeTemplateId": null,
|
||||
"settings": {
|
||||
"general": {
|
||||
"isBrandingEnabled": false,
|
||||
"isInputPrefillEnabled": true,
|
||||
"isHideQueryParamsEnabled": true,
|
||||
"rememberUser": { "isEnabled": false }
|
||||
},
|
||||
"typingEmulation": { "enabled": true, "speed": 300, "maxDelay": 1.5 },
|
||||
"metadata": {
|
||||
"description": "Build beautiful conversational forms and embed them directly in your applications without a line of code. Triple your response rate and collect answers that has more value compared to a traditional form."
|
||||
}
|
||||
},
|
||||
"createdAt": "2023-10-04T14:28:55.282Z",
|
||||
"updatedAt": "2023-10-04T14:29:11.949Z",
|
||||
"icon": null,
|
||||
"folderId": null,
|
||||
"publicId": null,
|
||||
"customDomain": null,
|
||||
"workspaceId": "proWorkspace",
|
||||
"resultsTablePreferences": null,
|
||||
"isArchived": false,
|
||||
"isClosed": false,
|
||||
"whatsAppCredentialsId": null
|
||||
}
|
@ -27,6 +27,13 @@ test('API chat execution should work on preview bot', async ({ request }) => {
|
||||
id: 'chat-sub-bot',
|
||||
publicId: 'chat-sub-bot-public',
|
||||
})
|
||||
await importTypebotInDatabase(
|
||||
getTestAsset('typebots/chat/startingWithInput.json'),
|
||||
{
|
||||
id: 'starting-with-input',
|
||||
publicId: 'starting-with-input-public',
|
||||
}
|
||||
)
|
||||
await createWebhook(typebotId, {
|
||||
id: 'chat-webhook-id',
|
||||
method: HttpMethod.GET,
|
||||
@ -218,4 +225,26 @@ test('API chat execution should work on published bot', async ({ request }) => {
|
||||
])
|
||||
expect(messages[2].content.richText.length).toBeGreaterThan(0)
|
||||
})
|
||||
await test.step('Starting with a message when typebot starts with input should proceed', async () => {
|
||||
const { messages } = await (
|
||||
await request.post(`/api/v1/sendMessage`, {
|
||||
data: {
|
||||
message: 'Hey',
|
||||
startParams: {
|
||||
typebot: 'starting-with-input-public',
|
||||
},
|
||||
} satisfies SendMessageInput,
|
||||
})
|
||||
).json()
|
||||
expect(messages[0].content.richText).toStrictEqual([
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: "That's nice!",
|
||||
},
|
||||
],
|
||||
type: 'p',
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
@ -28,6 +28,7 @@ import { validateRatingReply } from './blocks/inputs/rating/validateRatingReply'
|
||||
import { parsePictureChoicesReply } from './blocks/inputs/pictureChoice/parsePictureChoicesReply'
|
||||
import { parseVariables } from './variables/parseVariables'
|
||||
import { updateVariablesInSession } from './variables/updateVariablesInSession'
|
||||
import { startBotFlow } from './startBotFlow'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
export const continueBotFlow =
|
||||
@ -36,6 +37,9 @@ export const continueBotFlow =
|
||||
reply?: string
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
let newSessionState = { ...state }
|
||||
|
||||
if (!newSessionState.currentBlock) return startBotFlow(state)
|
||||
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
(group) => group.id === state.currentBlock?.groupId
|
||||
)
|
||||
|
@ -26,14 +26,19 @@ import { startBotFlow } from './startBotFlow'
|
||||
import { prefillVariables } from './variables/prefillVariables'
|
||||
import { deepParseVariables } from './variables/deepParseVariables'
|
||||
import { injectVariablesFromExistingResult } from './variables/injectVariablesFromExistingResult'
|
||||
import { getNextGroup } from './getNextGroup'
|
||||
import { upsertResult } from './queries/upsertResult'
|
||||
import { continueBotFlow } from './continueBotFlow'
|
||||
|
||||
type Props = {
|
||||
message: string | undefined
|
||||
startParams: StartParams
|
||||
userId: string | undefined
|
||||
initialSessionState?: Pick<SessionState, 'whatsApp' | 'expiryTimeout'>
|
||||
}
|
||||
|
||||
export const startSession = async ({
|
||||
message,
|
||||
startParams,
|
||||
userId,
|
||||
initialSessionState,
|
||||
@ -130,13 +135,39 @@ export const startSession = async ({
|
||||
}
|
||||
}
|
||||
|
||||
let chatReply = await startBotFlow(initialState, startParams.startGroupId)
|
||||
|
||||
// If params has message and first block is an input block, we can directly continue the bot flow
|
||||
if (message) {
|
||||
const firstEdgeId =
|
||||
chatReply.newSessionState.typebotsQueue[0].typebot.groups[0].blocks[0]
|
||||
.outgoingEdgeId
|
||||
const nextGroup = await getNextGroup(chatReply.newSessionState)(firstEdgeId)
|
||||
const newSessionState = nextGroup.newSessionState
|
||||
const firstBlock = nextGroup.group?.blocks.at(0)
|
||||
if (firstBlock && isInputBlock(firstBlock)) {
|
||||
const resultId = newSessionState.typebotsQueue[0].resultId
|
||||
if (resultId)
|
||||
await upsertResult({
|
||||
hasStarted: true,
|
||||
isCompleted: false,
|
||||
resultId,
|
||||
typebot: newSessionState.typebotsQueue[0].typebot,
|
||||
})
|
||||
chatReply = await continueBotFlow({
|
||||
...newSessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
})(message)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
messages,
|
||||
input,
|
||||
clientSideActions: startFlowClientActions,
|
||||
newSessionState,
|
||||
logs,
|
||||
} = await startBotFlow(initialState, startParams.startGroupId)
|
||||
} = chatReply
|
||||
|
||||
const clientSideActions = startFlowClientActions ?? []
|
||||
|
||||
|
@ -12,7 +12,6 @@ import { decrypt } from '@typebot.io/lib/api'
|
||||
import { saveStateToDatabase } from '../saveStateToDatabase'
|
||||
import prisma from '@typebot.io/lib/prisma'
|
||||
import { isDefined } from '@typebot.io/lib/utils'
|
||||
import { startBotFlow } from '../startBotFlow'
|
||||
|
||||
type Props = {
|
||||
receivedMessage: WhatsAppIncomingMessage
|
||||
@ -65,9 +64,9 @@ export const resumeWhatsAppFlow = async ({
|
||||
|
||||
const resumeResponse =
|
||||
session && !isSessionExpired
|
||||
? session.state.currentBlock
|
||||
? await continueBotFlow(session.state)(messageContent)
|
||||
: await startBotFlow({ ...session.state, whatsApp: { contact } })
|
||||
? await continueBotFlow({ ...session.state, whatsApp: { contact } })(
|
||||
messageContent
|
||||
)
|
||||
: workspaceId
|
||||
? await startWhatsAppSession({
|
||||
incomingMessage: messageContent,
|
||||
|
@ -12,11 +12,8 @@ import {
|
||||
WhatsAppCredentials,
|
||||
defaultSessionExpiryTimeout,
|
||||
} from '@typebot.io/schemas/features/whatsapp'
|
||||
import { isInputBlock, isNotDefined } from '@typebot.io/lib/utils'
|
||||
import { isNotDefined } from '@typebot.io/lib/utils'
|
||||
import { startSession } from '../startSession'
|
||||
import { getNextGroup } from '../getNextGroup'
|
||||
import { continueBotFlow } from '../continueBotFlow'
|
||||
import { upsertResult } from '../queries/upsertResult'
|
||||
|
||||
type Props = {
|
||||
incomingMessage?: string
|
||||
@ -80,7 +77,8 @@ export const startWhatsAppSession = async ({
|
||||
publicTypebot.settings.whatsApp?.sessionExpiryTimeout ??
|
||||
defaultSessionExpiryTimeout
|
||||
|
||||
let chatReply = await startSession({
|
||||
return startSession({
|
||||
message: incomingMessage,
|
||||
startParams: {
|
||||
typebot: publicTypebot.typebot.publicId as string,
|
||||
},
|
||||
@ -92,31 +90,6 @@ export const startWhatsAppSession = async ({
|
||||
expiryTimeout: sessionExpiryTimeoutHours * 60 * 60 * 1000,
|
||||
},
|
||||
})
|
||||
|
||||
let sessionState: SessionState = chatReply.newSessionState
|
||||
|
||||
// If first block is an input block, we can directly continue the bot flow
|
||||
const firstEdgeId =
|
||||
sessionState.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
const nextGroup = await getNextGroup(sessionState)(firstEdgeId)
|
||||
sessionState = nextGroup.newSessionState
|
||||
const firstBlock = nextGroup.group?.blocks.at(0)
|
||||
if (firstBlock && isInputBlock(firstBlock)) {
|
||||
const resultId = sessionState.typebotsQueue[0].resultId
|
||||
if (resultId)
|
||||
await upsertResult({
|
||||
hasStarted: true,
|
||||
isCompleted: false,
|
||||
resultId,
|
||||
typebot: sessionState.typebotsQueue[0].typebot,
|
||||
})
|
||||
chatReply = await continueBotFlow({
|
||||
...sessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
})(incomingMessage)
|
||||
}
|
||||
|
||||
return chatReply
|
||||
}
|
||||
|
||||
export const messageMatchStartCondition = (
|
||||
|
Reference in New Issue
Block a user