Skip to content

Commit

Permalink
♻️ (api) Auto start bot if starting with input
Browse files Browse the repository at this point in the history
Closes #877, closes #884
  • Loading branch information
baptisteArno committed Oct 4, 2023
1 parent 2bc9dfb commit 9e6a1f7
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions apps/builder/src/features/whatsapp/startWhatsAppPreview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export const startWhatsAppPreview = authenticatedProcedure

const { newSessionState, messages, input, clientSideActions, logs } =
await startSession({
message: undefined,
startParams: {
isOnlyRegistering: !canSendDirectMessagesToUser,
typebot: typebotId,
Expand Down
2 changes: 1 addition & 1 deletion apps/viewer/src/features/chat/api/sendMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
102 changes: 102 additions & 0 deletions apps/viewer/src/test/assets/typebots/chat/startingWithInput.json
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions apps/viewer/src/test/chat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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',
},
])
})
})
4 changes: 4 additions & 0 deletions packages/bot-engine/continueBotFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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
)
Expand Down
33 changes: 32 additions & 1 deletion packages/bot-engine/startSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 ?? []

Expand Down
7 changes: 3 additions & 4 deletions packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
33 changes: 3 additions & 30 deletions packages/bot-engine/whatsapp/startWhatsAppSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
},
Expand All @@ -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 = (
Expand Down

3 comments on commit 9e6a1f7

@vercel
Copy link

@vercel vercel bot commented on 9e6a1f7 Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

builder-v2 – ./apps/builder

builder-v2-git-main-typebot-io.vercel.app
builder-v2-typebot-io.vercel.app
app.typebot.io

@vercel
Copy link

@vercel vercel bot commented on 9e6a1f7 Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 9e6a1f7 Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viewer-v2 – ./apps/viewer

localove.online
projektelco.com
proscale.com.br
sellmycarbr.com
share.bot.gives
appmillion.online
averdadehoje.site
bot.cerograsa.com
bot.chatbotcv.com
bot.hostnation.de
bot.ketoolife.com
bot.phuonghub.com
bot.reviewamp.com
bot.reviewzer.com
bot.uluhub.com.br
cares.urlabout.me
chat.ezbooking.ai
chat.gaswadern.de
chat.gniorder.com
chat.leadmagic.io
chat.onrentme.com
chat.rojie.online
chatdocidadao.com
chatwebonline.com
ds-leadmagnet.com
encode.share5.net
fmm.wpwakanda.com
footballmeetup.ie
gentleman-shop.fr
island.wakanda.is
k1.kandabrand.com
kp.pedroknoll.com
kylebot.goafk.com
lb.ticketfute.com
mariwelash.com.br
metodoelev.com.br
nutriandreia.shop
order.chatjer.com
ov1.wpwakanda.com
ov2.wpwakanda.com
ov3.wpwakanda.com
pcb.drapamela.com
seribusebulan.com
softwarelucra.com
support.triplo.ai
survey.collab.day
test.eqfeqfeq.com
viewer.typebot.io
welcome.triplo.ai
www.thegymgame.it
zeropendencia.com
1988.bouclidom.com
a.onewebcenter.com
amancarseat.online
amostra-safe.click
viewer-v2-typebot-io.vercel.app
mdb.assessoria.aloisio.progenbr.com
mdb.assessoria.girotto.progenbr.com
mdb.assessoria.marinho.progenbr.com
mdb.assessoria.rodrigo.progenbr.com
register.thailandmicespecialist.com
mdb.assessoria.desideri.progenbr.com
mdb.assessoria.fernanda.progenbr.com
mdb.assessoria.jbatista.progenbr.com
mdb.assessoria.mauricio.progenbr.com
mdb.evento.autocadastro.progenbr.com
form.shopmercedesbenzsouthorlando.com
mdb.evento.equipeinterna.progenbr.com
bot.studiotecnicoimmobiliaremerelli.it
mdb.assessoria.boaventura.progenbr.com
mdb.assessoria.jtrebesqui.progenbr.com
pesquisa.escolamodacomproposito.com.br
anamnese.clinicaramosodontologia.com.br
gabinete.baleia.formulario.progenbr.com
mdb.assessoria.carreirinha.progenbr.com
chrome-os-inquiry-system.itschromeos.com
mdb.assessoria.paulomarques.progenbr.com
viewer-v2-git-main-typebot-io.vercel.app
main-menu-for-itschromeos.itschromeos.com
mdb.assessoria.qrcode.ademir.progenbr.com
mdb.assessoria.qrcode.arthur.progenbr.com
mdb.assessoria.qrcode.danilo.progenbr.com
mdb.assessoria.qrcode.marcao.progenbr.com
mdb.assessoria.qrcode.marcio.progenbr.com
mdb.assessoria.qrcode.aloisio.progenbr.com
mdb.assessoria.qrcode.girotto.progenbr.com
mdb.assessoria.qrcode.marinho.progenbr.com
mdb.assessoria.qrcode.rodrigo.progenbr.com
mdb.assessoria.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.desideri.progenbr.com
mdb.assessoria.qrcode.fernanda.progenbr.com
mdb.assessoria.qrcode.jbatista.progenbr.com
mdb.assessoria.qrcode.mauricio.progenbr.com
mdb.assessoria.fernanda.regional.progenbr.com
mdb.assessoria.qrcode.boaventura.progenbr.com
mdb.assessoria.qrcode.jtrebesqui.progenbr.com
mdb.assessoria.qrcode.carreirinha.progenbr.com
mdb.assessoria.qrcode.paulomarques.progenbr.com
mdb.assessoria.qrcode.carlosalexandre.progenbr.com
mdb.assessoria.qrcode.fernanda.regional.progenbr.com

Please sign in to comment.