2
0

Add attachments option to text input (#1608)

Closes #854
This commit is contained in:
Baptiste Arnaud
2024-06-26 10:13:38 +02:00
committed by GitHub
parent 80da7af4f1
commit 6db0464fd7
88 changed files with 2959 additions and 735 deletions

View File

@ -7,10 +7,11 @@ import { parseDynamicTheme } from '../parseDynamicTheme'
import { saveStateToDatabase } from '../saveStateToDatabase'
import { computeCurrentProgress } from '../computeCurrentProgress'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
import { Message } from '@typebot.io/schemas'
type Props = {
origin: string | undefined
message?: string
message?: Message
sessionId: string
textBubbleContentFormat: 'richText' | 'markdown'
}

View File

@ -1,14 +1,14 @@
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
import { Message } from '@typebot.io/schemas'
import { computeCurrentProgress } from '../computeCurrentProgress'
import { filterPotentiallySensitiveLogs } from '../logs/filterPotentiallySensitiveLogs'
import { restartSession } from '../queries/restartSession'
import { saveStateToDatabase } from '../saveStateToDatabase'
import { startSession } from '../startSession'
import { isNotEmpty } from '@typebot.io/lib'
type Props = {
origin: string | undefined
message?: string
message?: Message
isOnlyRegistering: boolean
publicId: string
isStreamEnabled: boolean
@ -48,8 +48,8 @@ export const startChat = async ({
prefilledVariables,
resultId: startResultId,
textBubbleContentFormat,
message,
},
message,
})
let corsOrigin

View File

@ -1,4 +1,4 @@
import { StartFrom, StartTypebot } from '@typebot.io/schemas'
import { Message, StartFrom, StartTypebot } from '@typebot.io/schemas'
import { restartSession } from '../queries/restartSession'
import { saveStateToDatabase } from '../saveStateToDatabase'
import { startSession } from '../startSession'
@ -6,7 +6,7 @@ import { computeCurrentProgress } from '../computeCurrentProgress'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
type Props = {
message?: string
message?: Message
isOnlyRegistering: boolean
isStreamEnabled: boolean
startFrom?: StartFrom
@ -53,8 +53,8 @@ export const startChatPreview = async ({
prefilledVariables,
sessionId,
textBubbleContentFormat,
message,
},
message,
})
const session = isOnlyRegistering

View File

@ -23,6 +23,7 @@ import {
} from '@typebot.io/schemas/features/blocks/logic/setVariable/constants'
import { createCodeRunner } from '@typebot.io/variables/codeRunners'
import { stringifyError } from '@typebot.io/lib/stringifyError'
import { AnswerV2 } from '@typebot.io/prisma'
export const executeSetVariable = async (
state: SessionState,
@ -246,7 +247,7 @@ const toISOWithTz = (date: Date, timeZone: string) => {
}
type ParsedTranscriptProps = {
answers: Pick<Answer, 'blockId' | 'content'>[]
answers: Pick<Answer, 'blockId' | 'content' | 'attachedFileUrls'>[]
setVariableHistory: Pick<
SetVariableHistoryItem,
'blockId' | 'variableId' | 'value'
@ -273,6 +274,10 @@ const parsePreviewTranscriptProps = async (
}
}
type UnifiedAnswersFromDB = (ParsedTranscriptProps['answers'][number] & {
createdAt: Date
})[]
const parseResultTranscriptProps = async (
state: SessionState
): Promise<ParsedTranscriptProps | undefined> => {
@ -299,6 +304,7 @@ const parseResultTranscriptProps = async (
blockId: true,
content: true,
createdAt: true,
attachedFileUrls: true,
},
},
setVariableHistory: {
@ -313,8 +319,8 @@ const parseResultTranscriptProps = async (
})
if (!result) return
return {
answers: result.answersV2
.concat(result.answers)
answers: (result.answersV2 as UnifiedAnswersFromDB)
.concat(result.answers as UnifiedAnswersFromDB)
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()),
setVariableHistory: (
result.setVariableHistory as SetVariableHistoryItem[]

View File

@ -4,11 +4,12 @@ import {
ContinueChatResponse,
Group,
InputBlock,
Message,
SessionState,
SetVariableHistoryItem,
Variable,
} from '@typebot.io/schemas'
import { byId } from '@typebot.io/lib'
import { byId, isDefined } from '@typebot.io/lib'
import { isInputBlock } from '@typebot.io/schemas/helpers'
import { executeGroup, parseInput } from './executeGroup'
import { getNextGroup } from './getNextGroup'
@ -42,10 +43,9 @@ import { ForgedBlock } from '@typebot.io/forge-repository/types'
import { forgedBlocks } from '@typebot.io/forge-repository/definitions'
import { resumeChatCompletion } from './blocks/integrations/legacy/openai/resumeChatCompletion'
import { env } from '@typebot.io/env'
import { downloadMedia } from './whatsapp/downloadMedia'
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
import { isURL } from '@typebot.io/lib/validators/isURL'
import { isForgedBlockType } from '@typebot.io/schemas/features/blocks/forged/helpers'
import { resetSessionState } from './resetSessionState'
type Params = {
version: 1 | 2
@ -69,7 +69,11 @@ export const continueBotFlow = async (
const setVariableHistory: SetVariableHistoryItem[] = []
if (!newSessionState.currentBlockId)
return startBotFlow({ state, version, textBubbleContentFormat })
return startBotFlow({
state: resetSessionState(newSessionState),
version,
textBubbleContentFormat,
})
const { block, group, blockIndex } = getBlockById(
newSessionState.currentBlockId,
@ -161,7 +165,7 @@ export const continueBotFlow = async (
newVariables: [
{
...variableToUpdate,
value: safeJsonParse(reply as string),
value: reply?.text ? safeJsonParse(reply?.text) : undefined,
},
],
})
@ -187,7 +191,14 @@ export const continueBotFlow = async (
formattedReply =
'reply' in parsedReplyResult ? parsedReplyResult.reply : undefined
newSessionState = await processAndSaveAnswer(state, block)(formattedReply)
newSessionState = await processAndSaveAnswer(
state,
block
)(
isDefined(formattedReply)
? { ...reply, type: 'text', text: formattedReply }
: undefined
)
}
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
@ -267,37 +278,92 @@ export const continueBotFlow = async (
const processAndSaveAnswer =
(state: SessionState, block: InputBlock) =>
async (reply: string | undefined): Promise<SessionState> => {
async (reply: Message | undefined): Promise<SessionState> => {
if (!reply) return state
let newState = await saveAnswerInDb(state, block)(reply)
return newState
return saveAnswerInDb(state, block)(reply)
}
const saveVariableValueIfAny =
const saveVariablesValueIfAny =
(state: SessionState, block: InputBlock) =>
(reply: string): SessionState => {
(reply: Message): SessionState => {
if (!block.options?.variableId) return state
const foundVariable = state.typebotsQueue[0].typebot.variables.find(
(variable) => variable.id === block.options?.variableId
)
if (!foundVariable) return state
const { updatedState } = updateVariablesInSession({
newVariables: [
{
...foundVariable,
value: Array.isArray(foundVariable.value)
? foundVariable.value.concat(reply)
: reply,
},
],
currentBlockId: undefined,
state,
})
return updatedState
const newSessionState = saveAttachmentsVarIfAny({ block, reply, state })
return saveInputVarIfAny({ block, reply, state: newSessionState })
}
const saveAttachmentsVarIfAny = ({
block,
reply,
state,
}: {
block: InputBlock
reply: Message
state: SessionState
}): SessionState => {
if (
block.type !== InputBlockType.TEXT ||
!block.options?.attachments?.isEnabled ||
!block.options?.attachments?.saveVariableId ||
!reply.attachedFileUrls ||
reply.attachedFileUrls.length === 0
)
return state
const variable = state.typebotsQueue[0].typebot.variables.find(
(variable) => variable.id === block.options?.attachments?.saveVariableId
)
if (!variable) return state
const { updatedState } = updateVariablesInSession({
newVariables: [
{
id: variable.id,
name: variable.name,
value: Array.isArray(variable.value)
? variable.value.concat(reply.attachedFileUrls)
: reply.attachedFileUrls.length === 1
? reply.attachedFileUrls[0]
: reply.attachedFileUrls,
},
],
currentBlockId: undefined,
state,
})
return updatedState
}
const saveInputVarIfAny = ({
block,
reply,
state,
}: {
block: InputBlock
reply: Message
state: SessionState
}): SessionState => {
const foundVariable = state.typebotsQueue[0].typebot.variables.find(
(variable) => variable.id === block.options?.variableId
)
if (!foundVariable) return state
const { updatedState } = updateVariablesInSession({
newVariables: [
{
...foundVariable,
value:
Array.isArray(foundVariable.value) && reply.text
? foundVariable.value.concat(reply.text)
: reply.text,
},
],
currentBlockId: undefined,
state,
})
return updatedState
}
const parseRetryMessage =
(state: SessionState) =>
async (
@ -346,26 +412,27 @@ const parseDefaultRetryMessage = (block: InputBlock): string => {
const saveAnswerInDb =
(state: SessionState, block: InputBlock) =>
async (reply: string): Promise<SessionState> => {
async (reply: Message): Promise<SessionState> => {
let newSessionState = state
await saveAnswer({
answer: {
blockId: block.id,
content: reply,
content: reply.text,
attachedFileUrls: reply.attachedFileUrls,
},
reply,
state,
})
newSessionState = {
...saveVariableValueIfAny(newSessionState, block)(reply),
...saveVariablesValueIfAny(newSessionState, block)(reply),
previewMetadata: state.typebotsQueue[0].resultId
? newSessionState.previewMetadata
: {
...newSessionState.previewMetadata,
answers: (newSessionState.previewMetadata?.answers ?? []).concat({
blockId: block.id,
content: reply,
content: reply.text,
attachedFileUrls: reply.attachedFileUrls,
}),
},
}
@ -378,7 +445,10 @@ const saveAnswerInDb =
return setNewAnswerInState(newSessionState)({
key: key ?? block.id,
value: reply,
value:
(reply.attachedFileUrls ?? []).length > 0
? `${reply.attachedFileUrls!.join(', ')}\n\n${reply.text}`
: reply.text,
})
}
@ -465,41 +535,17 @@ const getOutgoingEdgeId =
const parseReply =
(state: SessionState) =>
async (reply: Reply, block: InputBlock): Promise<ParsedReply> => {
if (reply && typeof reply !== 'string') {
if (block.type !== InputBlockType.FILE) return { status: 'fail' }
if (block.options?.visibility !== 'Public') {
return {
status: 'success',
reply:
env.NEXTAUTH_URL +
`/api/typebots/${state.typebotsQueue[0].typebot.id}/whatsapp/media/${reply.mediaId}`,
}
}
const { file, mimeType } = await downloadMedia({
mediaId: reply.mediaId,
systemUserAccessToken: reply.accessToken,
})
const url = await uploadFileToBucket({
file,
key: `public/workspaces/${reply.workspaceId}/typebots/${state.typebotsQueue[0].typebot.id}/results/${state.typebotsQueue[0].resultId}/${reply.mediaId}`,
mimeType,
})
return {
status: 'success',
reply: url,
}
}
switch (block.type) {
case InputBlockType.EMAIL: {
if (!reply) return { status: 'fail' }
const formattedEmail = formatEmail(reply)
const formattedEmail = formatEmail(reply.text)
if (!formattedEmail) return { status: 'fail' }
return { status: 'success', reply: formattedEmail }
}
case InputBlockType.PHONE: {
if (!reply) return { status: 'fail' }
const formattedPhone = formatPhoneNumber(
reply,
reply.text,
block.options?.defaultCountryCode
)
if (!formattedPhone) return { status: 'fail' }
@ -507,58 +553,60 @@ const parseReply =
}
case InputBlockType.URL: {
if (!reply) return { status: 'fail' }
const isValid = isURL(reply, { require_protocol: false })
const isValid = isURL(reply.text, { require_protocol: false })
if (!isValid) return { status: 'fail' }
return { status: 'success', reply: reply }
return { status: 'success', reply: reply.text }
}
case InputBlockType.CHOICE: {
if (!reply) return { status: 'fail' }
return parseButtonsReply(state)(reply, block)
return parseButtonsReply(state)(reply.text, block)
}
case InputBlockType.NUMBER: {
if (!reply) return { status: 'fail' }
const isValid = validateNumber(reply, {
const isValid = validateNumber(reply.text, {
options: block.options,
variables: state.typebotsQueue[0].typebot.variables,
})
if (!isValid) return { status: 'fail' }
return { status: 'success', reply: parseNumber(reply) }
return { status: 'success', reply: parseNumber(reply.text) }
}
case InputBlockType.DATE: {
if (!reply) return { status: 'fail' }
return parseDateReply(reply, block)
return parseDateReply(reply.text, block)
}
case InputBlockType.FILE: {
if (!reply)
return block.options?.isRequired ?? defaultFileInputOptions.isRequired
? { status: 'fail' }
: { status: 'skip' }
const urls = reply.split(', ')
const urls = reply.text.split(', ')
const status = urls.some((url) =>
isURL(url, { require_tld: env.S3_ENDPOINT !== 'localhost' })
)
? 'success'
: 'fail'
return { status, reply: reply }
if (!block.options?.isMultipleAllowed && urls.length > 1)
return { status, reply: reply.text.split(',')[0] }
return { status, reply: reply.text }
}
case InputBlockType.PAYMENT: {
if (!reply) return { status: 'fail' }
if (reply === 'fail') return { status: 'fail' }
return { status: 'success', reply: reply }
if (reply.text === 'fail') return { status: 'fail' }
return { status: 'success', reply: reply.text }
}
case InputBlockType.RATING: {
if (!reply) return { status: 'fail' }
const isValid = validateRatingReply(reply, block)
const isValid = validateRatingReply(reply.text, block)
if (!isValid) return { status: 'fail' }
return { status: 'success', reply: reply }
return { status: 'success', reply: reply.text }
}
case InputBlockType.PICTURE_CHOICE: {
if (!reply) return { status: 'fail' }
return parsePictureChoicesReply(state)(reply, block)
return parsePictureChoicesReply(state)(reply.text, block)
}
case InputBlockType.TEXT: {
if (!reply) return { status: 'fail' }
return { status: 'success', reply: reply }
return { status: 'success', reply: reply.text }
}
}
}

View File

@ -4,7 +4,6 @@ import { SessionState } from '@typebot.io/schemas'
type Props = {
answer: Omit<Prisma.AnswerV2CreateManyInput, 'resultId'>
reply: string
state: SessionState
}
export const saveAnswer = async ({ answer, state }: Props) => {

View File

@ -0,0 +1,20 @@
import { SessionState } from '@typebot.io/schemas/features/chat/sessionState'
export const resetSessionState = (state: SessionState): SessionState => ({
...state,
currentSetVariableHistoryIndex: undefined,
currentVisitedEdgeIndex: undefined,
previewMetadata: undefined,
progressMetadata: undefined,
typebotsQueue: state.typebotsQueue.map((queueItem) => ({
...queueItem,
answers: [],
typebot: {
...queueItem.typebot,
variables: queueItem.typebot.variables.map((variable) => ({
...variable,
value: undefined,
})),
},
})),
})

View File

@ -61,14 +61,12 @@ type StartParams =
type Props = {
version: 1 | 2
message: Reply
startParams: StartParams
initialSessionState?: Pick<SessionState, 'whatsApp' | 'expiryTimeout'>
}
export const startSession = async ({
version,
message,
startParams,
initialSessionState,
}: Props): Promise<
@ -188,7 +186,7 @@ export const startSession = async ({
})
// If params has message and first block is an input block, we can directly continue the bot flow
if (message) {
if (startParams.message) {
const firstEdgeId = getFirstEdgeId({
typebot: chatReply.newSessionState.typebotsQueue[0].typebot,
startEventId:
@ -213,7 +211,7 @@ export const startSession = async ({
resultId,
typebot: newSessionState.typebotsQueue[0].typebot,
})
chatReply = await continueBotFlow(message, {
chatReply = await continueBotFlow(startParams.message, {
version,
state: {
...newSessionState,

View File

@ -1,6 +1,7 @@
import {
ContinueChatResponse,
CustomEmbedBubble,
Message,
SessionState,
SetVariableHistoryItem,
} from '@typebot.io/schemas'
@ -21,14 +22,7 @@ export type ExecuteIntegrationResponse = {
newSetVariableHistory?: SetVariableHistoryItem[]
} & Pick<ContinueChatResponse, 'clientSideActions' | 'logs'>
type WhatsAppMediaMessage = {
type: 'whatsapp media'
mediaId: string
workspaceId?: string
accessToken: string
}
export type Reply = string | WhatsAppMediaMessage | undefined
export type Reply = Message | undefined
export type ParsedReply =
| { status: 'success'; reply: string }

View File

@ -1,4 +1,4 @@
import { SessionState } from '@typebot.io/schemas'
import { Block, SessionState } from '@typebot.io/schemas'
import {
WhatsAppCredentials,
WhatsAppIncomingMessage,
@ -15,6 +15,13 @@ import { isDefined } from '@typebot.io/lib/utils'
import { Reply } from '../types'
import { setIsReplyingInChatSession } from '../queries/setIsReplyingInChatSession'
import { removeIsReplyingInChatSession } from '../queries/removeIsReplyingInChatSession'
import redis from '@typebot.io/lib/redis'
import { downloadMedia } from './downloadMedia'
import { InputBlockType } from '@typebot.io/schemas/features/blocks/inputs/constants'
import { uploadFileToBucket } from '@typebot.io/lib/s3/uploadFileToBucket'
import { getBlockById } from '@typebot.io/schemas/helpers'
const incomingMessageDebounce = 3000
type Props = {
receivedMessage: WhatsAppIncomingMessage
@ -61,33 +68,59 @@ export const resumeWhatsAppFlow = async ({
}
}
const reply = await getIncomingMessageContent({
message: receivedMessage,
workspaceId,
accessToken: credentials?.systemUserAccessToken,
})
const session = await getSession(sessionId)
const { incomingMessages, isReplyingWasSet } =
await aggregateParallelMediaMessagesIfRedisEnabled({
receivedMessage,
existingSessionId: session?.id,
newSessionId: sessionId,
})
if (incomingMessages.length === 0) {
if (isReplyingWasSet) await removeIsReplyingInChatSession(sessionId)
return {
message: 'Message received',
}
}
const isSessionExpired =
session &&
isDefined(session.state.expiryTimeout) &&
session?.updatedAt.getTime() + session.state.expiryTimeout < Date.now()
if (session?.isReplying) {
if (!isSessionExpired) {
console.log('Is currently replying, skipping...')
return {
message: 'Message received',
if (!isReplyingWasSet) {
if (session?.isReplying) {
if (!isSessionExpired) {
console.log('Is currently replying, skipping...')
return {
message: 'Message received',
}
}
} else {
await setIsReplyingInChatSession({
existingSessionId: session?.id,
newSessionId: sessionId,
})
}
} else {
await setIsReplyingInChatSession({
existingSessionId: session?.id,
newSessionId: sessionId,
})
}
const currentTypebot = session?.state.typebotsQueue[0].typebot
const { block } =
(currentTypebot && session?.state.currentBlockId
? getBlockById(session.state.currentBlockId, currentTypebot.groups)
: undefined) ?? {}
const reply = await getIncomingMessageContent({
messages: incomingMessages,
workspaceId,
accessToken: credentials?.systemUserAccessToken,
typebotId: currentTypebot?.id,
resultId: session?.state.typebotsQueue[0].resultId,
block,
})
const resumeResponse =
session && !isSessionExpired
? await continueBotFlow(reply, {
@ -155,35 +188,107 @@ export const resumeWhatsAppFlow = async ({
}
const getIncomingMessageContent = async ({
message,
messages,
workspaceId,
accessToken,
typebotId,
resultId,
block,
}: {
message: WhatsAppIncomingMessage
messages: WhatsAppIncomingMessage[]
workspaceId?: string
accessToken: string
typebotId?: string
resultId?: string
block?: Block
}): Promise<Reply> => {
switch (message.type) {
case 'text':
return message.text.body
case 'button':
return message.button.text
case 'interactive': {
return message.interactive.button_reply.id
let text: string = ''
const attachedFileUrls: string[] = []
for (const message of messages) {
switch (message.type) {
case 'text': {
if (text !== '') text += `\n\n${message.text.body}`
else text = message.text.body
break
}
case 'button': {
if (text !== '') text += `\n\n${message.button.text}`
else text = message.button.text
break
}
case 'interactive': {
if (text !== '') text += `\n\n${message.interactive.button_reply.id}`
else text = message.interactive.button_reply.id
break
}
case 'document':
case 'audio':
case 'video':
case 'image': {
let mediaId: string | undefined
if (message.type === 'video') mediaId = message.video.id
if (message.type === 'image') mediaId = message.image.id
if (message.type === 'audio') mediaId = message.audio.id
if (message.type === 'document') mediaId = message.document.id
if (!mediaId) return
const fileVisibility =
block?.type === InputBlockType.FILE
? block.options?.visibility
: block?.type === InputBlockType.TEXT
? block.options?.attachments?.visibility
: undefined
let fileUrl
if (fileVisibility !== 'Public') {
fileUrl =
env.NEXTAUTH_URL +
`/api/typebots/${typebotId}/whatsapp/media/${
workspaceId ? `` : 'preview/'
}${mediaId}`
} else {
const { file, mimeType } = await downloadMedia({
mediaId,
systemUserAccessToken: accessToken,
})
const url = await uploadFileToBucket({
file,
key:
resultId && workspaceId && typebotId
? `public/workspaces/${workspaceId}/typebots/${typebotId}/results/${resultId}/${mediaId}`
: `tmp/whatsapp/media/${mediaId}`,
mimeType,
})
fileUrl = url
}
if (block?.type === InputBlockType.FILE) {
if (text !== '') text += `, ${fileUrl}`
else text = fileUrl
} else if (block?.type === InputBlockType.TEXT) {
let caption: string | undefined
if (message.type === 'document' && message.document.caption) {
if (!/^[\w,\s-]+\.[A-Za-z]{3}$/.test(message.document.caption))
caption = message.document.caption
} else if (message.type === 'image' && message.image.caption)
caption = message.image.caption
else if (message.type === 'video' && message.video.caption)
caption = message.video.caption
if (caption) text = text === '' ? caption : `${text}\n\n${caption}`
attachedFileUrls.push(fileUrl)
}
break
}
case 'location': {
const location = `${message.location.latitude}, ${message.location.longitude}`
if (text !== '') text += `\n\n${location}`
else text = location
break
}
}
case 'document':
case 'audio':
case 'video':
case 'image':
let mediaId: string | undefined
if (message.type === 'video') mediaId = message.video.id
if (message.type === 'image') mediaId = message.image.id
if (message.type === 'audio') mediaId = message.audio.id
if (message.type === 'document') mediaId = message.document.id
if (!mediaId) return
return { type: 'whatsapp media', mediaId, workspaceId, accessToken }
case 'location':
return `${message.location.latitude}, ${message.location.longitude}`
}
return {
type: 'text',
text,
attachedFileUrls,
}
}
@ -227,3 +332,57 @@ const getCredentials = async ({
phoneNumberId: data.phoneNumberId,
}
}
const aggregateParallelMediaMessagesIfRedisEnabled = async ({
receivedMessage,
existingSessionId,
newSessionId,
}: {
receivedMessage: WhatsAppIncomingMessage
existingSessionId?: string
newSessionId: string
}): Promise<{
isReplyingWasSet: boolean
incomingMessages: WhatsAppIncomingMessage[]
}> => {
if (redis && ['document', 'video', 'image'].includes(receivedMessage.type)) {
const redisKey = `wasession:${newSessionId}`
try {
const len = await redis.rpush(redisKey, JSON.stringify(receivedMessage))
if (len === 1) {
await setIsReplyingInChatSession({
existingSessionId,
newSessionId,
})
}
await new Promise((resolve) =>
setTimeout(resolve, incomingMessageDebounce)
)
const newMessagesResponse = await redis.lrange(redisKey, 0, -1)
if (!newMessagesResponse || newMessagesResponse.length > len) {
// Current message was aggregated with other messages another webhook handler. Skipping...
return { isReplyingWasSet: true, incomingMessages: [] }
}
redis.del(redisKey).then()
return {
isReplyingWasSet: true,
incomingMessages: newMessagesResponse.map((msgStr) =>
JSON.parse(msgStr)
),
}
} catch (error) {
console.error('Failed to process webhook event:', error, receivedMessage)
}
}
return {
isReplyingWasSet: false,
incomingMessages: [receivedMessage],
}
}

View File

@ -58,11 +58,16 @@ export const sendChatReplyToWhatsApp = async ({
const result = await executeClientSideAction({ to, credentials })(action)
if (!result) continue
const { input, newSessionState, messages, clientSideActions } =
await continueBotFlow(result.replyToSend, {
version: 2,
state,
textBubbleContentFormat: 'richText',
})
await continueBotFlow(
result.replyToSend
? { type: 'text', text: result.replyToSend }
: undefined,
{
version: 2,
state,
textBubbleContentFormat: 'richText',
}
)
return sendChatReplyToWhatsApp({
to,
@ -128,11 +133,16 @@ export const sendChatReplyToWhatsApp = async ({
)
if (!result) continue
const { input, newSessionState, messages, clientSideActions } =
await continueBotFlow(result.replyToSend, {
version: 2,
state,
textBubbleContentFormat: 'richText',
})
await continueBotFlow(
result.replyToSend
? { type: 'text', text: result.replyToSend }
: undefined,
{
version: 2,
state,
textBubbleContentFormat: 'richText',
}
)
return sendChatReplyToWhatsApp({
to,

View File

@ -68,7 +68,7 @@ export const startWhatsAppSession = async ({
(publicTypebot.settings.whatsApp?.startCondition?.comparisons.length ??
0) > 0 &&
messageMatchStartCondition(
incomingMessage ?? '',
incomingMessage ?? { type: 'text', text: '' },
publicTypebot.settings.whatsApp?.startCondition
)
)
@ -90,13 +90,13 @@ export const startWhatsAppSession = async ({
return startSession({
version: 2,
message: incomingMessage,
startParams: {
type: 'live',
publicId: publicTypebot.typebot.publicId as string,
isOnlyRegistering: false,
isStreamEnabled: false,
textBubbleContentFormat: 'richText',
message: incomingMessage,
},
initialSessionState: {
whatsApp: {