ai-chatbot/lib/db/schema.ts
Your Name 68eab912d8
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
perf: optimize agent style rendering
2026-05-20 18:10:43 +08:00

1353 lines
58 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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", "agent_admin"] })
.notNull()
.default("regular"),
isHighVip: boolean("isHighVip").notNull().default(false),
isActive: boolean("isActive").notNull().default(true),
activeSessionKey: varchar("activeSessionKey", { length: 64 }),
activeSessionCreatedAt: timestamp("activeSessionCreatedAt"),
activeSessionIp: varchar("activeSessionIp", { length: 64 }),
activeSessionUserAgent: text("activeSessionUserAgent"),
activeSessionLastSeenAt: timestamp("activeSessionLastSeenAt"),
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 paymentOrder = pgTable("PaymentOrder", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
provider: varchar("provider", { length: 32 }).notNull().default("dulupay"),
outTradeNo: varchar("outTradeNo", { length: 128 }).notNull().unique(),
tradeNo: varchar("tradeNo", { length: 128 }),
packageId: varchar("packageId", { length: 64 }),
points: integer("points").notNull().default(0),
amountCents: integer("amountCents").notNull().default(0),
currency: varchar("currency", { length: 8 }).notNull().default("CNY"),
payType: varchar("payType", { length: 32 }),
status: varchar("status", { length: 32 }).notNull().default("pending"),
paymentUrl: text("paymentUrl"),
rawCallback: jsonb("rawCallback"),
errorMessage: text("errorMessage"),
paidAt: timestamp("paidAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type PaymentOrder = InferSelectModel<typeof paymentOrder>;
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 systemSettingHistory = pgTable("SystemSettingHistory", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
key: varchar("key", { length: 128 }).notNull(),
previousValue: jsonb("previousValue"),
nextValue: jsonb("nextValue"),
reason: varchar("reason", { length: 128 }),
changedAt: timestamp("changedAt").notNull().defaultNow(),
changedBy: uuid("changedBy").references(() => user.id),
});
export type SystemSettingHistory = InferSelectModel<typeof systemSettingHistory>;
export const dingTalkNotificationTask = pgTable("DingTalkNotificationTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
title: varchar("title", { length: 100 }).notNull(),
content: text("content").notNull(),
status: varchar("status", { length: 24 }).notNull().default("pending"),
scheduledAt: timestamp("scheduledAt").notNull(),
attempts: integer("attempts").notNull().default(0),
lastAttemptAt: timestamp("lastAttemptAt"),
sentAt: timestamp("sentAt"),
cancelledAt: timestamp("cancelledAt"),
lastError: text("lastError"),
createdBy: uuid("createdBy").references(() => user.id),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type DingTalkNotificationTask = InferSelectModel<
typeof dingTalkNotificationTask
>;
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", "grsai-gpt-image", "grsai-video", "huanke-gemini", "qianwen-video", "doubao-video", "ruxa-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image", "fal-image", "openai-image", "stepfun-image", "midjourney", "fal-video", "zhipu", "tripo-3d", "worldlabs-3d", "kittycad-cad", "runway", "krea", "bfl-image"] })
.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(), // 显示名称
avatarUrl: text("avatarUrl"), // 模型头像(建议 400x400)
description: text("description"), // 模型描述
supportsVision: boolean("supportsVision").notNull().default(false), // 是否支持图片输入
supportsImageGen: boolean("supportsImageGen").notNull().default(false), // 是否支持图片生成
isEnabled: boolean("isEnabled").notNull().default(true), // 是否启用
isHidden: boolean("isHidden").notNull().default(false), // 是否在前端模型选择中隐藏
isDefault: boolean("isDefault").notNull().default(false), // 是否为默认模型
isDefaultTextEditModel: boolean("isDefaultTextEditModel").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
excludeFromFailover: boolean("excludeFromFailover").notNull().default(false), // 不参与故障迁移
healthStatus: varchar("healthStatus", {
enum: ["healthy", "degraded", "auto_disabled"],
})
.notNull()
.default("healthy"),
healthConsecutiveFailures: integer("healthConsecutiveFailures")
.notNull()
.default(0),
healthLastEventType: varchar("healthLastEventType", { length: 32 }),
healthLastError: text("healthLastError"),
healthLastFailureAt: timestamp("healthLastFailureAt"),
healthLastSuccessAt: timestamp("healthLastSuccessAt"),
healthAutoDisabledAt: timestamp("healthAutoDisabledAt"),
healthAutoDisabledReason: text("healthAutoDisabledReason"),
healthRecoverySuccessStreak: integer("healthRecoverySuccessStreak")
.notNull()
.default(0),
healthObservationStartedAt: timestamp("healthObservationStartedAt"),
healthUpdatedAt: timestamp("healthUpdatedAt").notNull().defaultNow(),
// 🆕 图片生成模型特性配置(用于阿里生图、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>;
export const aiModelHealthEvent = pgTable("AiModelHealthEvent", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
modelId: uuid("modelId").references(() => aiModel.id, { onDelete: "set null" }),
providerId: uuid("providerId").references(() => aiProvider.id, {
onDelete: "set null",
}),
modelRuntimeId: varchar("modelRuntimeId", { length: 128 }),
modelDisplayName: varchar("modelDisplayName", { length: 128 }),
providerType: varchar("providerType", { length: 64 }),
providerName: varchar("providerName", { length: 128 }),
eventType: varchar("eventType", { length: 32 }).notNull().default("error"),
severity: varchar("severity", { length: 24 }).notNull().default("error"),
context: varchar("context", { length: 96 }),
counted: boolean("counted").notNull().default(true),
consecutiveFailures: integer("consecutiveFailures").notNull().default(0),
autoDisabled: boolean("autoDisabled").notNull().default(false),
elapsedMs: integer("elapsedMs"),
requestId: varchar("requestId", { length: 128 }),
errorMessage: text("errorMessage"),
metadata: jsonb("metadata"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type AiModelHealthEvent = InferSelectModel<typeof aiModelHealthEvent>;
// 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>;
// 阿里云 OSS 图片迁移任务表
export const storageMigrationTask = pgTable("StorageMigrationTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
sourceUrl: text("sourceUrl").notNull().unique(),
sourcePath: text("sourcePath").notNull(),
sourceStorageConfigId: uuid("sourceStorageConfigId").references(() => storageConfig.id),
targetStorageConfigId: uuid("targetStorageConfigId").references(() => storageConfig.id),
targetUrl: text("targetUrl"),
status: varchar("status", { length: 32 }).notNull().default("pending"),
attemptCount: integer("attemptCount").notNull().default(0),
lastError: text("lastError"),
migrateAfterAt: timestamp("migrateAfterAt").notNull(),
cleanupAfterAt: timestamp("cleanupAfterAt").notNull(),
migratedAt: timestamp("migratedAt"),
cleanedAt: timestamp("cleanedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type StorageMigrationTask = InferSelectModel<typeof storageMigrationTask>;
// 网站设置表
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), // 是否显示精选榜按钮
productPlanningEnabled: boolean("productPlanningEnabled").notNull().default(true), // 是否显示并启用商品策划工具
// 无限画布设置
canvasEnabled: boolean("canvasEnabled").notNull().default(true), // 是否启用无限画布功能
agentEnabled: boolean("agentEnabled").notNull().default(true), // 是否启用风格化功能
realtimeCanvasEnabled: boolean("realtimeCanvasEnabled").notNull().default(true), // 是否显示实时画布入口
canvasDefaultModelId: varchar("canvasDefaultModelId", { length: 128 }), // 画布默认选择的模型ID(null表示智能自动)
canvasAutoModeDefaultModelId: varchar("canvasAutoModeDefaultModelId", { length: 128 }), // 智能自动模式下实际使用的默认生图模型ID
agentWorkflowDefaultModelId: varchar("agentWorkflowDefaultModelId", { length: 128 }), // 风格化专用默认生图模型ID
canvasDetailDefaultModelId: varchar("canvasDetailDefaultModelId", { length: 128 }), // AI详情生成专用默认生图模型ID
canvasDetailAgentModelId: varchar("canvasDetailAgentModelId", { length: 128 }), // AI详情生成专用Agent模型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),
isEmailVerificationEnabled: boolean("isEmailVerificationEnabled")
.notNull()
.default(false),
emailVerificationExpiresMinutes: integer("emailVerificationExpiresMinutes")
.notNull()
.default(10),
resendApiKey: text("resendApiKey"),
resendFromEmail: text("resendFromEmail"),
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 emailVerificationCode = pgTable("EmailVerificationCode", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
email: varchar("email", { length: 64 }).notNull(),
purpose: varchar("purpose", { length: 32 }).notNull().default("register"),
codeHash: varchar("codeHash", { length: 128 }).notNull(),
expiresAt: timestamp("expiresAt").notNull(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type EmailVerificationCode = InferSelectModel<typeof emailVerificationCode>;
// 生成图片记录表
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 externalImageApiRequestLog = pgTable("ExternalImageApiRequestLog", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
apiKeyId: varchar("apiKeyId", { length: 64 }),
apiKeyName: varchar("apiKeyName", { length: 128 }),
ownerUserId: uuid("ownerUserId").references(() => user.id),
modelDbId: uuid("modelDbId").references(() => aiModel.id),
modelRuntimeId: varchar("modelRuntimeId", { length: 128 }),
modelDisplayName: varchar("modelDisplayName", { length: 128 }),
prompt: text("prompt"),
negativePrompt: text("negativePrompt"),
imageCount: integer("imageCount").notNull().default(1),
aspectRatio: varchar("aspectRatio", { length: 32 }),
imageSize: varchar("imageSize", { length: 32 }),
status: varchar("status", { length: 32 }).notNull().default("pending"),
requesterIp: varchar("requesterIp", { length: 64 }),
requesterUserAgent: text("requesterUserAgent"),
requestPayload: jsonb("requestPayload"),
responsePayload: jsonb("responsePayload"),
generatedImageIds: jsonb("generatedImageIds"),
generatedAssetIds: jsonb("generatedAssetIds"),
generatedImageUrls: jsonb("generatedImageUrls"),
errorMessage: text("errorMessage"),
durationMs: integer("durationMs"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
completedAt: timestamp("completedAt"),
});
export type ExternalImageApiRequestLog = InferSelectModel<
typeof externalImageApiRequestLog
>;
export const clientPerformanceEvent = pgTable("ClientPerformanceEvent", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId").references(() => user.id, { onDelete: "set null" }),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "set null" }),
projectId: varchar("projectId", { length: 64 }),
eventType: varchar("eventType", { length: 64 }).notNull(),
section: varchar("section", { length: 32 }).notNull().default("unknown"),
action: varchar("action", { length: 64 }),
path: varchar("path", { length: 256 }),
durationMs: integer("durationMs").notNull().default(0),
blockingDurationMs: integer("blockingDurationMs").notNull().default(0),
domNodeCount: integer("domNodeCount").notNull().default(0),
visibleImageCount: integer("visibleImageCount").notNull().default(0),
loadedImageCount: integer("loadedImageCount").notNull().default(0),
viewport: jsonb("viewport"),
browser: varchar("browser", { length: 128 }),
metadata: jsonb("metadata"),
requesterIp: varchar("requesterIp", { length: 64 }),
requesterUserAgent: text("requesterUserAgent"),
occurredAt: timestamp("occurredAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type ClientPerformanceEvent = InferSelectModel<
typeof clientPerformanceEvent
>;
// 风格化分类表
export const agentPresetCategory = pgTable("AgentPresetCategory", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
name: varchar("name", { length: 64 }).notNull(),
slug: varchar("slug", { length: 64 }).notNull(),
scope: varchar("scope", { enum: ["public", "private", "global"] })
.notNull()
.default("public"),
createdBy: uuid("createdBy").references(() => user.id, { onDelete: "set null" }),
sortOrder: integer("sortOrder").notNull().default(0),
isEnabled: boolean("isEnabled").notNull().default(true),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type AgentPresetCategory = InferSelectModel<typeof agentPresetCategory>;
// 风格化表
export const agentPreset = pgTable("AgentPreset", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
ownerUserId: uuid("ownerUserId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
name: varchar("name", { length: 128 }).notNull(),
type: varchar("type", { enum: ["image", "video", "text"] })
.notNull()
.default("image"),
secondaryType: varchar("secondaryType", { length: 128 }),
visibility: varchar("visibility", { enum: ["public", "private"] })
.notNull()
.default("private"),
categoryId: uuid("categoryId").references(() => agentPresetCategory.id, {
onDelete: "set null",
}),
categoryPath: jsonb("categoryPath").$type<{
sectionId?: string | null;
sectionName?: string | null;
level1Id?: string | null;
level1Name?: string | null;
level2Id?: string | null;
level2Name?: string | null;
level3Id?: string | null;
level3Name?: string | null;
productCategory?: string | null;
audienceTag?: string | null;
usageScenario?: string | null;
brand?: string | null;
}>(),
promptTemplate: text("promptTemplate").notNull().default(""),
description: text("description"),
defaultModelId: uuid("defaultModelId").references(() => aiModel.id, {
onDelete: "set null",
}),
defaultParams: jsonb("defaultParams").$type<Record<string, any>>(),
slotCount: integer("slotCount").notNull().default(1),
requiredUploadCount: integer("requiredUploadCount").notNull().default(1),
coverAssetId: uuid("coverAssetId"),
status: varchar("status", { enum: ["active", "disabled", "hidden", "blocked"] })
.notNull()
.default("active"),
sortOrder: integer("sortOrder").notNull().default(0),
usageCount: integer("usageCount").notNull().default(0),
lastUsedAt: timestamp("lastUsedAt"),
forkedFromPresetId: uuid("forkedFromPresetId"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type AgentPreset = InferSelectModel<typeof agentPreset>;
// 风格化槽位表(每个槽位=一套提示词 + 一组参考图 + 一张用户上传图)
export const agentPresetSlot = pgTable("AgentPresetSlot", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
presetId: uuid("presetId")
.notNull()
.references(() => agentPreset.id, { onDelete: "cascade" }),
slotIndex: integer("slotIndex").notNull(),
title: varchar("title", { length: 128 }),
promptTemplate: text("promptTemplate").notNull().default(""),
defaultParams: jsonb("defaultParams").$type<Record<string, any>>(),
requireUpload: boolean("requireUpload").notNull().default(true),
uploadLabel: varchar("uploadLabel", { length: 128 }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type AgentPresetSlot = InferSelectModel<typeof agentPresetSlot>;
// ============================================
// 无限画布相关表
// ============================================
// 画布项目表
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"),
workspaceType: varchar("workspaceType", { enum: ["canvas", "agent", "video"] })
.notNull()
.default("canvas"),
// 存储完整画布状态(节点、边、视口等)
canvasData: jsonb("canvasData").$type<{
nodes: any[];
edges?: any[];
selectedNodeIds?: string[];
viewport?: { x: number; y: number; zoom: number };
tool?: string;
suppressedMediaKeys?: string[];
workflowState?: {
inputs?: {
primarySku?: string;
secondarySku?: string;
brand?: string;
store?: string;
thirdCategory?: string;
fourthCategory?: string;
fifthCategory?: string;
priceBand?: string;
};
appearanceRecognitionEnabled?: boolean;
materials?: {
primary?: {
result?: {
query?: string;
matchedSku?: string;
materialId?: string | null;
productModel?: string | null;
zipUrl?: string | null;
fixedCount?: number;
otherCount?: number;
totalCount?: number;
images?: Array<{
id?: string;
key?: string;
label?: string;
fileName?: string;
imageUrl?: string;
assetId?: string | null;
mimeType?: string | null;
appearanceSummary?: string | null;
group?: "fixed" | "other";
sortOrder?: number;
}>;
} | null;
selectedImageIds?: string[];
};
secondary?: {
result?: {
query?: string;
matchedSku?: string;
materialId?: string | null;
productModel?: string | null;
zipUrl?: string | null;
fixedCount?: number;
otherCount?: number;
totalCount?: number;
images?: Array<{
id?: string;
key?: string;
label?: string;
fileName?: string;
imageUrl?: string;
assetId?: string | null;
mimeType?: string | null;
appearanceSummary?: string | null;
group?: "fixed" | "other";
sortOrder?: number;
}>;
} | null;
selectedImageIds?: string[];
};
};
selections?: Record<string, string>;
selectedAgentPresetId?: string | null;
currentStage?: "workflow" | "mode";
};
}>(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type CanvasProject = InferSelectModel<typeof canvasProject>;
// AI详情项目存档表
export const canvasDetailArchive = pgTable("CanvasDetailArchive", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
projectId: uuid("projectId")
.notNull()
.references(() => canvasProject.id, { onDelete: "cascade" }),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
title: varchar("title", { length: 256 }).notNull().default("AI详情项目"),
prompt: text("prompt").notNull().default(""),
generationMode: varchar("generationMode", { length: 32 })
.notNull()
.default("classic"),
brief: jsonb("brief").notNull(),
slots: jsonb("slots").notNull(),
summary: text("summary"),
imageCount: integer("imageCount"),
coverUrl: text("coverUrl"),
uiSurfaceSnapshot: jsonb("uiSurfaceSnapshot").$type<Record<string, any> | null>(),
uiSurfaceLatestReview: jsonb("uiSurfaceLatestReview").$type<Record<string, any> | null>(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type CanvasDetailArchive = InferSelectModel<typeof canvasDetailArchive>;
// 画布图片生成任务表(异步队列)
export const canvasImageTask = pgTable("CanvasImageTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
status: varchar("status", { length: 16 })
.notNull()
.default("pending"), // pending | running | succeeded | failed
request: jsonb("request").notNull(),
result: jsonb("result"),
error: text("error"),
attempts: integer("attempts").notNull().default(0),
startedAt: timestamp("startedAt"),
completedAt: timestamp("completedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type CanvasImageTask = InferSelectModel<typeof canvasImageTask>;
export const enhanceProTask = pgTable("EnhanceProTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
messageId: uuid("messageId"),
clientRequestId: varchar("clientRequestId", { length: 64 }),
providerTaskId: varchar("providerTaskId", { length: 128 }).notNull(),
taskKeyId: varchar("taskKeyId", { length: 64 }),
status: varchar("status", { length: 16 })
.notNull()
.default("pending"), // pending | running | finalizing | succeeded | failed
prompt: text("prompt"),
sourceImageUrl: text("sourceImageUrl").notNull(),
request: jsonb("request").notNull(),
result: jsonb("result"),
error: text("error"),
pointsCost: integer("pointsCost").notNull().default(0),
pointsCharged: boolean("pointsCharged").notNull().default(false),
chargedAt: timestamp("chargedAt"),
completedAt: timestamp("completedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type EnhanceProTask = InferSelectModel<typeof enhanceProTask>;
export const agentPsdRenderTask = pgTable("AgentPsdRenderTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
status: varchar("status", { length: 16 })
.notNull()
.default("pending"), // pending | running | succeeded | failed
request: jsonb("request").notNull(),
result: jsonb("result"),
error: text("error"),
attempts: integer("attempts").notNull().default(0),
startedAt: timestamp("startedAt"),
completedAt: timestamp("completedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type AgentPsdRenderTask = InferSelectModel<typeof agentPsdRenderTask>;
// 画布节点表(可选,用于更细粒度的查询)
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' | 'model3d'
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>;
// 3D 资产库记录表(用于模型页资产库持久化)
export const model3dAsset = pgTable("Model3DAsset", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
projectId: uuid("projectId")
.notNull()
.references(() => canvasProject.id, { onDelete: "cascade" }),
taskId: varchar("taskId", { length: 128 }).notNull(),
provider: varchar("provider", { length: 32 }).notNull().default("tripo-3d"),
status: varchar("status", { length: 32 }).notNull().default("queued"),
mode: varchar("mode", { length: 32 }).notNull().default("text"),
prompt: text("prompt").notNull().default(""),
modelVersion: varchar("modelVersion", { length: 64 }).notNull().default(""),
progress: integer("progress"),
modelUrl: text("modelUrl"),
previewImageUrl: text("previewImageUrl"),
error: text("error"),
sourceTaskId: varchar("sourceTaskId", { length: 128 }),
exportFormat: varchar("exportFormat", { length: 32 }),
assetPayload: jsonb("assetPayload").$type<Record<string, unknown> | null>(),
shareEnabled: boolean("shareEnabled").notNull().default(false),
shareToken: varchar("shareToken", { length: 64 }),
sharedAt: timestamp("sharedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type Model3DAsset = InferSelectModel<typeof model3dAsset>;
// 九宫格视频分镜任务主表
export const videoStoryboardTask = pgTable("VideoStoryboardTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
messageId: uuid("messageId").references(() => message.id, {
onDelete: "set null",
}),
providerType: varchar("providerType", { length: 64 })
.notNull()
.default("ruxa-video"),
modelRef: varchar("modelRef", { length: 128 }),
gridType: varchar("gridType", { length: 32 }),
gridRows: integer("gridRows"),
gridCols: integer("gridCols"),
sourceImageUrl: text("sourceImageUrl").notNull(),
isNineGrid: boolean("isNineGrid").notNull().default(false),
detectionConfidence: integer("detectionConfidence").notNull().default(0),
normalizedScript: jsonb("normalizedScript"),
status: varchar("status", { length: 32 }).notNull().default("pending"),
confirmationStatus: varchar("confirmationStatus", { length: 32 })
.notNull()
.default("draft"),
hdImageStageStatus: varchar("hdImageStageStatus", { length: 32 })
.notNull()
.default("pending"),
currentShot: integer("currentShot").notNull().default(0),
totalShots: integer("totalShots").notNull().default(9),
error: text("error"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
startedAt: timestamp("startedAt"),
confirmedAt: timestamp("confirmedAt"),
confirmedByMessageId: uuid("confirmedByMessageId").references(() => message.id, {
onDelete: "set null",
}),
completedAt: timestamp("completedAt"),
});
export type VideoStoryboardTask = InferSelectModel<typeof videoStoryboardTask>;
// 九宫格视频分镜子任务表
export const videoStoryboardShotTask = pgTable("VideoStoryboardShotTask", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
parentTaskId: uuid("parentTaskId")
.notNull()
.references(() => videoStoryboardTask.id, { onDelete: "cascade" }),
shotIndex: integer("shotIndex").notNull(),
tileIndex: integer("tileIndex"),
tileImageUrl: text("tileImageUrl"),
upscaledImageUrl: text("upscaledImageUrl"),
hdImageUrl: text("hdImageUrl"),
hdImagePrompt: text("hdImagePrompt"),
prompt: text("prompt"),
modelId: varchar("modelId", { length: 128 }),
fallbackChain: jsonb("fallbackChain"),
attemptCount: integer("attemptCount").notNull().default(0),
status: varchar("status", { length: 32 }).notNull().default("pending"),
hdImageStatus: varchar("hdImageStatus", { length: 32 }).notNull().default("pending"),
videoStatus: varchar("videoStatus", { length: 32 }).notNull().default("pending"),
userApproved: boolean("userApproved").notNull().default(false),
userApprovedAt: timestamp("userApprovedAt"),
videoUrl: text("videoUrl"),
errorCode: varchar("errorCode", { length: 64 }),
errorMessage: text("errorMessage"),
startedAt: timestamp("startedAt"),
completedAt: timestamp("completedAt"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type VideoStoryboardShotTask = InferSelectModel<typeof videoStoryboardShotTask>;
// 会话工作记忆表(用于连续对话中的图片/资产目标定位)
export const conversationWorkingState = pgTable("ConversationWorkingState", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
chatId: uuid("chatId")
.notNull()
.references(() => chat.id, { onDelete: "cascade" })
.unique(),
projectId: uuid("projectId").references(() => canvasProject.id, {
onDelete: "set null",
}),
currentIntent: varchar("currentIntent", { length: 64 }),
currentSubIntent: varchar("currentSubIntent", { length: 64 }),
currentFocusAssetIds: jsonb("currentFocusAssetIds").$type<string[]>().notNull().default([]),
currentFocusImageUrls: jsonb("currentFocusImageUrls").$type<string[]>().notNull().default([]),
selectedAssetIds: jsonb("selectedAssetIds").$type<string[]>().notNull().default([]),
selectedImageUrls: jsonb("selectedImageUrls").$type<string[]>().notNull().default([]),
lastUploadedAssetIds: jsonb("lastUploadedAssetIds").$type<string[]>().notNull().default([]),
lastUploadedImageUrls: jsonb("lastUploadedImageUrls").$type<string[]>().notNull().default([]),
lastGeneratedAssetIds: jsonb("lastGeneratedAssetIds").$type<string[]>().notNull().default([]),
lastGeneratedImageUrls: jsonb("lastGeneratedImageUrls").$type<string[]>().notNull().default([]),
lastReferencedAssetIds: jsonb("lastReferencedAssetIds").$type<string[]>().notNull().default([]),
lastReferencedImageUrls: jsonb("lastReferencedImageUrls").$type<string[]>().notNull().default([]),
lastResolvedTargetAssetIds: jsonb("lastResolvedTargetAssetIds")
.$type<string[]>()
.notNull()
.default([]),
lastResolvedTargetImageUrls: jsonb("lastResolvedTargetImageUrls")
.$type<string[]>()
.notNull()
.default([]),
activeCanvasNodeIds: jsonb("activeCanvasNodeIds").$type<string[]>().notNull().default([]),
lastCanvasSnapshot: jsonb("lastCanvasSnapshot"),
lastTurnSummary: jsonb("lastTurnSummary"),
lastMessageId: uuid("lastMessageId"),
turnCount: integer("turnCount").notNull().default(0),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type ConversationWorkingState = InferSelectModel<typeof conversationWorkingState>;
export const aiCadWorkspace = pgTable("AiCadWorkspace", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
name: varchar("name", { length: 128 }).notNull().default("AI-CAD 工作台"),
prompt: text("prompt").notNull().default(""),
generationProvider: varchar("generationProvider", {
length: 32,
enum: ["kittycad-cad", "tripo-3d"],
})
.notNull()
.default("kittycad-cad"),
modelVersion: varchar("modelVersion", { length: 128 })
.notNull()
.default("Turbo-v1.0-20250506"),
viewerSettings: jsonb("viewerSettings").notNull(),
messages: jsonb("messages").$type<Record<string, unknown>[]>().notNull().default([]),
versions: jsonb("versions").$type<Record<string, unknown>[]>().notNull().default([]),
activeVersionId: varchar("activeVersionId", { length: 64 }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type AiCadWorkspace = InferSelectModel<typeof aiCadWorkspace>;
export type FlowBoardSnapshotData = {
children: Record<string, unknown>[];
viewport?: Record<string, unknown> | null;
theme?: Record<string, unknown> | null;
updatedAt?: string | null;
};
export const flowBoardDraft = pgTable("FlowBoardDraft", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
title: varchar("title", { length: 256 })
.notNull()
.default("未命名流程白板"),
snapshot: jsonb("snapshot")
.$type<FlowBoardSnapshotData>()
.notNull()
.default({ children: [] }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type FlowBoardDraft = InferSelectModel<typeof flowBoardDraft>;
// 每轮消息的目标资产/图片定位审计表
export const messageTargetResolution = pgTable("MessageTargetResolution", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
chatId: uuid("chatId")
.notNull()
.references(() => chat.id, { onDelete: "cascade" }),
messageId: uuid("messageId").references(() => message.id, {
onDelete: "set null",
}),
projectId: uuid("projectId").references(() => canvasProject.id, {
onDelete: "set null",
}),
rawText: text("rawText").notNull(),
uploadedAssetIds: jsonb("uploadedAssetIds").$type<string[]>().notNull().default([]),
uploadedImageUrls: jsonb("uploadedImageUrls").$type<string[]>().notNull().default([]),
selectedAssetIds: jsonb("selectedAssetIds").$type<string[]>().notNull().default([]),
selectedImageUrls: jsonb("selectedImageUrls").$type<string[]>().notNull().default([]),
candidateAssetIds: jsonb("candidateAssetIds").$type<string[]>().notNull().default([]),
candidateImageUrls: jsonb("candidateImageUrls").$type<string[]>().notNull().default([]),
resolvedAssetIds: jsonb("resolvedAssetIds").$type<string[]>().notNull().default([]),
resolvedImageUrls: jsonb("resolvedImageUrls").$type<string[]>().notNull().default([]),
resolutionMode: varchar("resolutionMode", { length: 64 }).notNull(),
confidence: integer("confidence").notNull().default(0),
needsClarification: boolean("needsClarification").notNull().default(false),
reasoning: jsonb("reasoning"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type MessageTargetResolution = InferSelectModel<typeof messageTargetResolution>;
// 统一媒体资产表(上传图 / 生成图 / 视频 / 3D 预览的统一索引)
export const mediaAsset = pgTable("MediaAsset", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
projectId: uuid("projectId").references(() => canvasProject.id, {
onDelete: "cascade",
}),
mediaType: varchar("mediaType", { length: 32 }).notNull(),
sourceType: varchar("sourceType", { length: 32 }).notNull(),
status: varchar("status", { length: 32 }).notNull().default("ready"),
originalUrl: text("originalUrl").notNull(),
normalizedUrl: text("normalizedUrl").notNull(),
previewUrl: text("previewUrl"),
thumbnailUrl: text("thumbnailUrl"),
mimeType: varchar("mimeType", { length: 128 }),
width: integer("width"),
height: integer("height"),
durationMs: integer("durationMs"),
displayName: varchar("displayName", { length: 256 }),
prompt: text("prompt"),
modelId: varchar("modelId", { length: 128 }),
sourceMessageId: uuid("sourceMessageId").references(() => message.id, {
onDelete: "set null",
}),
sourceTable: varchar("sourceTable", { length: 64 }),
sourceRowId: varchar("sourceRowId", { length: 128 }),
sourceNodeId: varchar("sourceNodeId", { length: 128 }),
rootAssetId: uuid("rootAssetId"),
primaryParentAssetId: uuid("primaryParentAssetId"),
summary: jsonb("summary"),
metadata: jsonb("metadata"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type MediaAsset = InferSelectModel<typeof mediaAsset>;
// 媒体资产关系表(版本链 / 画布节点绑定 / 来源关系)
export const mediaAssetRelation = pgTable("MediaAssetRelation", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
projectId: uuid("projectId").references(() => canvasProject.id, {
onDelete: "cascade",
}),
fromAssetId: uuid("fromAssetId").notNull().references(() => mediaAsset.id, {
onDelete: "cascade",
}),
toAssetId: uuid("toAssetId").notNull().references(() => mediaAsset.id, {
onDelete: "cascade",
}),
relationType: varchar("relationType", { length: 64 }).notNull(),
sourceMessageId: uuid("sourceMessageId").references(() => message.id, {
onDelete: "set null",
}),
sourceNodeId: varchar("sourceNodeId", { length: 128 }),
confidence: integer("confidence").notNull().default(100),
metadata: jsonb("metadata"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type MediaAssetRelation = InferSelectModel<typeof mediaAssetRelation>;
// 资产记忆快照表(视觉摘要 / 主体标签 / 场景标签等可复用缓存)
export const assetMemorySnapshot = pgTable("AssetMemorySnapshot", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
assetId: uuid("assetId").references(() => mediaAsset.id, {
onDelete: "cascade",
}),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "cascade" }),
projectId: uuid("projectId").references(() => canvasProject.id, {
onDelete: "cascade",
}),
snapshotType: varchar("snapshotType", { length: 32 })
.notNull()
.default("visual_summary"),
status: varchar("status", { length: 32 }).notNull().default("ready"),
normalizedUrl: text("normalizedUrl").notNull(),
summaryText: text("summaryText"),
subjectTags: jsonb("subjectTags").$type<string[]>().notNull().default([]),
sceneTags: jsonb("sceneTags").$type<string[]>().notNull().default([]),
styleTags: jsonb("styleTags").$type<string[]>().notNull().default([]),
lightingTags: jsonb("lightingTags").$type<string[]>().notNull().default([]),
confidence: integer("confidence").notNull().default(0),
sourceModelId: varchar("sourceModelId", { length: 128 }),
metadata: jsonb("metadata"),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
});
export type AssetMemorySnapshot = InferSelectModel<typeof assetMemorySnapshot>;
// 风格化参考图绑定表
export const agentPresetReferenceAsset = pgTable("AgentPresetReferenceAsset", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
presetId: uuid("presetId")
.notNull()
.references(() => agentPreset.id, { onDelete: "cascade" }),
slotId: uuid("slotId").references(() => agentPresetSlot.id, {
onDelete: "cascade",
}),
assetId: uuid("assetId")
.notNull()
.references(() => mediaAsset.id, { onDelete: "cascade" }),
sortOrder: integer("sortOrder").notNull().default(0),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type AgentPresetReferenceAsset = InferSelectModel<typeof agentPresetReferenceAsset>;
// 风格化执行快照表
export const agentPresetExecutionSnapshot = pgTable("AgentPresetExecutionSnapshot", {
id: uuid("id").primaryKey().notNull().defaultRandom(),
presetId: uuid("presetId")
.notNull()
.references(() => agentPreset.id, { onDelete: "cascade" }),
projectId: uuid("projectId").references(() => canvasProject.id, {
onDelete: "set null",
}),
chatId: uuid("chatId").references(() => chat.id, { onDelete: "set null" }),
messageId: uuid("messageId").references(() => message.id, { onDelete: "set null" }),
userId: uuid("userId")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
presetName: varchar("presetName", { length: 128 }).notNull(),
presetType: varchar("presetType", { enum: ["image", "video", "text"] }).notNull(),
secondaryType: varchar("secondaryType", { length: 128 }),
visibility: varchar("visibility", { enum: ["public", "private"] }).notNull(),
categoryId: uuid("categoryId").references(() => agentPresetCategory.id, {
onDelete: "set null",
}),
categoryName: varchar("categoryName", { length: 64 }),
promptTemplate: text("promptTemplate").notNull().default(""),
defaultModelId: uuid("defaultModelId").references(() => aiModel.id, {
onDelete: "set null",
}),
resolvedModelId: uuid("resolvedModelId").references(() => aiModel.id, {
onDelete: "set null",
}),
defaultParams: jsonb("defaultParams").$type<Record<string, any>>(),
slotCount: integer("slotCount").notNull().default(1),
requiredUploadCount: integer("requiredUploadCount").notNull().default(1),
slots: jsonb("slots")
.$type<
Array<{
slotIndex: number;
title?: string | null;
promptTemplate: string;
defaultParams?: Record<string, any> | null;
requireUpload?: boolean;
uploadLabel?: string | null;
referenceAssetIds: string[];
referenceImageUrls: string[];
}>
>()
.notNull()
.default([]),
referenceAssetIds: jsonb("referenceAssetIds").$type<string[]>().notNull().default([]),
referenceImageUrls: jsonb("referenceImageUrls").$type<string[]>().notNull().default([]),
userInputText: text("userInputText"),
requestPayload: jsonb("requestPayload").$type<Record<string, any>>(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
});
export type AgentPresetExecutionSnapshot = InferSelectModel<
typeof agentPresetExecutionSnapshot
>;