AI 小说生成工作台:分层流水线 + 故事圣经(一期完整实现)

- 后端:Node/Express + node:sqlite,人物/世界观/分卷/细纲/正文 8 阶段生成流水线(SSE 流式 + zod 校验 + 重试)
- 前端:React 18 + Vite + antd 三栏工作台:流水线引导、章节树、编辑器(快切章节/段落改写)、故事圣经面板
- 写作工艺注入:人物声口/内在冲突/秘密,细纲 POV/情绪曲线/功能/张力,正文专业规则
- 自动回写故事圣经(摘要/设定/人物变化/伏笔/时间线),TXT/EPUB 导出,token 成本日志
This commit is contained in:
Administrator
2026-09-13 20:35:47 +08:00
commit 4073edcc20
49 changed files with 9827 additions and 0 deletions
+135
View File
@@ -0,0 +1,135 @@
import { db } from './connection.js'
export function migrate() {
db.exec(`
CREATE TABLE IF NOT EXISTS llm_config (
id INTEGER PRIMARY KEY CHECK (id = 1),
base_url TEXT NOT NULL,
api_key TEXT NOT NULL DEFAULT '',
model TEXT NOT NULL,
temperature REAL NOT NULL DEFAULT 0.8,
max_tokens INTEGER NOT NULL DEFAULT 12000,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
genre TEXT NOT NULL DEFAULT '',
premise TEXT NOT NULL DEFAULT '',
style_notes TEXT NOT NULL DEFAULT '',
style_sample TEXT NOT NULL DEFAULT '',
target_chapter_words INTEGER NOT NULL DEFAULT 3000,
target_chapter_count INTEGER NOT NULL DEFAULT 30,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS characters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name TEXT NOT NULL,
aliases TEXT NOT NULL DEFAULT '',
role TEXT NOT NULL DEFAULT '配角',
appearance TEXT NOT NULL DEFAULT '',
personality TEXT NOT NULL DEFAULT '',
background TEXT NOT NULL DEFAULT '',
goals TEXT NOT NULL DEFAULT '',
arc_notes TEXT NOT NULL DEFAULT '',
sort INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS world_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
category TEXT NOT NULL DEFAULT '其他',
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
sort INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS volumes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
title TEXT NOT NULL,
summary TEXT NOT NULL DEFAULT '',
sort INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS beats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
volume_id INTEGER REFERENCES volumes(id) ON DELETE SET NULL,
sort INTEGER NOT NULL DEFAULT 0,
title TEXT NOT NULL,
events TEXT NOT NULL DEFAULT '',
involved_characters TEXT NOT NULL DEFAULT '[]',
foreshadow_plan TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS chapters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
beat_id INTEGER REFERENCES beats(id) ON DELETE SET NULL,
volume_id INTEGER REFERENCES volumes(id) ON DELETE SET NULL,
sort INTEGER NOT NULL DEFAULT 0,
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'empty',
summary TEXT NOT NULL DEFAULT '',
word_count INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS foreshadows (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
description TEXT NOT NULL,
planted_chapter_id INTEGER REFERENCES chapters(id) ON DELETE SET NULL,
paid_chapter_id INTEGER REFERENCES chapters(id) ON DELETE SET NULL,
status TEXT NOT NULL DEFAULT 'planted',
notes TEXT NOT NULL DEFAULT '',
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS timeline_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
chapter_id INTEGER REFERENCES chapters(id) ON DELETE SET NULL,
story_time TEXT NOT NULL DEFAULT '',
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
sort INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS generation_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
stage TEXT NOT NULL,
model TEXT NOT NULL DEFAULT '',
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
ok INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL
);
`)
// 增量迁移:为旧库补齐新增列(写作专业字段)
const addColumns: [string, string, string][] = [
['projects', 'narrative_pov', "ALTER TABLE projects ADD COLUMN narrative_pov TEXT NOT NULL DEFAULT ''"],
['projects', 'taboos', "ALTER TABLE projects ADD COLUMN taboos TEXT NOT NULL DEFAULT ''"],
['characters', 'speech_pattern', "ALTER TABLE characters ADD COLUMN speech_pattern TEXT NOT NULL DEFAULT ''"],
['characters', 'inner_conflict', "ALTER TABLE characters ADD COLUMN inner_conflict TEXT NOT NULL DEFAULT ''"],
['characters', 'secret', "ALTER TABLE characters ADD COLUMN secret TEXT NOT NULL DEFAULT ''"],
['beats', 'pov', "ALTER TABLE beats ADD COLUMN pov TEXT NOT NULL DEFAULT ''"],
['beats', 'emotional_arc', "ALTER TABLE beats ADD COLUMN emotional_arc TEXT NOT NULL DEFAULT ''"],
['beats', 'purpose', "ALTER TABLE beats ADD COLUMN purpose TEXT NOT NULL DEFAULT ''"],
['beats', 'tension', 'ALTER TABLE beats ADD COLUMN tension INTEGER NOT NULL DEFAULT 0']
]
for (const [table, col, ddl] of addColumns) {
const cols = db.prepare(`PRAGMA table_info(${table})`).all() as unknown as { name: string }[]
if (!cols.some((c) => c.name === col)) db.exec(ddl)
}
}