2
0
Files
bot/packages/bot-engine/parseBubbleBlock.ts
2024-04-18 10:17:25 +02:00

217 lines
6.1 KiB
TypeScript

import { parseVideoUrl } from '@typebot.io/schemas/features/blocks/bubbles/video/helpers'
import {
BubbleBlock,
Variable,
ContinueChatResponse,
Typebot,
} from '@typebot.io/schemas'
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
import {
getVariablesToParseInfoInText,
parseVariables,
} from '@typebot.io/variables/parseVariables'
import { TDescendant } from '@udecode/plate-common'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
import { defaultVideoBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/video/constants'
import { convertMarkdownToRichText } from '@typebot.io/lib/markdown/convertMarkdownToRichText'
type Params = {
version: 1 | 2
typebotVersion: Typebot['version']
variables: Variable[]
}
export type BubbleBlockWithDefinedContent = BubbleBlock & {
content: NonNullable<BubbleBlock['content']>
}
export const parseBubbleBlock = (
block: BubbleBlockWithDefinedContent,
{ version, variables, typebotVersion }: Params
): ContinueChatResponse['messages'][0] => {
switch (block.type) {
case BubbleBlockType.TEXT: {
if (version === 1)
return {
...block,
content: {
...block.content,
richText: (block.content?.richText ?? []).map(
deepParseVariables(variables)
),
},
}
return {
...block,
content: {
...block.content,
richText: parseVariablesInRichText(block.content?.richText ?? [], {
variables,
takeLatestIfList: typebotVersion !== '6',
}),
},
}
}
case BubbleBlockType.EMBED: {
const message = deepParseVariables(variables, {
removeEmptyStrings: true,
})(block)
return {
...message,
content: {
...message.content,
height:
typeof message.content?.height === 'string'
? parseFloat(message.content.height)
: message.content?.height,
},
}
}
case BubbleBlockType.VIDEO: {
const parsedContent = block.content
? deepParseVariables(variables, { removeEmptyStrings: true })(
block.content
)
: undefined
return {
...block,
content: {
...parsedContent,
...(parsedContent?.url ? parseVideoUrl(parsedContent.url) : {}),
height:
typeof parsedContent?.height === 'string'
? parseFloat(parsedContent.height)
: defaultVideoBubbleContent.height,
},
}
}
default:
return deepParseVariables(variables, { removeEmptyStrings: true })(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 &&
!element.bold &&
!element.italic &&
!element.underline
) {
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
),
}
})