⚗️ Implement chat API
This commit is contained in:
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
);
|
@ -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
|
||||
|
@ -15,7 +15,6 @@
|
||||
"tsconfig": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "12.0.0",
|
||||
"db": "workspace:*"
|
||||
"next": "13.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
101
packages/models/src/features/chat.ts
Normal file
101
packages/models/src/features/chat.ts
Normal 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>
|
@ -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>
|
||||
|
@ -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<
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -6,3 +6,4 @@ export * from './features/answer'
|
||||
export * from './features/utils'
|
||||
export * from './features/credentials'
|
||||
export * from './features/webhooks'
|
||||
export * from './features/chat'
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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: [
|
||||
|
Reference in New Issue
Block a user