2
0

🐛 (sendMessage) Correctly preprocess and parse fetched bot

This commit is contained in:
Baptiste Arnaud
2023-08-24 09:11:10 +02:00
parent ee3b94c35d
commit 06ecdf040e
15 changed files with 63 additions and 45 deletions

View File

@@ -1,7 +1,9 @@
import { useToast } from '@/hooks/useToast' import { useToast } from '@/hooks/useToast'
import { Button, ButtonProps, chakra } from '@chakra-ui/react' import { Button, ButtonProps, chakra } from '@chakra-ui/react'
import { Typebot, typebotCreateSchema } from '@typebot.io/schemas' import { Typebot, typebotCreateSchema } from '@typebot.io/schemas'
import { preprocessTypebot } from '@typebot.io/schemas/features/typebot/helpers/preprocessTypebot'
import React, { ChangeEvent } from 'react' import React, { ChangeEvent } from 'react'
import { z } from 'zod'
type Props = { type Props = {
onNewTypebot: (typebot: Typebot) => void onNewTypebot: (typebot: Typebot) => void
@@ -18,7 +20,9 @@ export const ImportTypebotFromFileButton = ({
const file = e.target.files[0] const file = e.target.files[0]
const fileContent = await readFile(file) const fileContent = await readFile(file)
try { try {
const typebot = typebotCreateSchema.parse(JSON.parse(fileContent)) const typebot = z
.preprocess(preprocessTypebot, typebotCreateSchema)
.parse(JSON.parse(fileContent))
onNewTypebot(typebot as Typebot) onNewTypebot(typebot as Typebot)
} catch (err) { } catch (err) {
console.error(err) console.error(err)

View File

@@ -8,9 +8,10 @@ import {
SessionState, SessionState,
Variable, Variable,
ReplyLog, ReplyLog,
Typebot,
VariableWithValue, VariableWithValue,
Edge, Edge,
typebotInSessionStateSchema,
TypebotInSession,
} from '@typebot.io/schemas' } from '@typebot.io/schemas'
import { ExecuteLogicResponse } from '@/features/chat/types' import { ExecuteLogicResponse } from '@/features/chat/types'
import { createId } from '@paralleldrive/cuid2' import { createId } from '@paralleldrive/cuid2'
@@ -73,7 +74,7 @@ export const executeTypebotLink = async (
const addLinkedTypebotToState = async ( const addLinkedTypebotToState = async (
state: SessionState, state: SessionState,
block: TypebotLinkBlock, block: TypebotLinkBlock,
linkedTypebot: Pick<Typebot, 'id' | 'edges' | 'groups' | 'variables'> linkedTypebot: TypebotInSession
): Promise<SessionState> => { ): Promise<SessionState> => {
const currentTypebotInQueue = state.typebotsQueue[0] const currentTypebotInQueue = state.typebotsQueue[0]
const isPreview = isNotDefined(currentTypebotInQueue.resultId) const isPreview = isNotDefined(currentTypebotInQueue.resultId)
@@ -191,17 +192,19 @@ const fetchTypebot = async (state: SessionState, typebotId: string) => {
const typebot = await prisma.typebot.findUnique({ const typebot = await prisma.typebot.findUnique({
where: { id: typebotId }, where: { id: typebotId },
select: { select: {
version: true,
id: true, id: true,
edges: true, edges: true,
groups: true, groups: true,
variables: true, variables: true,
}, },
}) })
return typebot as Pick<Typebot, 'id' | 'edges' | 'groups' | 'variables'> return typebotInSessionStateSchema.parse(typebot)
} }
const typebot = await prisma.publicTypebot.findUnique({ const typebot = await prisma.publicTypebot.findUnique({
where: { typebotId }, where: { typebotId },
select: { select: {
version: true,
id: true, id: true,
edges: true, edges: true,
groups: true, groups: true,
@@ -209,8 +212,8 @@ const fetchTypebot = async (state: SessionState, typebotId: string) => {
}, },
}) })
if (!typebot) return null if (!typebot) return null
return { return typebotInSessionStateSchema.parse({
...typebot, ...typebot,
id: typebotId, id: typebotId,
} as Pick<Typebot, 'id' | 'edges' | 'groups' | 'variables'> })
} }

View File

@@ -11,8 +11,8 @@ import {
SessionState, SessionState,
StartParams, StartParams,
StartTypebot, StartTypebot,
startTypebotSchema,
Theme, Theme,
Typebot,
Variable, Variable,
VariableWithValue, VariableWithValue,
} from '@typebot.io/schemas' } from '@typebot.io/schemas'
@@ -152,6 +152,7 @@ const startSession = async (
{ {
resultId: result?.id, resultId: result?.id,
typebot: { typebot: {
version: typebot.version,
id: typebot.id, id: typebot.id,
groups: typebot.groups, groups: typebot.groups,
edges: typebot.edges, edges: typebot.edges,
@@ -268,17 +269,14 @@ const getTypebot = async (
const parsedTypebot = const parsedTypebot =
typebotQuery && 'typebot' in typebotQuery typebotQuery && 'typebot' in typebotQuery
? ({ ? {
id: typebotQuery.typebotId, id: typebotQuery.typebotId,
...omit(typebotQuery.typebot, 'workspace'), ...omit(typebotQuery.typebot, 'workspace'),
...omit(typebotQuery, 'typebot', 'typebotId'), ...omit(typebotQuery, 'typebot', 'typebotId'),
} as StartTypebot & Pick<Typebot, 'isArchived' | 'isClosed'>) }
: (typebotQuery as StartTypebot & Pick<Typebot, 'isArchived'>) : typebotQuery
if ( if (!parsedTypebot || parsedTypebot.isArchived)
!parsedTypebot ||
('isArchived' in parsedTypebot && parsedTypebot.isArchived)
)
throw new TRPCError({ throw new TRPCError({
code: 'NOT_FOUND', code: 'NOT_FOUND',
message: 'Typebot not found', message: 'Typebot not found',
@@ -299,7 +297,7 @@ const getTypebot = async (
message: 'Typebot is closed', message: 'Typebot is closed',
}) })
return parsedTypebot return startTypebotSchema.parse(parsedTypebot)
} }
const getResult = async ({ const getResult = async ({

View File

@@ -142,6 +142,7 @@ test('API chat execution should work on published bot', async ({ request }) => {
data: { message: '8', sessionId: chatSessionId }, data: { message: '8', sessionId: chatSessionId },
}) })
).json() ).json()
console.log(messages, input)
expect(messages[0].content.richText).toStrictEqual([ expect(messages[0].content.richText).toStrictEqual([
{ {
children: [{ text: "I'm gonna shoot multiple inputs now..." }], children: [{ text: "I'm gonna shoot multiple inputs now..." }],

View File

@@ -137,7 +137,7 @@ export const continueBotFlow =
formattedReply !== reply ? formattedReply : undefined, formattedReply !== reply ? formattedReply : undefined,
} }
const nextGroup = getNextGroup(newSessionState)(nextEdgeId) const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
newSessionState = nextGroup.newSessionState newSessionState = nextGroup.newSessionState

View File

@@ -124,7 +124,9 @@ export const executeGroup =
if (!nextEdgeId && state.typebotsQueue.length === 1) if (!nextEdgeId && state.typebotsQueue.length === 1)
return { messages, newSessionState, clientSideActions, logs } return { messages, newSessionState, clientSideActions, logs }
const nextGroup = getNextGroup(newSessionState)(nextEdgeId ?? undefined) const nextGroup = await getNextGroup(newSessionState)(
nextEdgeId ?? undefined
)
newSessionState = nextGroup.newSessionState newSessionState = nextGroup.newSessionState

View File

@@ -1,5 +1,6 @@
import { byId } from '@typebot.io/lib' import { byId } from '@typebot.io/lib'
import { Group, SessionState } from '@typebot.io/schemas' import { Group, SessionState } from '@typebot.io/schemas'
import { upsertResult } from '../queries/upsertResult'
export type NextGroup = { export type NextGroup = {
group?: Group group?: Group
@@ -8,12 +9,20 @@ export type NextGroup = {
export const getNextGroup = export const getNextGroup =
(state: SessionState) => (state: SessionState) =>
(edgeId?: string): NextGroup => { async (edgeId?: string): Promise<NextGroup> => {
const nextEdge = state.typebotsQueue[0].typebot.edges.find(byId(edgeId)) const nextEdge = state.typebotsQueue[0].typebot.edges.find(byId(edgeId))
if (!nextEdge) { if (!nextEdge) {
if (state.typebotsQueue.length > 1) { if (state.typebotsQueue.length > 1) {
const nextEdgeId = state.typebotsQueue[0].edgeIdToTriggerWhenDone const nextEdgeId = state.typebotsQueue[0].edgeIdToTriggerWhenDone
const isMergingWithParent = state.typebotsQueue[0].isMergingWithParent const isMergingWithParent = state.typebotsQueue[0].isMergingWithParent
const currentResultId = state.typebotsQueue[0].resultId
if (!isMergingWithParent && currentResultId)
await upsertResult({
resultId: currentResultId,
typebot: state.typebotsQueue[0].typebot,
isCompleted: true,
hasStarted: state.typebotsQueue[0].answers.length > 0,
})
const newSessionState = { const newSessionState = {
...state, ...state,
typebotsQueue: [ typebotsQueue: [
@@ -48,7 +57,7 @@ export const getNextGroup =
...state.typebotsQueue.slice(2), ...state.typebotsQueue.slice(2),
], ],
} satisfies SessionState } satisfies SessionState
const nextGroup = getNextGroup(newSessionState)(nextEdgeId) const nextGroup = await getNextGroup(newSessionState)(nextEdgeId)
if (!nextGroup) if (!nextGroup)
return { return {
newSessionState, newSessionState,

View File

@@ -21,7 +21,7 @@ export const startBotFlow = async (
const firstEdgeId = const firstEdgeId =
state.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId state.typebotsQueue[0].typebot.groups[0].blocks[0].outgoingEdgeId
if (!firstEdgeId) return { messages: [], newSessionState: state } if (!firstEdgeId) return { messages: [], newSessionState: state }
const nextGroup = getNextGroup(state)(firstEdgeId) const nextGroup = await getNextGroup(state)(firstEdgeId)
if (!nextGroup.group) return { messages: [], newSessionState: state } if (!nextGroup.group) return { messages: [], newSessionState: state }
return executeGroup(state)(nextGroup.group) return executeGroup(state)(nextGroup.group)
} }

View File

@@ -8,6 +8,7 @@ export const findPublicTypebot = ({ publicId }: Props) =>
prisma.publicTypebot.findFirst({ prisma.publicTypebot.findFirst({
where: { typebot: { publicId } }, where: { typebot: { publicId } },
select: { select: {
version: true,
groups: true, groups: true,
edges: true, edges: true,
settings: true, settings: true,

View File

@@ -9,6 +9,7 @@ export const findTypebot = ({ id, userId }: Props) =>
prisma.typebot.findFirst({ prisma.typebot.findFirst({
where: { id, workspace: { members: { some: { userId } } } }, where: { id, workspace: { members: { some: { userId } } } },
select: { select: {
version: true,
id: true, id: true,
groups: true, groups: true,
edges: true, edges: true,

View File

@@ -10,6 +10,7 @@ import {
} from '@typebot.io/schemas' } from '@typebot.io/schemas'
export const leadGenerationTypebot: StartTypebot = { export const leadGenerationTypebot: StartTypebot = {
version: null,
id: 'clckrl4q5000t3b6sabwokaar', id: 'clckrl4q5000t3b6sabwokaar',
groups: [ groups: [
{ {

View File

@@ -20,6 +20,7 @@ import { inputBlockSchemas } from '../blocks/schemas'
import { chatCompletionMessageSchema } from '../blocks/integrations/openai' import { chatCompletionMessageSchema } from '../blocks/integrations/openai'
import { sessionStateSchema } from './sessionState' import { sessionStateSchema } from './sessionState'
import { dynamicThemeSchema } from './shared' import { dynamicThemeSchema } from './shared'
import { preprocessTypebot } from '../typebot/helpers/preprocessTypebot'
const chatSessionSchema = z.object({ const chatSessionSchema = z.object({
id: z.string(), id: z.string(),
@@ -84,14 +85,18 @@ const scriptToExecuteSchema = z.object({
), ),
}) })
const startTypebotSchema = typebotSchema._def.schema.pick({ export const startTypebotSchema = z.preprocess(
id: true, preprocessTypebot,
groups: true, typebotSchema._def.schema.pick({
edges: true, version: true,
variables: true, id: true,
settings: true, groups: true,
theme: true, edges: true,
}) variables: true,
settings: true,
theme: true,
})
)
const startParamsSchema = z.object({ const startParamsSchema = z.object({
typebot: startTypebotSchema typebot: startTypebotSchema

View File

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

View File

@@ -5,7 +5,6 @@ import {
variableSchema, variableSchema,
themeSchema, themeSchema,
settingsSchema, settingsSchema,
typebotSchema,
} from './typebot' } from './typebot'
import { z } from 'zod' import { z } from 'zod'
import { preprocessTypebot } from './typebot/helpers/preprocessTypebot' import { preprocessTypebot } from './typebot/helpers/preprocessTypebot'
@@ -26,13 +25,4 @@ export const publicTypebotSchema = z.preprocess(
}) })
) satisfies z.ZodType<PrismaPublicTypebot, z.ZodTypeDef, unknown> ) satisfies z.ZodType<PrismaPublicTypebot, z.ZodTypeDef, unknown>
const publicTypebotWithName = publicTypebotSchema._def.schema.merge(
typebotSchema._def.schema.pick({
name: true,
isArchived: true,
isClosed: true,
})
)
export type PublicTypebot = z.infer<typeof publicTypebotSchema> export type PublicTypebot = z.infer<typeof publicTypebotSchema>
export type PublicTypebotWithName = z.infer<typeof publicTypebotWithName>

View File

@@ -5,10 +5,10 @@ export const preprocessTypebot = (typebot: any) => {
if (!typebot || typebot.version === '5') return typebot if (!typebot || typebot.version === '5') return typebot
return { return {
...typebot, ...typebot,
groups: typebot.groups.map(preprocessGroup), groups: typebot.groups ? typebot.groups.map(preprocessGroup) : [],
edges: typebot.edges?.filter( edges: typebot.edges
(edge: any) => edgeSchema.safeParse(edge).success ? typebot.edges?.filter((edge: any) => edgeSchema.safeParse(edge).success)
), : [],
} }
} }