3c9b65d982
- Nuxt 4 + Nuxt UI 4 + AI SDK 7 + Drizzle/SQLite - 主编规划大纲与章节简报、选派作家;作家流式写入章节文件并实时推送前端 - 逐章确认模式(默认)与自动模式;摘要与故事记忆 - 酒馆角色卡(PNG/JSON,V1-V3)与世界书导入 - 自定义最高优先级系统提示词、审稿/修改/多版本、任务日志与提示词检视 - 模拟 LLM 服务与端到端测试脚本 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
143 lines
6.8 KiB
TypeScript
143 lines
6.8 KiB
TypeScript
import { sqliteTable, text, integer, real, index } from 'drizzle-orm/sqlite-core'
|
||
import type {
|
||
ProviderType, ProjectRequirements, ProjectSettings, ProjectMemory,
|
||
AgentRole, ChapterStatus, VersionStatus, JobType, JobStatus, LoreEntrySource
|
||
} from '../../shared/types'
|
||
|
||
const id = () => text('id').primaryKey()
|
||
const createdAt = () => integer('created_at', { mode: 'timestamp_ms' }).notNull().$defaultFn(() => new Date())
|
||
const updatedAt = () => integer('updated_at', { mode: 'timestamp_ms' }).notNull().$defaultFn(() => new Date()).$onUpdateFn(() => new Date())
|
||
|
||
/** API 提供方(OpenAI 兼容 / Anthropic / Google) */
|
||
export const providers = sqliteTable('providers', {
|
||
id: id(),
|
||
name: text('name').notNull(),
|
||
type: text('type').$type<ProviderType>().notNull(),
|
||
baseUrl: text('base_url').notNull().default(''),
|
||
apiKeyEnc: text('api_key_enc').notNull().default(''),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
})
|
||
|
||
/** 模型预设:某个提供方下的具体模型 + 采样参数 */
|
||
export const models = sqliteTable('models', {
|
||
id: id(),
|
||
providerId: text('provider_id').notNull().references(() => providers.id, { onDelete: 'cascade' }),
|
||
label: text('label').notNull(),
|
||
modelId: text('model_id').notNull(),
|
||
temperature: real('temperature'),
|
||
topP: real('top_p'),
|
||
maxOutputTokens: integer('max_output_tokens'),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
})
|
||
|
||
export const projects = sqliteTable('projects', {
|
||
id: id(),
|
||
title: text('title').notNull(),
|
||
description: text('description').notNull().default(''),
|
||
worldSetting: text('world_setting').notNull().default(''),
|
||
storyOutline: text('story_outline').notNull().default(''),
|
||
requirements: text('requirements', { mode: 'json' }).$type<ProjectRequirements>().notNull(),
|
||
settings: text('settings', { mode: 'json' }).$type<ProjectSettings>().notNull(),
|
||
memory: text('memory', { mode: 'json' }).$type<ProjectMemory>().notNull(),
|
||
userName: text('user_name').notNull().default(''),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
})
|
||
|
||
export const characters = sqliteTable('characters', {
|
||
id: id(),
|
||
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||
name: text('name').notNull(),
|
||
identity: text('identity').notNull().default(''),
|
||
description: text('description').notNull().default(''),
|
||
personality: text('personality').notNull().default(''),
|
||
speechExamples: text('speech_examples').notNull().default(''),
|
||
notes: text('notes').notNull().default(''),
|
||
avatar: text('avatar').notNull().default(''),
|
||
sourceCard: text('source_card', { mode: 'json' }).$type<Record<string, unknown> | null>(),
|
||
sortOrder: integer('sort_order').notNull().default(0),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
}, t => [index('characters_project_idx').on(t.projectId)])
|
||
|
||
/** 世界书条目:常驻条目每次注入,关键词条目在章节简报/大纲命中时注入 */
|
||
export const loreEntries = sqliteTable('lore_entries', {
|
||
id: id(),
|
||
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||
title: text('title').notNull().default(''),
|
||
keys: text('keys', { mode: 'json' }).$type<string[]>().notNull(),
|
||
content: text('content').notNull().default(''),
|
||
constant: integer('constant', { mode: 'boolean' }).notNull().default(false),
|
||
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
|
||
source: text('source').$type<LoreEntrySource>().notNull().default('user'),
|
||
sortOrder: integer('sort_order').notNull().default(100),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
}, t => [index('lore_project_idx').on(t.projectId)])
|
||
|
||
/** AI 成员:主编 / 作家 / 摘要员 */
|
||
export const agents = sqliteTable('agents', {
|
||
id: id(),
|
||
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||
role: text('role').$type<AgentRole>().notNull(),
|
||
name: text('name').notNull(),
|
||
modelId: text('model_id').references(() => models.id, { onDelete: 'set null' }),
|
||
systemPrompt: text('system_prompt').notNull().default(''),
|
||
styleNotes: text('style_notes').notNull().default(''),
|
||
createdBy: text('created_by').$type<'user' | 'editor'>().notNull().default('user'),
|
||
enabled: integer('enabled', { mode: 'boolean' }).notNull().default(true),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
}, t => [index('agents_project_idx').on(t.projectId)])
|
||
|
||
export const chapters = sqliteTable('chapters', {
|
||
id: id(),
|
||
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||
index: integer('index').notNull(),
|
||
title: text('title').notNull().default(''),
|
||
synopsis: text('synopsis').notNull().default(''),
|
||
brief: text('brief').notNull().default(''),
|
||
writerAgentId: text('writer_agent_id').references(() => agents.id, { onDelete: 'set null' }),
|
||
writerLocked: integer('writer_locked', { mode: 'boolean' }).notNull().default(false),
|
||
editorNotes: text('editor_notes').notNull().default(''),
|
||
reviewNotes: text('review_notes').notNull().default(''),
|
||
status: text('status').$type<ChapterStatus>().notNull().default('planned'),
|
||
activeVersionId: text('active_version_id'),
|
||
summary: text('summary').notNull().default(''),
|
||
createdAt: createdAt(),
|
||
updatedAt: updatedAt()
|
||
}, t => [index('chapters_project_idx').on(t.projectId, t.index)])
|
||
|
||
export const chapterVersions = sqliteTable('chapter_versions', {
|
||
id: id(),
|
||
chapterId: text('chapter_id').notNull().references(() => chapters.id, { onDelete: 'cascade' }),
|
||
version: integer('version').notNull(),
|
||
filePath: text('file_path').notNull(),
|
||
wordCount: integer('word_count').notNull().default(0),
|
||
writerAgentId: text('writer_agent_id'),
|
||
writerName: text('writer_name').notNull().default(''),
|
||
modelLabel: text('model_label').notNull().default(''),
|
||
feedback: text('feedback').notNull().default(''),
|
||
status: text('status').$type<VersionStatus>().notNull().default('writing'),
|
||
createdAt: createdAt()
|
||
}, t => [index('versions_chapter_idx').on(t.chapterId)])
|
||
|
||
export const jobs = sqliteTable('jobs', {
|
||
id: id(),
|
||
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||
chapterId: text('chapter_id'),
|
||
type: text('type').$type<JobType>().notNull(),
|
||
status: text('status').$type<JobStatus>().notNull().default('running'),
|
||
agentName: text('agent_name').notNull().default(''),
|
||
modelLabel: text('model_label').notNull().default(''),
|
||
inputTokens: integer('input_tokens').notNull().default(0),
|
||
outputTokens: integer('output_tokens').notNull().default(0),
|
||
error: text('error').notNull().default(''),
|
||
log: text('log').notNull().default(''),
|
||
prompt: text('prompt', { mode: 'json' }).$type<{ system: string, prompt: string } | null>(),
|
||
createdAt: createdAt(),
|
||
finishedAt: integer('finished_at', { mode: 'timestamp_ms' })
|
||
}, t => [index('jobs_project_idx').on(t.projectId, t.createdAt)])
|