🐛 New sendMessage version for the new parser
Make sure old client still communicate with old parser
This commit is contained in:
@ -31,128 +31,102 @@ import { updateVariablesInSession } from './variables/updateVariablesInSession'
|
||||
import { startBotFlow } from './startBotFlow'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
|
||||
export const continueBotFlow =
|
||||
(state: SessionState) =>
|
||||
async (
|
||||
reply?: string
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
let newSessionState = { ...state }
|
||||
type Params = {
|
||||
version: 1 | 2
|
||||
state: SessionState
|
||||
}
|
||||
export const continueBotFlow = async (
|
||||
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(
|
||||
(group) => group.id === state.currentBlock?.groupId
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
(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 =
|
||||
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)
|
||||
)
|
||||
if (existingVariable && reply) {
|
||||
const newVariable = {
|
||||
...existingVariable,
|
||||
value: safeJsonParse(reply),
|
||||
}
|
||||
newSessionState = updateVariablesInSession(state)([newVariable])
|
||||
if (existingVariable && reply) {
|
||||
const newVariable = {
|
||||
...existingVariable,
|
||||
value: safeJsonParse(reply),
|
||||
}
|
||||
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
||||
const result = resumeWebhookExecution({
|
||||
state,
|
||||
block,
|
||||
response: JSON.parse(reply),
|
||||
})
|
||||
if (result.newSessionState) newSessionState = result.newSessionState
|
||||
} else if (
|
||||
block.type === IntegrationBlockType.OPEN_AI &&
|
||||
block.options.task === 'Create chat completion'
|
||||
) {
|
||||
if (reply) {
|
||||
const result = await resumeChatCompletion(state, {
|
||||
options: block.options,
|
||||
outgoingEdgeId: block.outgoingEdgeId,
|
||||
})(reply)
|
||||
newSessionState = result.newSessionState
|
||||
newSessionState = updateVariablesInSession(state)([newVariable])
|
||||
}
|
||||
} else if (reply && block.type === IntegrationBlockType.WEBHOOK) {
|
||||
const result = resumeWebhookExecution({
|
||||
state,
|
||||
block,
|
||||
response: JSON.parse(reply),
|
||||
})
|
||||
if (result.newSessionState) newSessionState = result.newSessionState
|
||||
} else if (
|
||||
block.type === IntegrationBlockType.OPEN_AI &&
|
||||
block.options.task === 'Create chat completion'
|
||||
) {
|
||||
if (reply) {
|
||||
const result = await resumeChatCompletion(state, {
|
||||
options: block.options,
|
||||
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 itemId = nextEdgeId
|
||||
? newSessionState.typebotsQueue[0].typebot.edges.find(byId(nextEdgeId))
|
||||
?.from.itemId
|
||||
: undefined
|
||||
newSessionState = await processAndSaveAnswer(
|
||||
state,
|
||||
block,
|
||||
itemId
|
||||
)(formattedReply)
|
||||
}
|
||||
|
||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||
const chatReply = await executeGroup(newSessionState)({
|
||||
const groupHasMoreBlocks = blockIndex < group.blocks.length - 1
|
||||
|
||||
const nextEdgeId = getOutgoingEdgeId(newSessionState)(block, formattedReply)
|
||||
|
||||
if (groupHasMoreBlocks && !nextEdgeId) {
|
||||
const chatReply = await executeGroup(
|
||||
{
|
||||
...group,
|
||||
blocks: group.blocks.slice(blockIndex + 1),
|
||||
})
|
||||
return {
|
||||
...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)
|
||||
|
||||
},
|
||||
{ version, state: newSessionState }
|
||||
)
|
||||
return {
|
||||
...chatReply,
|
||||
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 =
|
||||
(state: SessionState, block: InputBlock, itemId?: string) =>
|
||||
async (reply: string | undefined): Promise<SessionState> => {
|
||||
|
@ -1,17 +1,13 @@
|
||||
import {
|
||||
BubbleBlock,
|
||||
BubbleBlockType,
|
||||
ChatReply,
|
||||
Group,
|
||||
InputBlock,
|
||||
InputBlockType,
|
||||
RuntimeOptions,
|
||||
SessionState,
|
||||
Variable,
|
||||
} from '@typebot.io/schemas'
|
||||
import {
|
||||
isBubbleBlock,
|
||||
isEmpty,
|
||||
isInputBlock,
|
||||
isIntegrationBlock,
|
||||
isLogicBlock,
|
||||
@ -26,49 +22,85 @@ import { injectVariableValuesInPictureChoiceBlock } from './blocks/inputs/pictur
|
||||
import { getPrefilledInputValue } from './getPrefilledValue'
|
||||
import { parseDateInput } from './blocks/inputs/date/parseDateInput'
|
||||
import { deepParseVariables } from './variables/deepParseVariables'
|
||||
import { parseVideoUrl } from '@typebot.io/lib/parseVideoUrl'
|
||||
import { TDescendant, createPlateEditor } from '@udecode/plate-common'
|
||||
import {
|
||||
createDeserializeMdPlugin,
|
||||
deserializeMd,
|
||||
} from '@udecode/plate-serializer-md'
|
||||
import { getVariablesToParseInfoInText } from './variables/parseVariables'
|
||||
import { parseBubbleBlock } from './parseBubbleBlock'
|
||||
|
||||
export const executeGroup =
|
||||
(
|
||||
state: SessionState,
|
||||
currentReply?: ChatReply,
|
||||
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
|
||||
type ContextProps = {
|
||||
version: 1 | 2
|
||||
state: SessionState
|
||||
currentReply?: ChatReply
|
||||
currentLastBubbleId?: string
|
||||
}
|
||||
|
||||
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) {
|
||||
nextEdgeId = block.outgoingEdgeId
|
||||
let newSessionState = state
|
||||
|
||||
if (isBubbleBlock(block)) {
|
||||
messages.push(
|
||||
parseBubbleBlock(newSessionState.typebotsQueue[0].typebot.variables)(
|
||||
block
|
||||
)
|
||||
)
|
||||
lastBubbleBlockId = block.id
|
||||
continue
|
||||
for (const block of group.blocks) {
|
||||
nextEdgeId = block.outgoingEdgeId
|
||||
|
||||
if (isBubbleBlock(block)) {
|
||||
messages.push(
|
||||
parseBubbleBlock(block, {
|
||||
version,
|
||||
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 {
|
||||
messages,
|
||||
input: await parseInput(newSessionState)(block),
|
||||
newSessionState: {
|
||||
...newSessionState,
|
||||
currentBlock: {
|
||||
@ -79,78 +111,38 @@ export const executeGroup =
|
||||
clientSideActions,
|
||||
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)
|
||||
return { messages, newSessionState, clientSideActions, logs }
|
||||
|
||||
const nextGroup = await getNextGroup(newSessionState)(
|
||||
nextEdgeId ?? undefined
|
||||
)
|
||||
|
||||
newSessionState = nextGroup.newSessionState
|
||||
|
||||
if (!nextGroup.group) {
|
||||
return { messages, newSessionState, clientSideActions, logs }
|
||||
if (executionResponse.outgoingEdgeId) {
|
||||
nextEdgeId = executionResponse.outgoingEdgeId
|
||||
break
|
||||
}
|
||||
|
||||
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 =
|
||||
(state: SessionState) =>
|
||||
(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 =
|
||||
(state: SessionState) =>
|
||||
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 { getNextGroup } from './getNextGroup'
|
||||
|
||||
export const startBotFlow = async (
|
||||
state: SessionState,
|
||||
type Props = {
|
||||
version: 1 | 2
|
||||
state: SessionState
|
||||
startGroupId?: string
|
||||
): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
}
|
||||
|
||||
export const startBotFlow = async ({
|
||||
version,
|
||||
state,
|
||||
startGroupId,
|
||||
}: Props): Promise<ChatReply & { newSessionState: SessionState }> => {
|
||||
let newSessionState = state
|
||||
if (startGroupId) {
|
||||
const group = state.typebotsQueue[0].typebot.groups.find(
|
||||
@ -17,7 +24,7 @@ export const startBotFlow = async (
|
||||
code: 'BAD_REQUEST',
|
||||
message: "startGroupId doesn't exist",
|
||||
})
|
||||
return executeGroup(newSessionState)(group)
|
||||
return executeGroup(group, { version, state: newSessionState })
|
||||
}
|
||||
const firstEdgeId =
|
||||
newSessionState.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
|
||||
@ -25,5 +32,5 @@ export const startBotFlow = async (
|
||||
const nextGroup = await getNextGroup(newSessionState)(firstEdgeId)
|
||||
newSessionState = nextGroup.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'
|
||||
|
||||
type Props = {
|
||||
version: 1 | 2
|
||||
message: string | undefined
|
||||
startParams: StartParams
|
||||
userId: string | undefined
|
||||
@ -38,6 +39,7 @@ type Props = {
|
||||
}
|
||||
|
||||
export const startSession = async ({
|
||||
version,
|
||||
message,
|
||||
startParams,
|
||||
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 (message) {
|
||||
@ -154,10 +160,13 @@ export const startSession = async ({
|
||||
resultId,
|
||||
typebot: newSessionState.typebotsQueue[0].typebot,
|
||||
})
|
||||
chatReply = await continueBotFlow({
|
||||
...newSessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
})(message)
|
||||
chatReply = await continueBotFlow(message, {
|
||||
version,
|
||||
state: {
|
||||
...newSessionState,
|
||||
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,10 @@ type VariableToParseInformation = {
|
||||
|
||||
export const getVariablesToParseInfoInText = (
|
||||
text: string,
|
||||
variables: Variable[]
|
||||
{
|
||||
variables,
|
||||
takeLatestIfList,
|
||||
}: { variables: Variable[]; takeLatestIfList?: boolean }
|
||||
): VariableToParseInformation[] => {
|
||||
const pattern = /\{\{([^{}]+)\}\}|(\$)\{\{([^{}]+)\}\}/g
|
||||
const variablesToParseInfo: VariableToParseInformation[] = []
|
||||
@ -78,7 +81,12 @@ export const getVariablesToParseInfoInText = (
|
||||
startIndex: match.index,
|
||||
endIndex: match.index + match[0].length,
|
||||
textToReplace: match[0],
|
||||
value: safeStringify(variable?.value) ?? '',
|
||||
value:
|
||||
safeStringify(
|
||||
takeLatestIfList && Array.isArray(variable?.value)
|
||||
? variable?.value[variable?.value.length - 1]
|
||||
: variable?.value
|
||||
) ?? '',
|
||||
})
|
||||
}
|
||||
return variablesToParseInfo
|
||||
|
@ -64,9 +64,10 @@ export const resumeWhatsAppFlow = async ({
|
||||
|
||||
const resumeResponse =
|
||||
session && !isSessionExpired
|
||||
? await continueBotFlow({ ...session.state, whatsApp: { contact } })(
|
||||
messageContent
|
||||
)
|
||||
? await continueBotFlow(messageContent, {
|
||||
version: 2,
|
||||
state: { ...session.state, whatsApp: { contact } },
|
||||
})
|
||||
: workspaceId
|
||||
? await startWhatsAppSession({
|
||||
incomingMessage: messageContent,
|
||||
|
@ -51,7 +51,7 @@ export const sendChatReplyToWhatsApp = async ({
|
||||
const result = await executeClientSideAction({ to, credentials })(action)
|
||||
if (!result) continue
|
||||
const { input, newSessionState, messages, clientSideActions } =
|
||||
await continueBotFlow(state)(result.replyToSend)
|
||||
await continueBotFlow(result.replyToSend, { version: 2, state })
|
||||
|
||||
return sendChatReplyToWhatsApp({
|
||||
to,
|
||||
@ -95,7 +95,7 @@ export const sendChatReplyToWhatsApp = async ({
|
||||
)
|
||||
if (!result) continue
|
||||
const { input, newSessionState, messages, clientSideActions } =
|
||||
await continueBotFlow(state)(result.replyToSend)
|
||||
await continueBotFlow(result.replyToSend, { version: 2, state })
|
||||
|
||||
return sendChatReplyToWhatsApp({
|
||||
to,
|
||||
|
@ -78,6 +78,7 @@ export const startWhatsAppSession = async ({
|
||||
defaultSessionExpiryTimeout
|
||||
|
||||
return startSession({
|
||||
version: 2,
|
||||
message: incomingMessage,
|
||||
startParams: {
|
||||
typebot: publicTypebot.typebot.publicId as string,
|
||||
|
@ -16,7 +16,7 @@ npm install @typebot.io/js
|
||||
|
||||
```
|
||||
<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: 'my-typebot',
|
||||
@ -34,7 +34,7 @@ There, you can change the container dimensions. Here is a code example:
|
||||
|
||||
```html
|
||||
<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: 'my-typebot',
|
||||
@ -54,7 +54,7 @@ Here is an example:
|
||||
|
||||
```html
|
||||
<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: 'my-typebot',
|
||||
@ -96,7 +96,7 @@ Here is an example:
|
||||
|
||||
```html
|
||||
<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: 'my-typebot',
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.1.34",
|
||||
"version": "0.2.0",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
@ -32,7 +32,7 @@ export async function getInitialChatReplyQuery({
|
||||
if (paymentInProgressState) removePaymentInProgressFromStorage()
|
||||
const { data, error } = await sendRequest<InitialChatReply>({
|
||||
method: 'POST',
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sendMessage`,
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v2/sendMessage`,
|
||||
body: {
|
||||
startParams: paymentInProgressState
|
||||
? undefined
|
||||
|
@ -8,6 +8,6 @@ export const sendMessageQuery = ({
|
||||
}: SendMessageInput & { apiHost?: string }) =>
|
||||
sendRequest<ChatReply>({
|
||||
method: 'POST',
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v1/sendMessage`,
|
||||
url: `${isNotEmpty(apiHost) ? apiHost : guessApiHost()}/api/v2/sendMessage`,
|
||||
body,
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.1.34",
|
||||
"version": "0.2.0",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.1.34",
|
||||
"version": "0.2.0",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -40,7 +40,7 @@ class Typebot_Public
|
||||
|
||||
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 (
|
||||
get_option('excluded_pages') !== null &&
|
||||
get_option('excluded_pages') !== ''
|
||||
@ -91,7 +91,7 @@ class Typebot_Public
|
||||
|
||||
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%';
|
||||
$height = '500px';
|
||||
$api_host = 'https://viewer.typebot.io';
|
||||
|
Reference in New Issue
Block a user