2
0

⚗️ Implement chat API

This commit is contained in:
Baptiste Arnaud
2022-11-29 10:02:40 +01:00
parent 49ba434350
commit bf0d0c2475
122 changed files with 5075 additions and 292 deletions

View File

@ -46,9 +46,7 @@
},
"peerDependencies": {
"db": "workspace:*",
"models": "workspace:*",
"react": "18.0.0",
"react-dom": "18.0.0",
"utils": "workspace:*"
"react-dom": "18.0.0"
}
}

View File

@ -0,0 +1,9 @@
-- CreateTable
CREATE TABLE "ChatSession" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"state" JSONB NOT NULL,
CONSTRAINT "ChatSession_pkey" PRIMARY KEY ("id")
);

View File

@ -84,7 +84,7 @@ model Workspace {
storageLimitFirstEmailSentAt DateTime?
chatsLimitSecondEmailSentAt DateTime?
storageLimitSecondEmailSentAt DateTime?
claimableCustomPlan ClaimableCustomPlan?
claimableCustomPlan ClaimableCustomPlan?
customChatsLimit Int?
customStorageLimit Int?
customSeatsLimit Int?
@ -269,20 +269,27 @@ model Webhook {
}
model ClaimableCustomPlan {
id String @id @default(cuid())
createdAt DateTime @default(now())
id String @id @default(cuid())
createdAt DateTime @default(now())
claimedAt DateTime?
name String
description String?
price Int
currency String
workspaceId String @unique
workspaceId String @unique
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
chatsLimit Int
storageLimit Int
seatsLimit Int
}
model ChatSession {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
state Json
}
enum WorkspaceRole {
ADMIN
MEMBER

View File

@ -15,7 +15,6 @@
"tsconfig": "workspace:*"
},
"peerDependencies": {
"next": "12.0.0",
"db": "workspace:*"
"next": "13.0.0"
}
}

View File

@ -1,27 +1,34 @@
import { z } from 'zod'
import { schemaForType } from './utils'
import { Answer as AnswerPrisma, Prisma } from 'db'
export const answerSchema = z.object({
createdAt: z.date(),
resultId: z.string(),
blockId: z.string(),
groupId: z.string(),
variableId: z.string().nullable(),
content: z.string(),
storageUsed: z.number().nullable(),
})
export const answerInputSchema = answerSchema
.omit({
createdAt: true,
resultId: true,
variableId: true,
storageUsed: true,
export const answerSchema = schemaForType<AnswerPrisma>()(
z.object({
createdAt: z.date(),
resultId: z.string(),
blockId: z.string(),
groupId: z.string(),
variableId: z.string().nullable(),
content: z.string(),
storageUsed: z.number().nullable(),
})
.and(
z.object({
variableId: z.string().nullish(),
storageUsed: z.number().nullish(),
})
)
export const answerInputSchema =
schemaForType<Prisma.AnswerUncheckedUpdateInput>()(
answerSchema
.omit({
createdAt: true,
resultId: true,
variableId: true,
storageUsed: true,
})
.and(
z.object({
variableId: z.string().nullish(),
storageUsed: z.number().nullish(),
})
)
)
export type Stats = {

View File

@ -0,0 +1,101 @@
import { z } from 'zod'
import {
audioBubbleContentSchema,
BubbleBlockType,
embedBubbleContentSchema,
googleAnalyticsOptionsSchema,
imageBubbleContentSchema,
inputBlockSchema,
textBubbleContentSchema,
videoBubbleContentSchema,
} from './blocks'
import { publicTypebotSchema } from './publicTypebot'
import { ChatSession as ChatSessionPrisma } from 'db'
import { schemaForType } from './utils'
import { resultSchema } from './result'
const typebotInSessionStateSchema = publicTypebotSchema.pick({
id: true,
groups: true,
edges: true,
variables: true,
})
export const sessionStateSchema = z.object({
typebot: typebotInSessionStateSchema,
linkedTypebots: z.object({
typebots: z.array(typebotInSessionStateSchema),
queue: z.array(z.object({ edgeId: z.string(), typebotId: z.string() })),
}),
currentTypebotId: z.string(),
result: resultSchema.pick({ id: true, variables: true, hasStarted: true }),
isPreview: z.boolean(),
currentBlock: z
.object({
blockId: z.string(),
groupId: z.string(),
})
.optional(),
})
const chatSessionSchema = schemaForType<ChatSessionPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
state: sessionStateSchema,
})
)
const simplifiedTextBubbleContentSchema = textBubbleContentSchema.pick({
plainText: true,
html: true,
})
const chatMessageContentSchema = simplifiedTextBubbleContentSchema
.or(imageBubbleContentSchema)
.or(videoBubbleContentSchema)
.or(embedBubbleContentSchema)
.or(audioBubbleContentSchema)
const codeToExecuteSchema = z.object({
content: z.string(),
args: z.array(
z.object({
id: z.string(),
value: z.string().or(z.number()).or(z.boolean()).nullish(),
})
),
})
export const chatReplySchema = z.object({
messages: z.array(
z.object({
type: z.nativeEnum(BubbleBlockType),
content: chatMessageContentSchema,
})
),
input: inputBlockSchema.optional(),
logic: z
.object({
redirectUrl: z.string().optional(),
codeToExecute: codeToExecuteSchema.optional(),
})
.optional(),
integrations: z
.object({
chatwoot: z
.object({
codeToExecute: codeToExecuteSchema,
})
.optional(),
googleAnalytics: googleAnalyticsOptionsSchema.optional(),
})
.optional(),
})
export type ChatSession = z.infer<typeof chatSessionSchema>
export type SessionState = z.infer<typeof sessionStateSchema>
export type TypebotInSession = z.infer<typeof typebotInSessionStateSchema>
export type ChatReply = z.infer<typeof chatReplySchema>
export type ChatMessageContent = z.infer<typeof chatMessageContentSchema>

View File

@ -1,21 +1,32 @@
import { Group, Edge, Settings, Theme, Variable } from './typebot'
import { PublicTypebot as PublicTypebotFromPrisma } from 'db'
import {
groupSchema,
edgeSchema,
variableSchema,
themeSchema,
settingsSchema,
typebotSchema,
} from './typebot'
import { PublicTypebot as PublicTypebotPrisma } from 'db'
import { z } from 'zod'
import { schemaForType } from './utils'
export type PublicTypebot = Omit<
PublicTypebotFromPrisma,
| 'groups'
| 'theme'
| 'settings'
| 'variables'
| 'edges'
| 'createdAt'
| 'updatedAt'
> & {
groups: Group[]
variables: Variable[]
edges: Edge[]
theme: Theme
settings: Settings
createdAt: string
updatedAt: string
}
export const publicTypebotSchema = schemaForType<PublicTypebotPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),
groups: z.array(groupSchema),
edges: z.array(edgeSchema),
variables: z.array(variableSchema),
theme: themeSchema,
settings: settingsSchema,
})
)
const publicTypebotWithName = publicTypebotSchema.and(
typebotSchema.pick({ name: true, isArchived: true, isClosed: true })
)
export type PublicTypebot = z.infer<typeof publicTypebotSchema>
export type PublicTypebotWithName = z.infer<typeof publicTypebotWithName>

