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

@ -7,7 +7,7 @@ import {
redirectOptionsSchema,
} from '../blocks'
import { logSchema } from '../result'
import { listVariableValue, typebotSchema } from '../typebot'
import { listVariableValue } from '../typebot'
import {
textBubbleContentSchema,
imageBubbleContentSchema,
@ -15,12 +15,13 @@ import {
audioBubbleContentSchema,
embedBubbleContentSchema,
} from '../blocks/bubbles'
import { BubbleBlockType } from '../blocks/bubbles/enums'
import { inputBlockSchemas } from '../blocks/schemas'
import { chatCompletionMessageSchema } from '../blocks/integrations/openai'
import { nativeMessageSchema } from '../blocks/integrations/openai'
import { sessionStateSchema } from './sessionState'
import { dynamicThemeSchema } from './shared'
import { preprocessTypebot } from '../typebot/helpers/preprocessTypebot'
import { typebotV5Schema, typebotV6Schema } from '../typebot/typebot'
import { inputBlockSchemas } from '../blocks/inputs/schema'
import { BubbleBlockType } from '../blocks/bubbles/constants'
const chatSessionSchema = z.object({
id: z.string(),
@ -85,58 +86,75 @@ const scriptToExecuteSchema = z.object({
),
})
const startTypebotPick = {
version: true,
id: true,
groups: true,
events: true,
edges: true,
variables: true,
settings: true,
theme: true,
} as const
export const startTypebotSchema = z.preprocess(
preprocessTypebot,
typebotSchema._def.schema.pick({
version: true,
id: true,
groups: true,
edges: true,
variables: true,
settings: true,
theme: true,
})
z.discriminatedUnion('version', [
typebotV5Schema._def.schema.pick(startTypebotPick),
typebotV6Schema.pick(startTypebotPick),
])
)
const startParamsSchema = z.object({
typebot: startTypebotSchema
.or(z.string())
.describe(
'Either a Typebot ID or a Typebot object. If you provide a Typebot object, it will be executed in preview mode. ([How can I find my typebot ID?](https://docs.typebot.io/api#how-to-find-my-typebotid)).'
),
isPreview: z
.boolean()
.optional()
.describe(
"If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab. You need to be authenticated with a bearer token for this to work."
),
resultId: z
.string()
.optional()
.describe("Provide it if you'd like to overwrite an existing result."),
startGroupId: z
.string()
.optional()
.describe('Start chat from a specific group.'),
prefilledVariables: z
.record(z.unknown())
.optional()
.describe(
'[More info about prefilled variables.](https://docs.typebot.io/editor/variables#prefilled-variables)'
),
isStreamEnabled: z
.boolean()
.optional()
.describe(
'Set this to `true` if you intend to stream OpenAI completions on a client.'
),
isOnlyRegistering: z
.boolean()
.optional()
.describe(
'If set to `true`, it will only register the session and not start the chat. This is used for other chat platform integration as it can require a session to be registered before sending the first message.'
),
})
export const startElementIdSchema = z.union([
z.object({
startGroupId: z.string().describe('Start chat from a specific group.'),
startEventId: z.never().optional(),
}),
z.object({
startEventId: z.string().describe('Start chat from a specific event.'),
startGroupId: z.never().optional(),
}),
z.object({}),
])
export type StartElementId = z.infer<typeof startElementIdSchema>
const startParamsSchema = z
.object({
typebot: startTypebotSchema
.or(z.string())
.describe(
'Either a Typebot ID or a Typebot object. If you provide a Typebot object, it will be executed in preview mode. ([How can I find my typebot ID?](https://docs.typebot.io/api#how-to-find-my-typebotid)).'
),
isPreview: z
.boolean()
.optional()
.describe(
"If set to `true`, it will start a Preview session with the unpublished bot and it won't be saved in the Results tab. You need to be authenticated with a bearer token for this to work."
),
resultId: z
.string()
.optional()
.describe("Provide it if you'd like to overwrite an existing result."),
prefilledVariables: z
.record(z.unknown())
.optional()
.describe(
'[More info about prefilled variables.](https://docs.typebot.io/editor/variables#prefilled-variables)'
),
isStreamEnabled: z
.boolean()
.optional()
.describe(
'Set this to `true` if you intend to stream OpenAI completions on a client.'
),
isOnlyRegistering: z
.boolean()
.optional()
.describe(
'If set to `true`, it will only register the session and not start the chat. This is used for other chat platform integration as it can require a session to be registered before sending the first message.'
),
})
.and(startElementIdSchema)
const replyLogSchema = logSchema
.pick({
@ -169,7 +187,6 @@ const runtimeOptionsSchema = paymentInputRuntimeOptionsSchema.optional()
const startPropsToInjectSchema = z.object({
googleAnalyticsId: z.string().optional(),
pixelId: z.string().optional().describe('Deprecated'),
pixelIds: z.array(z.string()).optional(),
gtmId: z.string().optional(),
customHeadCode: z.string().optional(),
@ -216,7 +233,7 @@ const clientSideActionSchema = z
z.object({
streamOpenAiChatCompletion: z.object({
messages: z.array(
chatCompletionMessageSchema.pick({ content: true, role: true })
nativeMessageSchema.pick({ content: true, role: true })
),
}),
})
@ -238,10 +255,29 @@ const clientSideActionSchema = z
)
)
const typebotInChatReplyPick = {
version: true,
id: true,
groups: true,
edges: true,
variables: true,
settings: true,
theme: true,
} as const
export const typebotInChatReply = z.preprocess(
preprocessTypebot,
z.discriminatedUnion('version', [
typebotV5Schema._def.schema.pick(typebotInChatReplyPick),
typebotV6Schema.pick(typebotInChatReplyPick),
])
)
export const chatReplySchema = z.object({
messages: z.array(chatMessageSchema),
input: z
.discriminatedUnion('type', [...inputBlockSchemas])
.union([
z.discriminatedUnion('type', [...inputBlockSchemas.v5]),
z.discriminatedUnion('type', [...inputBlockSchemas.v6]),
])
.and(
z.object({
prefilledValue: z.string().optional(),
@ -251,8 +287,18 @@ export const chatReplySchema = z.object({
.optional(),
clientSideActions: z.array(clientSideActionSchema).optional(),
sessionId: z.string().optional(),
typebot: typebotSchema._def.schema
.pick({ id: true, theme: true, settings: true })
typebot: z
.object({
id: z.string(),
theme: z.union([
typebotV5Schema._def.schema.shape.theme,
typebotV6Schema.shape.theme,
]),
settings: z.union([
typebotV5Schema._def.schema.shape.settings,
typebotV6Schema.shape.settings,
]),
})
.optional(),
resultId: z.string().optional(),
dynamicTheme: dynamicThemeSchema.optional(),

View File

@ -29,6 +29,7 @@ const resultInSessionStateSchema = resultSchema
)
const sessionStateSchemaV1 = z.object({
version: z.undefined(),
typebot: typebotInSessionStateSchema,
dynamicTheme: dynamicThemeSchema.optional(),
linkedTypebots: z.object({
@ -40,7 +41,6 @@ const sessionStateSchemaV1 = z.object({
currentBlock: z
.object({
blockId: z.string(),
groupId: z.string(),
})
.optional(),
isStreamEnabled: z.boolean().optional(),
@ -61,7 +61,6 @@ const sessionStateSchemaV2 = z.object({
currentBlock: z
.object({
blockId: z.string(),
groupId: z.string(),
})
.optional(),
isStreamEnabled: z.boolean().optional(),
@ -79,19 +78,60 @@ const sessionStateSchemaV2 = z.object({
.optional()
.describe('Expiry timeout in milliseconds'),
typingEmulation: settingsSchema.shape.typingEmulation.optional(),
currentVisitedEdgeIndex: z.number().optional(),
})
export type SessionState = z.infer<typeof sessionStateSchemaV2>
const sessionStateSchemaV3 = sessionStateSchemaV2
.omit({ currentBlock: true })
.extend({
version: z.literal('3'),
currentBlockId: z.string().optional(),
})
export const sessionStateSchema = sessionStateSchemaV1
.or(sessionStateSchemaV2)
export type SessionState = z.infer<typeof sessionStateSchemaV3>
export const sessionStateSchema = z
.discriminatedUnion('version', [
sessionStateSchemaV1,
sessionStateSchemaV2,
sessionStateSchemaV3,
])
.transform((state): SessionState => {
if ('version' in state) return state
return {
version: '2',
typebotsQueue: [
{
typebot: state.typebot,
if (state.version === '3') return state
let migratedState: any = state
if (!state.version) migratedState = migrateFromV1ToV2(state)
return migrateFromV2ToV3(migratedState)
})
const migrateFromV1ToV2 = (
state: z.infer<typeof sessionStateSchemaV1>
): z.infer<typeof sessionStateSchemaV2> => ({
version: '2',
typebotsQueue: [
{
typebot: state.typebot,
resultId: state.result.id,
answers: state.result.answers.map((answer) => ({
key:
(answer.variableId
? state.typebot.variables.find(
(variable) => variable.id === answer.variableId
)?.name
: state.typebot.groups.find((group) =>
group.blocks.find((block) => block.id === answer.blockId)
)?.title) ?? '',
value: answer.content,
})),
isMergingWithParent: true,
edgeIdToTriggerWhenDone:
state.linkedTypebots.queue.length > 0
? state.linkedTypebots.queue[0].edgeId
: undefined,
},
...state.linkedTypebots.typebots.map(
(typebot, index) =>
({
typebot,
resultId: state.result.id,
answers: state.result.answers.map((answer) => ({
key:
@ -104,37 +144,20 @@ export const sessionStateSchema = sessionStateSchemaV1
)?.title) ?? '',
value: answer.content,
})),
isMergingWithParent: true,
edgeIdToTriggerWhenDone:
state.linkedTypebots.queue.length > 0
? state.linkedTypebots.queue[0].edgeId
: undefined,
},
...state.linkedTypebots.typebots.map(
(typebot, index) =>
({
typebot,
resultId: state.result.id,
answers: state.result.answers.map((answer) => ({
key:
(answer.variableId
? state.typebot.variables.find(
(variable) => variable.id === answer.variableId
)?.name
: state.typebot.groups.find((group) =>
group.blocks.find(
(block) => block.id === answer.blockId
)
)?.title) ?? '',
value: answer.content,
})),
edgeIdToTriggerWhenDone: state.linkedTypebots.queue.at(index + 1)
?.edgeId,
} satisfies SessionState['typebotsQueue'][number])
),
],
dynamicTheme: state.dynamicTheme,
currentBlock: state.currentBlock,
isStreamEnabled: state.isStreamEnabled,
}
})
edgeIdToTriggerWhenDone: state.linkedTypebots.queue.at(index + 1)
?.edgeId,
} satisfies SessionState['typebotsQueue'][number])
),
],
dynamicTheme: state.dynamicTheme,
currentBlock: state.currentBlock,
isStreamEnabled: state.isStreamEnabled,
})
const migrateFromV2ToV3 = (
state: z.infer<typeof sessionStateSchemaV2>
): z.infer<typeof sessionStateSchemaV3> => ({
...state,
version: '3',
currentBlockId: state.currentBlock?.blockId,
})

View File

@ -1,16 +1,21 @@
import { z } from 'zod'
import { publicTypebotSchema } from '../publicTypebot'
import { publicTypebotSchemaV5, publicTypebotSchemaV6 } from '../publicTypebot'
import { preprocessTypebot } from '../typebot/helpers/preprocessTypebot'
const typebotInSessionStatePick = {
version: true,
id: true,
groups: true,
events: true,
edges: true,
variables: true,
} as const
export const typebotInSessionStateSchema = z.preprocess(
preprocessTypebot,
publicTypebotSchema._def.schema.pick({
version: true,
id: true,
groups: true,
edges: true,
variables: true,
})
z.discriminatedUnion('version', [
publicTypebotSchemaV5._def.schema.pick(typebotInSessionStatePick),
publicTypebotSchemaV6.pick(typebotInSessionStatePick),
])
)
export type TypebotInSession = z.infer<typeof typebotInSessionStateSchema>