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().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().notNull(), settings: text('settings', { mode: 'json' }).$type().notNull(), memory: text('memory', { mode: 'json' }).$type().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 | 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().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().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().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().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().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().notNull(), status: text('status').$type().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)])