@@ -63,20 +63,15 @@ export const continueBotFlow = async (
|
||||
setVariableHistory: SetVariableHistoryItem[]
|
||||
}
|
||||
> => {
|
||||
let firstBubbleWasStreamed = false
|
||||
let newSessionState = { ...state }
|
||||
const visitedEdges: VisitedEdge[] = []
|
||||
const setVariableHistory: SetVariableHistoryItem[] = []
|
||||
|
||||
if (!newSessionState.currentBlockId)
|
||||
if (!state.currentBlockId)
|
||||
return startBotFlow({
|
||||
state: resetSessionState(newSessionState),
|
||||
state: resetSessionState(state),
|
||||
version,
|
||||
textBubbleContentFormat,
|
||||
})
|
||||
|
||||
const { block, group, blockIndex } = getBlockById(
|
||||
newSessionState.currentBlockId,
|
||||
state.currentBlockId,
|
||||
state.typebotsQueue[0].typebot.groups
|
||||
)
|
||||
|
||||
@@ -86,7 +81,138 @@ export const continueBotFlow = async (
|
||||
message: 'Group / block not found',
|
||||
})
|
||||
|
||||
const nonInputProcessResult = await processNonInputBlock({
|
||||
block,
|
||||
state,
|
||||
reply,
|
||||
})
|
||||
|
||||
let newSessionState = nonInputProcessResult.newSessionState
|
||||
const { setVariableHistory, firstBubbleWasStreamed } = nonInputProcessResult
|
||||
|
||||
let formattedReply: string | undefined
|
||||
|
||||
if (isInputBlock(block)) {
|
||||
const parsedReplyResult = await parseReply(newSessionState)(reply, block)
|
||||
|
||||
if (parsedReplyResult.status === 'fail')
|
||||
return {
|
||||
...(await parseRetryMessage(newSessionState)(
|
||||
block,
|
||||
textBubbleContentFormat
|
||||
)),
|
||||
newSessionState,
|
||||
visitedEdges: [],
|
||||
setVariableHistory: [],
|
||||
}
|
||||
|
||||
formattedReply =
|
||||
'reply' in parsedReplyResult ? parsedReplyResult.reply : undefined
|
||||
newSessionState = await processAndSaveAnswer(
|
||||
state,
|
||||
block
|
||||
)(
|
||||
isDefined(formattedReply)
|
||||
? { ...reply, type: 'text', text: formattedReply }
|
||||
: undefined
|
||||
)
|
||||
}
|
||||
|
||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||
|
||||
const { edgeId: nextEdgeId, isOffDefaultPath } = getOutgoingEdgeId(
|
||||
newSessionState
|
||||
)(block, formattedReply)
|
||||
|
||||
const lastMessageNewFormat =
|
||||
reply?.type === 'text' && formattedReply !== reply?.text
|
||||
? formattedReply
|
||||
: undefined
|
||||
|
||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||
const chatReply = await executeGroup(
|
||||
{
|
||||
...group,
|
||||
blocks: group.blocks.slice(blockIndex + 1),
|
||||
} as Group,
|
||||
{
|
||||
version,
|
||||
state: newSessionState,
|
||||
visitedEdges: [],
|
||||
setVariableHistory,
|
||||
firstBubbleWasStreamed,
|
||||
startTime,
|
||||
textBubbleContentFormat,
|
||||
}
|
||||
)
|
||||
return {
|
||||
...chatReply,
|
||||
lastMessageNewFormat,
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEdgeId && state.typebotsQueue.length === 1)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
lastMessageNewFormat,
|
||||
visitedEdges: [],
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const nextGroup = await getNextGroup({
|
||||
state: newSessionState,
|
||||
edgeId: nextEdgeId,
|
||||
isOffDefaultPath,
|
||||
})
|
||||
|
||||
newSessionState = nextGroup.newSessionState
|
||||
|
||||
if (!nextGroup.group)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
lastMessageNewFormat,
|
||||
visitedEdges: nextGroup.visitedEdge ? [nextGroup.visitedEdge] : [],
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const chatReply = await executeGroup(nextGroup.group, {
|
||||
version,
|
||||
state: newSessionState,
|
||||
firstBubbleWasStreamed,
|
||||
visitedEdges: nextGroup.visitedEdge ? [nextGroup.visitedEdge] : [],
|
||||
setVariableHistory,
|
||||
startTime,
|
||||
textBubbleContentFormat,
|
||||
})
|
||||
|
||||
return {
|
||||
...chatReply,
|
||||
lastMessageNewFormat,
|
||||
}
|
||||
}
|
||||
|
||||
const processNonInputBlock = async ({
|
||||
block,
|
||||
state,
|
||||
reply,
|
||||
}: {
|
||||
block: Block
|
||||
state: SessionState
|
||||
reply: Reply
|
||||
}) => {
|
||||
if (reply?.type !== 'text')
|
||||
return {
|
||||
newSessionState: state,
|
||||
setVariableHistory: [],
|
||||
firstBubbleWasStreamed: false,
|
||||
}
|
||||
|
||||
const setVariableHistory: SetVariableHistoryItem[] = []
|
||||
let variableToUpdate: Variable | undefined
|
||||
let newSessionState = state
|
||||
let firstBubbleWasStreamed = false
|
||||
|
||||
if (block.type === LogicBlockType.SET_VARIABLE) {
|
||||
const existingVariable = state.typebotsQueue[0].typebot.variables.find(
|
||||
@@ -169,107 +295,10 @@ export const continueBotFlow = async (
|
||||
setVariableHistory.push(...newSetVariableHistory)
|
||||
}
|
||||
|
||||
let formattedReply: string | undefined
|
||||
|
||||
if (isInputBlock(block)) {
|
||||
const parsedReplyResult = await parseReply(newSessionState)(reply, block)
|
||||
|
||||
if (parsedReplyResult.status === 'fail')
|
||||
return {
|
||||
...(await parseRetryMessage(newSessionState)(
|
||||
block,
|
||||
textBubbleContentFormat
|
||||
)),
|
||||
newSessionState,
|
||||
visitedEdges: [],
|
||||
setVariableHistory: [],
|
||||
}
|
||||
|
||||
formattedReply =
|
||||
'reply' in parsedReplyResult ? parsedReplyResult.reply : undefined
|
||||
newSessionState = await processAndSaveAnswer(
|
||||
state,
|
||||
block
|
||||
)(
|
||||
isDefined(formattedReply)
|
||||
? { ...reply, type: 'text', text: formattedReply }
|
||||
: undefined
|
||||
)
|
||||
}
|
||||
|
||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||
|
||||
const { edgeId: nextEdgeId, isOffDefaultPath } = getOutgoingEdgeId(
|
||||
newSessionState
|
||||
)(block, formattedReply)
|
||||
|
||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||
const chatReply = await executeGroup(
|
||||
{
|
||||
...group,
|
||||
blocks: group.blocks.slice(blockIndex + 1),
|
||||
} as Group,
|
||||
{
|
||||
version,
|
||||
state: newSessionState,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
firstBubbleWasStreamed,
|
||||
startTime,
|
||||
textBubbleContentFormat,
|
||||
}
|
||||
)
|
||||
return {
|
||||
...chatReply,
|
||||
lastMessageNewFormat:
|
||||
formattedReply !== reply?.text ? formattedReply : undefined,
|
||||
}
|
||||
}
|
||||
|
||||
if (!nextEdgeId && state.typebotsQueue.length === 1)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
lastMessageNewFormat:
|
||||
formattedReply !== reply?.text ? formattedReply : undefined,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const nextGroup = await getNextGroup({
|
||||
state: newSessionState,
|
||||
edgeId: nextEdgeId,
|
||||
isOffDefaultPath,
|
||||
})
|
||||
|
||||
if (nextGroup.visitedEdge) visitedEdges.push(nextGroup.visitedEdge)
|
||||
|
||||
newSessionState = nextGroup.newSessionState
|
||||
|
||||
if (!nextGroup.group)
|
||||
return {
|
||||
messages: [],
|
||||
newSessionState,
|
||||
lastMessageNewFormat:
|
||||
formattedReply !== reply ? formattedReply : undefined,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
}
|
||||
|
||||
const chatReply = await executeGroup(nextGroup.group, {
|
||||
version,
|
||||
state: newSessionState,
|
||||
firstBubbleWasStreamed,
|
||||
visitedEdges,
|
||||
setVariableHistory,
|
||||
startTime,
|
||||
textBubbleContentFormat,
|
||||
})
|
||||
|
||||
return {
|
||||
...chatReply,
|
||||
lastMessageNewFormat:
|
||||
formattedReply !== reply?.text ? formattedReply : undefined,
|
||||
newSessionState,
|
||||
setVariableHistory,
|
||||
firstBubbleWasStreamed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +313,8 @@ const saveVariablesValueIfAny =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
(reply: Message): SessionState => {
|
||||
if (!block.options?.variableId) return state
|
||||
const newSessionState = saveAttachmentsVarIfAny({ block, reply, state })
|
||||
let newSessionState = saveAttachmentsVarIfAny({ block, reply, state })
|
||||
newSessionState = saveAudioClipVarIfAny({ block, reply, state })
|
||||
return saveInputVarIfAny({ block, reply, state: newSessionState })
|
||||
}
|
||||
|
||||
@@ -298,6 +328,7 @@ const saveAttachmentsVarIfAny = ({
|
||||
state: SessionState
|
||||
}): SessionState => {
|
||||
if (
|
||||
reply.type !== 'text' ||
|
||||
block.type !== InputBlockType.TEXT ||
|
||||
!block.options?.attachments?.isEnabled ||
|
||||
!block.options?.attachments?.saveVariableId ||
|
||||
@@ -330,6 +361,44 @@ const saveAttachmentsVarIfAny = ({
|
||||
return updatedState
|
||||
}
|
||||
|
||||
const saveAudioClipVarIfAny = ({
|
||||
block,
|
||||
reply,
|
||||
state,
|
||||
}: {
|
||||
block: InputBlock
|
||||
reply: Message
|
||||
state: SessionState
|
||||
}): SessionState => {
|
||||
if (
|
||||
reply.type !== 'audio' ||
|
||||
block.type !== InputBlockType.TEXT ||
|
||||
!block.options?.audioClip?.isEnabled ||
|
||||
!block.options?.audioClip?.saveVariableId
|
||||
)
|
||||
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: reply.url,
|
||||
},
|
||||
],
|
||||
currentBlockId: undefined,
|
||||
state,
|
||||
})
|
||||
|
||||
return updatedState
|
||||
}
|
||||
|
||||
const saveInputVarIfAny = ({
|
||||
block,
|
||||
reply,
|
||||
@@ -339,6 +408,8 @@ const saveInputVarIfAny = ({
|
||||
reply: Message
|
||||
state: SessionState
|
||||
}): SessionState => {
|
||||
if (reply.type !== 'text') return state
|
||||
|
||||
const foundVariable = state.typebotsQueue[0].typebot.variables.find(
|
||||
(variable) => variable.id === block.options?.variableId
|
||||
)
|
||||
@@ -411,11 +482,14 @@ const saveAnswerInDb =
|
||||
(state: SessionState, block: InputBlock) =>
|
||||
async (reply: Message): Promise<SessionState> => {
|
||||
let newSessionState = state
|
||||
const replyContent = reply.type === 'audio' ? reply.url : reply.text
|
||||
const attachedFileUrls =
|
||||
reply.type === 'text' ? reply.attachedFileUrls : undefined
|
||||
await saveAnswer({
|
||||
answer: {
|
||||
blockId: block.id,
|
||||
content: reply.text,
|
||||
attachedFileUrls: reply.attachedFileUrls,
|
||||
content: replyContent,
|
||||
attachedFileUrls,
|
||||
},
|
||||
state,
|
||||
})
|
||||
@@ -428,8 +502,8 @@ const saveAnswerInDb =
|
||||
...newSessionState.previewMetadata,
|
||||
answers: (newSessionState.previewMetadata?.answers ?? []).concat({
|
||||
blockId: block.id,
|
||||
content: reply.text,
|
||||
attachedFileUrls: reply.attachedFileUrls,
|
||||
content: replyContent,
|
||||
attachedFileUrls,
|
||||
}),
|
||||
},
|
||||
}
|
||||
@@ -443,9 +517,9 @@ const saveAnswerInDb =
|
||||
return setNewAnswerInState(newSessionState)({
|
||||
key: key ?? block.id,
|
||||
value:
|
||||
(reply.attachedFileUrls ?? []).length > 0
|
||||
? `${reply.attachedFileUrls!.join(', ')}\n\n${reply.text}`
|
||||
: reply.text,
|
||||
(attachedFileUrls ?? []).length > 0
|
||||
? `${attachedFileUrls!.join(', ')}\n\n${replyContent}`
|
||||
: replyContent,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -534,13 +608,13 @@ const parseReply =
|
||||
async (reply: Reply, block: InputBlock): Promise<ParsedReply> => {
|
||||
switch (block.type) {
|
||||
case InputBlockType.EMAIL: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
const formattedEmail = formatEmail(reply.text)
|
||||
if (!formattedEmail) return { status: 'fail' }
|
||||
return { status: 'success', reply: formattedEmail }
|
||||
}
|
||||
case InputBlockType.PHONE: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
const formattedPhone = formatPhoneNumber(
|
||||
reply.text,
|
||||
block.options?.defaultCountryCode
|
||||
@@ -549,17 +623,17 @@ const parseReply =
|
||||
return { status: 'success', reply: formattedPhone }
|
||||
}
|
||||
case InputBlockType.URL: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
const isValid = isURL(reply.text, { require_protocol: false })
|
||||
if (!isValid) return { status: 'fail' }
|
||||
return { status: 'success', reply: reply.text }
|
||||
}
|
||||
case InputBlockType.CHOICE: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
return parseButtonsReply(state)(reply.text, block)
|
||||
}
|
||||
case InputBlockType.NUMBER: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
const isValid = validateNumber(reply.text, {
|
||||
options: block.options,
|
||||
variables: state.typebotsQueue[0].typebot.variables,
|
||||
@@ -568,7 +642,7 @@ const parseReply =
|
||||
return { status: 'success', reply: parseNumber(reply.text) }
|
||||
}
|
||||
case InputBlockType.DATE: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
return parseDateReply(reply.text, block)
|
||||
}
|
||||
case InputBlockType.FILE: {
|
||||
@@ -576,34 +650,38 @@ const parseReply =
|
||||
return block.options?.isRequired ?? defaultFileInputOptions.isRequired
|
||||
? { status: 'fail' }
|
||||
: { status: 'skip' }
|
||||
const urls = reply.text.split(', ')
|
||||
const replyValue = reply.type === 'audio' ? reply.url : reply.text
|
||||
const urls = replyValue.split(', ')
|
||||
const status = urls.some((url) =>
|
||||
isURL(url, { require_tld: env.S3_ENDPOINT !== 'localhost' })
|
||||
)
|
||||
? 'success'
|
||||
: 'fail'
|
||||
if (!block.options?.isMultipleAllowed && urls.length > 1)
|
||||
return { status, reply: reply.text.split(',')[0] }
|
||||
return { status, reply: reply.text }
|
||||
return { status, reply: replyValue.split(',')[0] }
|
||||
return { status, reply: replyValue }
|
||||
}
|
||||
case InputBlockType.PAYMENT: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
if (reply.text === 'fail') return { status: 'fail' }
|
||||
return { status: 'success', reply: reply.text }
|
||||
}
|
||||
case InputBlockType.RATING: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
const isValid = validateRatingReply(reply.text, block)
|
||||
if (!isValid) return { status: 'fail' }
|
||||
return { status: 'success', reply: reply.text }
|
||||
}
|
||||
case InputBlockType.PICTURE_CHOICE: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
if (!reply || reply.type !== 'text') return { status: 'fail' }
|
||||
return parsePictureChoicesReply(state)(reply.text, block)
|
||||
}
|
||||
case InputBlockType.TEXT: {
|
||||
if (!reply) return { status: 'fail' }
|
||||
return { status: 'success', reply: reply.text }
|
||||
return {
|
||||
status: 'success',
|
||||
reply: reply.type === 'audio' ? reply.url : reply.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user