2
0
Files
bot/packages/bot-engine/parseBubbleBlock.ts
Baptiste Arnaud ad791789ba 🐛 Fix link parsing when using variables
Closes #764
2023-10-12 17:20:11 +02:00

200 lines
5.5 KiB
TypeScript

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,
parseVariables,
} 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) : {}),
height:
typeof parsedContent.height === 'string'
? parseFloat(parsedContent.height)
: parsedContent.height,
},
}
}
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('}}') &&
element.type !== 'a'
? 'variable'
: element.type
parsedElements.push({
...element,
url: element.url
? parseVariables(variables)(element.url as string)
: undefined,
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)
}