import type { InferSelectModel } from "drizzle-orm"; import { boolean, foreignKey, integer, json, jsonb, pgTable, primaryKey, text, timestamp, uuid, varchar, } from "drizzle-orm/pg-core"; import type { AppUsage } from "../usage"; export const user = pgTable("User", { id: uuid("id").primaryKey().notNull().defaultRandom(), email: varchar("email", { length: 64 }).notNull(), password: varchar("password", { length: 64 }), name: varchar("name", { length: 100 }), // 用户姓名(用于显示) department: varchar("department", { length: 256 }), // 部门信息 dingtalkUserId: varchar("dingtalkUserId", { length: 128 }), // 钉钉用户ID role: varchar("role", { enum: ["guest", "regular", "admin"] }) .notNull() .default("regular"), isActive: boolean("isActive").notNull().default(true), monthlyImageLimit: integer("monthlyImageLimit"), // 每月出图限额,NULL或0表示无限制 createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), }); export type User = InferSelectModel; export const chat = pgTable("Chat", { id: uuid("id").primaryKey().notNull().defaultRandom(), createdAt: timestamp("createdAt").notNull(), title: text("title").notNull(), userId: uuid("userId") .notNull() .references(() => user.id), visibility: varchar("visibility", { enum: ["public", "private"] }) .notNull() .default("private"), lastContext: jsonb("lastContext").$type(), }); export type Chat = InferSelectModel; // DEPRECATED: The following schema is deprecated and will be removed in the future. // Read the migration guide at https://chat-sdk.dev/docs/migration-guides/message-parts export const messageDeprecated = pgTable("Message", { id: uuid("id").primaryKey().notNull().defaultRandom(), chatId: uuid("chatId") .notNull() .references(() => chat.id), role: varchar("role").notNull(), content: json("content").notNull(), createdAt: timestamp("createdAt").notNull(), }); export type MessageDeprecated = InferSelectModel; export const message = pgTable("Message_v2", { id: uuid("id").primaryKey().notNull().defaultRandom(), chatId: uuid("chatId") .notNull() .references(() => chat.id), role: varchar("role").notNull(), parts: json("parts").notNull(), attachments: json("attachments").notNull(), createdAt: timestamp("createdAt").notNull(), // 🆕 视频存储相关字段 videoStorageStatus: varchar("video_storage_status", { length: 20 }).default("temporary"), temporaryVideoUrl: text("temporary_video_url"), permanentVideoUrl: text("permanent_video_url"), archivedAt: timestamp("archived_at"), videoMetadata: jsonb("video_metadata"), }); export type DBMessage = InferSelectModel; // DEPRECATED: The following schema is deprecated and will be removed in the future. // Read the migration guide at https://chat-sdk.dev/docs/migration-guides/message-parts export const voteDeprecated = pgTable( "Vote", { chatId: uuid("chatId") .notNull() .references(() => chat.id), messageId: uuid("messageId") .notNull() .references(() => messageDeprecated.id), isUpvoted: boolean("isUpvoted").notNull(), }, (table) => { return { pk: primaryKey({ columns: [table.chatId, table.messageId] }), }; } ); export type VoteDeprecated = InferSelectModel; export const vote = pgTable( "Vote_v2", { chatId: uuid("chatId") .notNull() .references(() => chat.id), messageId: uuid("messageId") .notNull() .references(() => message.id), isUpvoted: boolean("isUpvoted").notNull(), }, (table) => { return { pk: primaryKey({ columns: [table.chatId, table.messageId] }), }; } ); export type Vote = InferSelectModel; export const document = pgTable( "Document", { id: uuid("id").notNull().defaultRandom(), createdAt: timestamp("createdAt").notNull(), title: text("title").notNull(), content: text("content"), kind: varchar("text", { enum: ["text", "code", "image", "sheet"] }) .notNull() .default("text"), userId: uuid("userId") .notNull() .references(() => user.id), }, (table) => { return { pk: primaryKey({ columns: [table.id, table.createdAt] }), }; } ); export type Document = InferSelectModel; export const suggestion = pgTable( "Suggestion", { id: uuid("id").notNull().defaultRandom(), documentId: uuid("documentId").notNull(), documentCreatedAt: timestamp("documentCreatedAt").notNull(), originalText: text("originalText").notNull(), suggestedText: text("suggestedText").notNull(), description: text("description"), isResolved: boolean("isResolved").notNull().default(false), userId: uuid("userId") .notNull() .references(() => user.id), createdAt: timestamp("createdAt").notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.id] }), documentRef: foreignKey({ columns: [table.documentId, table.documentCreatedAt], foreignColumns: [document.id, document.createdAt], }), }) ); export type Suggestion = InferSelectModel; export const stream = pgTable( "Stream", { id: uuid("id").notNull().defaultRandom(), chatId: uuid("chatId").notNull(), createdAt: timestamp("createdAt").notNull(), }, (table) => ({ pk: primaryKey({ columns: [table.id] }), chatRef: foreignKey({ columns: [table.chatId], foreignColumns: [chat.id], }), }) ); export type Stream = InferSelectModel; export const systemSettings = pgTable("SystemSettings", { id: uuid("id").primaryKey().notNull().defaultRandom(), key: varchar("key", { length: 128 }).notNull().unique(), value: jsonb("value").notNull(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), }); export type SystemSettings = InferSelectModel; export const toolConfig = pgTable("ToolConfig", { id: uuid("id").primaryKey().notNull().defaultRandom(), toolName: varchar("toolName", { length: 128 }).notNull().unique(), isEnabled: boolean("isEnabled").notNull().default(true), description: text("description"), config: jsonb("config"), updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), }); export type ToolConfig = InferSelectModel; // AI 提供商配置表 export const aiProvider = pgTable("AiProvider", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: varchar("name", { length: 128 }).notNull(), // 提供商名称,如 "阿里云通义千问" type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image", "zhipu"] }) .notNull() .default("custom"), // 提供商类型 baseUrl: text("baseUrl").notNull(), // API 基础 URL apiKey: text("apiKey").notNull(), // API Key (加密存储) isEnabled: boolean("isEnabled").notNull().default(true), createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), }); export type AiProvider = InferSelectModel; // AI 模型配置表 export const aiModel = pgTable("AiModel", { id: uuid("id").primaryKey().notNull().defaultRandom(), providerId: uuid("providerId") .notNull() .references(() => aiProvider.id, { onDelete: "cascade" }), modelId: varchar("modelId", { length: 128 }).notNull(), // 模型 ID,如 "qwen-turbo" displayName: varchar("displayName", { length: 128 }).notNull(), // 显示名称 description: text("description"), // 模型描述 supportsVision: boolean("supportsVision").notNull().default(false), // 是否支持图片输入 supportsImageGen: boolean("supportsImageGen").notNull().default(false), // 是否支持图片生成 isEnabled: boolean("isEnabled").notNull().default(true), // 是否启用 isDefault: boolean("isDefault").notNull().default(false), // 是否为默认模型 sortOrder: integer("sortOrder").notNull().default(0), // 排序顺序 capabilities: jsonb("capabilities"), // 🆕 模型能力配置(用于视频模型) defaultParams: jsonb("defaultParams"), // 🆕 默认参数配置 // 🆕 子模型相关字段(用于火山引擎视频等需要智能选择的模型) isSubModel: boolean("is_sub_model").notNull().default(false), // 是否为子模型(不在模型列表中显示) parentModelId: uuid("parent_model_id"), // 父模型ID(如果是子模型),注:暂不添加外键约束避免循环引用 // 🆕 备用模型 ID(当主模型请求失败时自动切换) fallbackModelId1: varchar("fallbackModelId1", { length: 128 }), // 备用模型 ID 1 fallbackModelId2: varchar("fallbackModelId2", { length: 128 }), // 备用模型 ID 2 // 🆕 图片生成模型特性配置(用于阿里生图、Gemini等) imageGenConfig: jsonb("imageGenConfig"), // { maxImages: 6, supportsSize: true, supportsPromptExtend: true, supportsNegativePrompt: true, enableLoadingPlaceholder: true } createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), }); export type AiModel = InferSelectModel; // S3 存储配置表 export const storageConfig = pgTable("StorageConfig", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: varchar("name", { length: 128 }).notNull(), // 配置名称 type: varchar("type", { enum: ["s3", "local"] }).notNull().default("s3"), endpoint: text("endpoint"), // S3 端点 region: varchar("region", { length: 64 }), // 区域 bucket: varchar("bucket", { length: 128 }), // 存储桶名称 accessKeyId: text("accessKeyId"), // Access Key ID secretAccessKey: text("secretAccessKey"), // Secret Access Key publicUrl: text("publicUrl"), // 公开访问 URL 前缀 isDefault: boolean("isDefault").notNull().default(false), // 是否为默认存储 isEnabled: boolean("isEnabled").notNull().default(true), createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), }); export type StorageConfig = InferSelectModel; // 网站设置表 export const siteSettings = pgTable("SiteSettings", { id: uuid("id").primaryKey().notNull().defaultRandom(), siteName: varchar("siteName", { length: 128 }).notNull().default("AI Chatbot"), siteLogo: text("siteLogo"), // 默认/白天模式 LOGO siteLogoDark: text("siteLogoDark"), // 黑夜模式 LOGO favicon: text("favicon"), showAutoModel: boolean("showAutoModel").notNull().default(true), // 是否显示"自动"模型选项 defaultTheme: varchar("defaultTheme", { length: 16 }).notNull().default("system"), // 默认主题:system | light | dark // 使用时间限制 usageTimeEnabled: boolean("usageTimeEnabled").notNull().default(false), // 是否启用时间限制 usageTimeStart: varchar("usageTimeStart", { length: 5 }).default("08:00"), // 开始时间(HH:mm) usageTimeEnd: varchar("usageTimeEnd", { length: 5 }).default("20:00"), // 结束时间(HH:mm) usageTimeMessage: text("usageTimeMessage").default("由壹点累了也是需要休息的"), // 超时提示词 // 广告横幅设置 bannerEnabled: boolean("bannerEnabled").notNull().default(true), // 是否显示广告横幅 bannerText: text("bannerText").default("升级会员, Nano Banana Pro 免费365天!"), // 广告横幅文字 bannerLink: text("bannerLink"), // 广告横幅链接(可选) // 工具箱设置 toolbox1Name: varchar("toolbox1Name", { length: 64 }).default("工具箱"), // 工具箱1名称 toolbox1Enabled: boolean("toolbox1Enabled").notNull().default(true), // 是否显示工具箱1 toolbox2Name: varchar("toolbox2Name", { length: 64 }).default("工具箱2"), // 工具箱2名称 toolbox2Enabled: boolean("toolbox2Enabled").notNull().default(false), // 是否显示工具箱2 // 功能教程设置 tutorialVideoUrl: text("tutorialVideoUrl"), // 功能教程视频链接 tutorialEnabled: boolean("tutorialEnabled").notNull().default(false), // 是否显示功能教程按钮 // 精选榜设置 rankingEnabled: boolean("rankingEnabled").notNull().default(true), // 是否显示精选榜按钮 // 无限画布设置 canvasEnabled: boolean("canvasEnabled").notNull().default(true), // 是否启用无限画布功能 canvasDefaultModelId: varchar("canvasDefaultModelId", { length: 128 }), // 画布默认选择的模型ID(null表示智能自动) canvasAutoModeDefaultModelId: varchar("canvasAutoModeDefaultModelId", { length: 128 }), // 智能自动模式下实际使用的默认生图模型ID updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), }); export type SiteSettings = InferSelectModel; // 导航链接表 export const navLinks = pgTable("NavLinks", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: varchar("name", { length: 64 }).notNull(), url: text("url").notNull(), order: integer("order").notNull().default(0), isEnabled: boolean("isEnabled").notNull().default(true), toolboxGroup: integer("toolboxGroup").notNull().default(1), // 属于哪个工具箱:1 或 2 createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), createdBy: uuid("createdBy").references(() => user.id), }); export type NavLink = InferSelectModel; // 视频教程表 export const tutorialVideos = pgTable("TutorialVideos", { id: uuid("id").primaryKey().notNull().defaultRandom(), title: varchar("title", { length: 128 }).notNull(), // 视频标题 videoUrl: text("videoUrl").notNull(), // 视频播放链接 coverUrl: text("coverUrl"), // 视频封面图片链接 description: text("description"), // 视频描述 order: integer("order").notNull().default(0), // 排序 isEnabled: boolean("isEnabled").notNull().default(true), // 是否启用 createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), createdBy: uuid("createdBy").references(() => user.id), }); export type TutorialVideo = InferSelectModel; // 认证配置表 export const authSettings = pgTable("AuthSettings", { id: uuid("id").primaryKey().notNull().defaultRandom(), dingTalkAppKey: text("dingTalkAppKey"), dingTalkAppSecret: text("dingTalkAppSecret"), // 多组织钉钉配置,存储为 JSON 数组:[{ name, appKey, appSecret }] dingTalkOrganizations: jsonb("dingTalkOrganizations").$type< { name: string; appKey: string; appSecret: string }[] | null >(), isDingTalkEnabled: boolean("isDingTalkEnabled").notNull().default(false), isRegistrationEnabled: boolean("isRegistrationEnabled").notNull().default(true), updatedAt: timestamp("updatedAt").notNull().defaultNow(), updatedBy: uuid("updatedBy").references(() => user.id), }); export type AuthSettings = InferSelectModel; // 临时登录令牌表 export const loginToken = pgTable("LoginToken", { id: uuid("id").primaryKey().notNull().defaultRandom(), token: text("token").notNull().unique(), email: varchar("email", { length: 64 }).notNull(), expiresAt: timestamp("expiresAt").notNull(), createdAt: timestamp("createdAt").notNull().defaultNow(), }); export type LoginToken = InferSelectModel; // 生成图片记录表 export const generatedImage = pgTable("GeneratedImage", { id: uuid("id").primaryKey().notNull().defaultRandom(), userId: uuid("userId") .notNull() .references(() => user.id), chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }), messageId: uuid("messageId"), thumbnailUrl: text("thumbnailUrl").notNull(), originalUrl: text("originalUrl").notNull(), imageSize: varchar("imageSize", { length: 10 }).default("2K"), prompt: text("prompt"), modelId: varchar("modelId", { length: 128 }), isFeatured: boolean("isFeatured").notNull().default(false), // 是否精选 featuredAt: timestamp("featuredAt"), // 精选时间 featuredBy: uuid("featuredBy").references(() => user.id), // 精选操作人 createdAt: timestamp("createdAt").notNull().defaultNow(), }); export type GeneratedImage = InferSelectModel; // ============================================ // 无限画布相关表 // ============================================ // 画布项目表 export const canvasProject = pgTable("CanvasProject", { id: uuid("id").primaryKey().notNull().defaultRandom(), userId: uuid("userId") .notNull() .references(() => user.id, { onDelete: "cascade" }), title: varchar("title", { length: 256 }).notNull().default("未命名画布"), description: text("description"), thumbnail: text("thumbnail"), // 存储完整画布状态(节点、边、视口等) canvasData: jsonb("canvasData").$type<{ nodes: any[]; edges: any[]; viewport: { x: number; y: number; zoom: number }; }>(), createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), }); export type CanvasProject = InferSelectModel; // 画布节点表(可选,用于更细粒度的查询) export const canvasNode = pgTable("CanvasNode", { id: uuid("id").primaryKey().notNull().defaultRandom(), projectId: uuid("projectId") .notNull() .references(() => canvasProject.id, { onDelete: "cascade" }), type: varchar("type", { length: 32 }).notNull(), // 'message' | 'image' | 'video' | 'text' | 'scene-fusion' position: jsonb("position").$type<{ x: number; y: number }>(), size: jsonb("size").$type<{ width: number; height: number }>(), data: jsonb("data"), // 节点具体数据 connections: jsonb("connections").$type().default([]), parentId: uuid("parentId"), // 父节点ID,用于对话上下文 createdAt: timestamp("createdAt").notNull().defaultNow(), updatedAt: timestamp("updatedAt").notNull().defaultNow(), }); export type CanvasNode = InferSelectModel;