View File

@ -2,17 +2,21 @@ import { z } from 'zod'
import { answerInputSchema, answerSchema } from './answer'
import { InputBlockType } from './blocks'
import { variableWithValueSchema } from './typebot/variable'
import { Result as ResultPrisma, Log as LogPrisma } from 'db'
import { schemaForType } from './utils'
export const resultSchema = z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),
variables: z.array(variableWithValueSchema),
isCompleted: z.boolean(),
hasStarted: z.boolean().nullable(),
isArchived: z.boolean().nullable(),
})
export const resultSchema = schemaForType<ResultPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
typebotId: z.string(),
variables: z.array(variableWithValueSchema),
isCompleted: z.boolean(),
hasStarted: z.boolean().nullable(),
isArchived: z.boolean().nullable(),
})
)
export const resultWithAnswersSchema = resultSchema.and(
z.object({
@ -26,23 +30,22 @@ export const resultWithAnswersInputSchema = resultSchema.and(
})
)
export const logSchema = z.object({
id: z.string(),
createdAt: z.date(),
resultId: z.string(),
status: z.string(),
description: z.string(),
details: z.string().nullable(),
})
export const logSchema = schemaForType<LogPrisma>()(
z.object({
id: z.string(),
createdAt: z.date(),
resultId: z.string(),
status: z.string(),
description: z.string(),
details: z.string().nullable(),
})
)
export type Result = z.infer<typeof resultSchema>
export type ResultWithAnswers = z.infer<typeof resultWithAnswersSchema>
export type ResultWithAnswersInput = z.infer<
typeof resultWithAnswersInputSchema
>
export type Log = z.infer<typeof logSchema>
export type ResultValues = Pick<

View File

@ -3,8 +3,10 @@ import { settingsSchema } from './settings'
import { blockSchema } from '../blocks'
import { themeSchema } from './theme'
import { variableSchema } from './variable'
import { Typebot as TypebotPrisma } from 'db'
import { schemaForType } from '../utils'
const groupSchema = z.object({
export const groupSchema = z.object({
id: z.string(),
title: z.string(),
graphCoordinates: z.object({
@ -25,7 +27,7 @@ const targetSchema = z.object({
blockId: z.string().optional(),
})
const edgeSchema = z.object({
export const edgeSchema = z.object({
id: z.string(),
from: sourceSchema,
to: targetSchema,
@ -37,27 +39,29 @@ const resultsTablePreferencesSchema = z.object({
columnsWidth: z.record(z.string(), z.number()),
})
const typebotSchema = z.object({
version: z.enum(['2']).optional(),
id: z.string(),
name: z.string(),
groups: z.array(groupSchema),
edges: z.array(edgeSchema),
variables: z.array(variableSchema),
theme: themeSchema,
settings: settingsSchema,
createdAt: z.string(),
updatedAt: z.string(),
icon: z.string().nullable(),
publishedTypebotId: z.string().nullable(),
folderId: z.string().nullable(),
publicId: z.string().nullable(),
customDomain: z.string().nullable(),
workspaceId: z.string(),
resultsTablePreferences: resultsTablePreferencesSchema.optional(),
isArchived: z.boolean(),
isClosed: z.boolean(),
})
export const typebotSchema = schemaForType<TypebotPrisma>()(
z.object({
version: z.enum(['2']).optional(),
id: z.string(),
name: z.string(),
groups: z.array(groupSchema),
edges: z.array(edgeSchema),
variables: z.array(variableSchema),
theme: themeSchema,
settings: settingsSchema,
createdAt: z.date(),
updatedAt: z.date(),
icon: z.string().nullable(),
publishedTypebotId: z.string().nullable(),
folderId: z.string().nullable(),
publicId: z.string().nullable(),
customDomain: z.string().nullable(),
workspaceId: z.string(),
resultsTablePreferences: resultsTablePreferencesSchema.nullable(),
isArchived: z.boolean(),
isClosed: z.boolean(),
})
)
export type Typebot = z.infer<typeof typebotSchema>
export type Target = z.infer<typeof targetSchema>

View File

@ -1 +1,9 @@
import { z } from 'zod'
export type IdMap<T> = { [id: string]: T }
export const schemaForType =
<T>() =>
<S extends z.ZodType<T, any, any>>(arg: S) => {
return arg
}

View File

@ -6,3 +6,4 @@ export * from './features/answer'
export * from './features/utils'
export * from './features/credentials'
export * from './features/webhooks'
export * from './features/chat'

View File

@ -20,9 +20,7 @@
},
"peerDependencies": {
"aws-sdk": "2.1152.0",
"db": "workspace:*",
"models": "workspace:*",
"next": "12.0.0",
"next": "13.0.0",
"nodemailer": "6.7.8"
}
}

View File

@ -79,7 +79,7 @@ export const importTypebotInDatabase = async (
...updates,
}
await prisma.typebot.create({
data: typebot,
data: parseCreateTypebot(typebot),
})
return prisma.publicTypebot.create({
data: parseTypebotToPublicTypebot(
@ -163,7 +163,7 @@ export const createTypebots = async (partialTypebots: Partial<Typebot>[]) => {
}
})
await prisma.typebot.createMany({
data: typebotsWithId.map(parseTestTypebot),
data: typebotsWithId.map(parseTestTypebot).map(parseCreateTypebot),
})
return prisma.publicTypebot.createMany({
data: typebotsWithId.map((t) =>
@ -177,7 +177,7 @@ export const updateTypebot = async (
) => {
await prisma.typebot.updateMany({
where: { id: partialTypebot.id },
data: partialTypebot,
data: parseUpdateTypebot(partialTypebot),
})
return prisma.publicTypebot.updateMany({
where: { typebotId: partialTypebot.id },
@ -194,3 +194,19 @@ export const updateWorkspace = async (
data,
})
}
const parseCreateTypebot = (typebot: Typebot) => ({
...typebot,
resultsTablePreferences:
typebot.resultsTablePreferences === null
? Prisma.DbNull
: typebot.resultsTablePreferences,
})
const parseUpdateTypebot = (typebot: Partial<Typebot>) => ({
...typebot,
resultsTablePreferences:
typebot.resultsTablePreferences === null
? Prisma.DbNull
: typebot.resultsTablePreferences,
})

View File

@ -22,13 +22,14 @@ export const parseTestTypebot = (
theme: defaultTheme,
settings: defaultSettings,
publicId: null,
updatedAt: new Date().toISOString(),
createdAt: new Date().toISOString(),
updatedAt: new Date(),
createdAt: new Date(),
publishedTypebotId: null,
customDomain: null,
icon: null,
isArchived: false,
isClosed: false,
resultsTablePreferences: null,
variables: [{ id: 'var1', name: 'var1' }],
...partialTypebot,
edges: [