2
0

♻️ Introduce typebot v6 with events (#1013)

Closes #885
This commit is contained in:
Baptiste Arnaud
2023-11-08 15:34:16 +01:00
committed by GitHub
parent 68e4fc71fb
commit 35300eaf34
634 changed files with 58971 additions and 31449 deletions

View File

@ -5,10 +5,11 @@ import {
Variable,
VariableWithValue,
Theme,
IntegrationBlockType,
GoogleAnalyticsBlock,
PixelBlock,
SessionState,
TypebotInSession,
Block,
} from '@typebot.io/schemas'
import {
ChatReply,
@ -30,6 +31,10 @@ import { getNextGroup } from './getNextGroup'
import { upsertResult } from './queries/upsertResult'
import { continueBotFlow } from './continueBotFlow'
import { parseVariables } from './variables/parseVariables'
import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants'
import { IntegrationBlockType } from '@typebot.io/schemas/features/blocks/integrations/constants'
import { defaultTheme } from '@typebot.io/schemas/features/typebot/theme/constants'
import { VisitedEdge } from '@typebot.io/prisma'
type Props = {
version: 1 | 2
@ -45,7 +50,9 @@ export const startSession = async ({
startParams,
userId,
initialSessionState,
}: Props): Promise<ChatReply & { newSessionState: SessionState }> => {
}: Props): Promise<
ChatReply & { newSessionState: SessionState; visitedEdges: VisitedEdge[] }
> => {
if (!startParams)
throw new TRPCError({
code: 'BAD_REQUEST',
@ -64,10 +71,10 @@ export const startSession = async ({
typebotId: typebot.id,
prefilledVariables,
isRememberUserEnabled:
typebot.settings.general.rememberUser?.isEnabled ??
(isDefined(typebot.settings.general.isNewResultOnRefreshEnabled)
? !typebot.settings.general.isNewResultOnRefreshEnabled
: false),
typebot.settings.general?.rememberUser?.isEnabled ??
(isDefined(typebot.settings.general?.isNewResultOnRefreshEnabled)
? !typebot.settings.general?.isNewResultOnRefreshEnabled
: defaultSettings.general.rememberUser.isEnabled),
})
const startVariables =
@ -75,22 +82,21 @@ export const startSession = async ({
? injectVariablesFromExistingResult(prefilledVariables, result.variables)
: prefilledVariables
const typebotInSession = convertStartTypebotToTypebotInSession(
typebot,
startVariables
)
const initialState: SessionState = {
version: '2',
version: '3',
typebotsQueue: [
{
resultId: result?.id,
typebot: {
version: typebot.version,
id: typebot.id,
groups: typebot.groups,
edges: typebot.edges,
variables: startVariables,
},
typebot: typebotInSession,
answers: result
? result.answers.map((answer) => {
const block = typebot.groups
.flatMap((group) => group.blocks)
.flatMap<Block>((group) => group.blocks)
.find((block) => block.id === answer.blockId)
if (!block || !isInputBlock(block))
return {
@ -98,9 +104,9 @@ export const startSession = async ({
value: answer.content,
}
const key =
(block.options.variableId
(block.options?.variableId
? startVariables.find(
(variable) => variable.id === block.options.variableId
(variable) => variable.id === block.options?.variableId
)?.name
: typebot.groups.find((group) =>
group.blocks.find(
@ -135,13 +141,18 @@ export const startSession = async ({
},
dynamicTheme: parseDynamicTheme(initialState),
messages: [],
visitedEdges: [],
}
}
let chatReply = await startBotFlow({
version,
state: initialState,
startGroupId: startParams.startGroupId,
...('startGroupId' in startParams
? { startGroupId: startParams.startGroupId }
: 'startEventId' in startParams
? { startEventId: startParams.startEventId }
: {}),
})
// If params has message and first block is an input block, we can directly continue the bot flow
@ -165,7 +176,7 @@ export const startSession = async ({
version,
state: {
...newSessionState,
currentBlock: { groupId: firstBlock.groupId, blockId: firstBlock.id },
currentBlockId: firstBlock.id,
},
})
}
@ -177,6 +188,7 @@ export const startSession = async ({
clientSideActions: startFlowClientActions,
newSessionState,
logs,
visitedEdges,
} = chatReply
const clientSideActions = startFlowClientActions ?? []
@ -188,12 +200,12 @@ export const startSession = async ({
if (isDefined(startClientSideAction)) {
if (!result) {
if ('startPropsToInject' in startClientSideAction) {
const { customHeadCode, googleAnalyticsId, pixelId, pixelIds, gtmId } =
const { customHeadCode, googleAnalyticsId, pixelIds, gtmId } =
startClientSideAction.startPropsToInject
let toolsList = ''
if (customHeadCode) toolsList += 'Custom head code, '
if (googleAnalyticsId) toolsList += 'Google Analytics, '
if (pixelId || pixelIds) toolsList += 'Pixel, '
if (pixelIds) toolsList += 'Pixel, '
if (gtmId) toolsList += 'Google Tag Manager, '
toolsList = toolsList.slice(0, -2)
startLogs.push({
@ -229,6 +241,7 @@ export const startSession = async ({
},
dynamicTheme: parseDynamicTheme(newSessionState),
logs: startLogs.length > 0 ? startLogs : undefined,
visitedEdges,
}
return {
@ -249,6 +262,7 @@ export const startSession = async ({
clientSideActions.length > 0 ? clientSideActions : undefined,
dynamicTheme: parseDynamicTheme(newSessionState),
logs: startLogs.length > 0 ? startLogs : undefined,
visitedEdges,
}
}
@ -341,12 +355,13 @@ const getResult = async ({
const parseDynamicThemeInState = (theme: Theme) => {
const hostAvatarUrl =
theme.chat.hostAvatar?.isEnabled ?? true
? theme.chat.hostAvatar?.url
theme.chat?.hostAvatar?.isEnabled ?? defaultTheme.chat.hostAvatar.isEnabled
? theme.chat?.hostAvatar?.url
: undefined
const guestAvatarUrl =
theme.chat.guestAvatar?.isEnabled ?? false
? theme.chat.guestAvatar?.url
theme.chat?.guestAvatar?.isEnabled ??
defaultTheme.chat.guestAvatar.isEnabled
? theme.chat?.guestAvatar?.url
: undefined
if (!hostAvatarUrl?.startsWith('{{') && !guestAvatarUrl?.startsWith('{{'))
return
@ -361,28 +376,30 @@ const parseDynamicThemeInState = (theme: Theme) => {
const parseStartClientSideAction = (
typebot: StartTypebot
): NonNullable<ChatReply['clientSideActions']>[number] | undefined => {
const blocks = typebot.groups.flatMap((group) => group.blocks)
const blocks = typebot.groups.flatMap<Block>((group) => group.blocks)
const pixelBlocks = (
blocks.filter(
(block) =>
block.type === IntegrationBlockType.PIXEL &&
isNotEmpty(block.options.pixelId) &&
block.options.isInitSkip !== true
isNotEmpty(block.options?.pixelId) &&
block.options?.isInitSkip !== true
) as PixelBlock[]
).map((pixelBlock) => pixelBlock.options.pixelId as string)
).map((pixelBlock) => pixelBlock.options?.pixelId as string)
const startPropsToInject = {
customHeadCode: isNotEmpty(typebot.settings.metadata.customHeadCode)
? sanitizeAndParseHeadCode(typebot.settings.metadata.customHeadCode)
customHeadCode: isNotEmpty(typebot.settings.metadata?.customHeadCode)
? sanitizeAndParseHeadCode(
typebot.settings.metadata?.customHeadCode as string
)
: undefined,
gtmId: typebot.settings.metadata.googleTagManagerId,
gtmId: typebot.settings.metadata?.googleTagManagerId,
googleAnalyticsId: (
blocks.find(
(block) =>
block.type === IntegrationBlockType.GOOGLE_ANALYTICS &&
block.options.trackingId
block.options?.trackingId
) as GoogleAnalyticsBlock | undefined
)?.options.trackingId,
)?.options?.trackingId,
pixelIds: pixelBlocks.length > 0 ? pixelBlocks : undefined,
}
@ -403,8 +420,10 @@ const sanitizeAndParseTheme = (
theme: Theme,
{ variables }: { variables: Variable[] }
): Theme => ({
general: deepParseVariables(variables)(theme.general),
chat: deepParseVariables(variables)(theme.chat),
general: theme.general
? deepParseVariables(variables)(theme.general)
: undefined,
chat: theme.chat ? deepParseVariables(variables)(theme.chat) : undefined,
customCss: theme.customCss
? removeLiteBadgeCss(parseVariables(variables)(theme.customCss))
: undefined,
@ -421,3 +440,25 @@ const removeLiteBadgeCss = (code: string) => {
const liteBadgeCssRegex = /.*#lite-badge.*{[\s\S][^{]*}/gm
return code.replace(liteBadgeCssRegex, '')
}
const convertStartTypebotToTypebotInSession = (
typebot: StartTypebot,
startVariables: Variable[]
): TypebotInSession =>
typebot.version === '6'
? {
version: typebot.version,
id: typebot.id,
groups: typebot.groups,
edges: typebot.edges,
variables: startVariables,
events: typebot.events,
}
: {
version: typebot.version,
id: typebot.id,
groups: typebot.groups,
edges: typebot.edges,
variables: startVariables,
events: typebot.events,
}