471 lines
20 KiB
TypeScript
471 lines
20 KiB
TypeScript
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表示无限制
|
||
pointsBalance: integer("pointsBalance").notNull().default(0), // 当前剩余积分
|
||
pointsGiftedTotal: integer("pointsGiftedTotal").notNull().default(0), // 管理员累计赠送积分
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
});
|
||
|
||
export type User = InferSelectModel<typeof user>;
|
||
|
||
export const pointsLedger = pgTable("PointsLedger", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
userId: uuid("userId")
|
||
.notNull()
|
||
.references(() => user.id, { onDelete: "cascade" }),
|
||
amount: integer("amount").notNull(), // 正数=赠送/充值,负数=消耗
|
||
reason: varchar("reason", { length: 64 }).notNull(), // gift | image_generation | recharge | adjust
|
||
modelId: varchar("modelId", { length: 128 }),
|
||
chatId: uuid("chatId"),
|
||
messageId: uuid("messageId"),
|
||
note: text("note"),
|
||
createdBy: uuid("createdBy").references(() => user.id),
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
});
|
||
|
||
export type PointsLedger = InferSelectModel<typeof pointsLedger>;
|
||
|
||
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<AppUsage | null>(),
|
||
});
|
||
|
||
export type Chat = InferSelectModel<typeof chat>;
|
||
|
||
// 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<typeof messageDeprecated>;
|
||
|
||
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<typeof message>;
|
||
|
||
// 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<typeof voteDeprecated>;
|
||
|
||
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<typeof vote>;
|
||
|
||
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<typeof document>;
|
||
|
||
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<typeof suggestion>;
|
||
|
||
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<typeof stream>;
|
||
|
||
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<typeof systemSettings>;
|
||
|
||
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<typeof toolConfig>;
|
||
|
||
// 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<typeof aiProvider>;
|
||
|
||
// 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<typeof aiModel>;
|
||
|
||
// 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<typeof storageConfig>;
|
||
|
||
// 网站设置表
|
||
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
|
||
lovartPromoText: text("lovartPromoText").default(
|
||
"现在升级,立享 Kling 3.0 与 Nano Banana Pro,最高 5 折优惠"
|
||
),
|
||
lovartPromoActionText: varchar("lovartPromoActionText", { length: 32 }).default("立即升级"),
|
||
lovartHeroTitle: text("lovartHeroTitle").default("由壹点AI 让设计更简单"),
|
||
lovartHeroSubtitle: text("lovartHeroSubtitle").default("懂你的设计代理,帮你搞定一切"),
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
updatedBy: uuid("updatedBy").references(() => user.id),
|
||
});
|
||
|
||
export type SiteSettings = InferSelectModel<typeof siteSettings>;
|
||
|
||
// 导航链接表
|
||
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<typeof navLinks>;
|
||
|
||
// 视频教程表
|
||
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<typeof tutorialVideos>;
|
||
|
||
// 认证配置表
|
||
export const authSettings = pgTable("AuthSettings", {
|
||
id: uuid("id").primaryKey().notNull().defaultRandom(),
|
||
dingTalkAppKey: text("dingTalkAppKey"),
|
||
dingTalkAppSecret: text("dingTalkAppSecret"),
|
||
googleClientId: text("googleClientId"),
|
||
googleClientSecret: text("googleClientSecret"),
|
||
authUrl: text("authUrl"),
|
||
// 多组织钉钉配置,存储为 JSON 数组:[{ name, appKey, appSecret }]
|
||
dingTalkOrganizations: jsonb("dingTalkOrganizations").$type<
|
||
{ name: string; appKey: string; appSecret: string }[] | null
|
||
>(),
|
||
isDingTalkEnabled: boolean("isDingTalkEnabled").notNull().default(false),
|
||
isGoogleEnabled: boolean("isGoogleEnabled").notNull().default(false),
|
||
isRegistrationEnabled: boolean("isRegistrationEnabled").notNull().default(true),
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
updatedBy: uuid("updatedBy").references(() => user.id),
|
||
});
|
||
|
||
export type AuthSettings = InferSelectModel<typeof authSettings>;
|
||
|
||
// 临时登录令牌表
|
||
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<typeof loginToken>;
|
||
|
||
// 生成图片记录表
|
||
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), // 是否精选
|
||
featuredCategory: varchar("featuredCategory", { length: 64 }), // 精选分类
|
||
featuredAt: timestamp("featuredAt"), // 精选时间
|
||
featuredBy: uuid("featuredBy").references(() => user.id), // 精选操作人
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
});
|
||
|
||
export type GeneratedImage = InferSelectModel<typeof generatedImage>;
|
||
|
||
// ============================================
|
||
// 无限画布相关表
|
||
// ============================================
|
||
|
||
// 画布项目表
|
||
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<typeof canvasProject>;
|
||
|
||
// 画布节点表(可选,用于更细粒度的查询)
|
||
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<string[]>().default([]),
|
||
parentId: uuid("parentId"), // 父节点ID,用于对话上下文
|
||
createdAt: timestamp("createdAt").notNull().defaultNow(),
|
||
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
|
||
});
|
||
|
||
export type CanvasNode = InferSelectModel<typeof canvasNode>;
|