2
0

🐛 New sendMessage version for the new parser

Make sure old client still communicate with old parser
This commit is contained in:
Baptiste Arnaud
2023-10-06 10:14:26 +02:00
parent 6f3e9e9251
commit 3838ac9c3f
35 changed files with 710 additions and 416 deletions

View File

@ -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']> => {