diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index 398a2ca..7dd863d 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -55,7 +55,8 @@ export function migrate() { 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 + sort INTEGER NOT NULL DEFAULT 0, + chapter_count INTEGER ); CREATE TABLE IF NOT EXISTS beats ( @@ -126,7 +127,8 @@ export function migrate() { ['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'] + ['beats', 'tension', 'ALTER TABLE beats ADD COLUMN tension INTEGER NOT NULL DEFAULT 0'], + ['volumes', 'chapter_count', 'ALTER TABLE volumes ADD COLUMN chapter_count INTEGER'] ] for (const [table, col, ddl] of addColumns) { const cols = db.prepare(`PRAGMA table_info(${table})`).all() as unknown as { name: string }[] diff --git a/server/src/db/types.ts b/server/src/db/types.ts index d12930e..ca26081 100644 --- a/server/src/db/types.ts +++ b/server/src/db/types.ts @@ -47,6 +47,7 @@ export interface VolumeRow { title: string summary: string sort: number + chapter_count: number | null } export interface BeatRow { diff --git a/server/src/routes/generate.ts b/server/src/routes/generate.ts index 5692537..b31d69f 100644 --- a/server/src/routes/generate.ts +++ b/server/src/routes/generate.ts @@ -202,9 +202,9 @@ generateRouter.post('/generate/beats', (req, res) => { }) .join('\n') - // 本卷目标章数:应用大纲时创建的空章节数;没有章节信息时默认 10 + // 本卷目标章数:优先用大纲规划的每卷章数,其次本卷已有章节数,都没有时默认 10 const existing = bundle.chapters.filter((c: ChapterRow) => c.volume_id === volume.id).length - const chapterCount = existing > 0 ? existing : 10 + const chapterCount = volume.chapter_count && volume.chapter_count > 0 ? volume.chapter_count : existing > 0 ? existing : 10 const messages = beatsMessages( toProjectLike(bundle.project), { title: volume.title, summary: volume.summary, chapter_count: chapterCount }, @@ -393,7 +393,13 @@ generateRouter.post('/generate/apply/volumes', (req, res) => { db.prepare('DELETE FROM beats WHERE project_id = ?').run(projectId) db.prepare('DELETE FROM volumes WHERE project_id = ?').run(projectId) items.forEach((v, i) => { - db.prepare('INSERT INTO volumes (project_id, title, summary, sort) VALUES (?,?,?,?)').run(projectId, v.title, v.summary, i + 1) + db.prepare('INSERT INTO volumes (project_id, title, summary, sort, chapter_count) VALUES (?,?,?,?,?)').run( + projectId, + v.title, + v.summary, + i + 1, + v.chapter_count ?? null + ) }) db.exec('COMMIT') } catch (e) { diff --git a/server/src/routes/outline.ts b/server/src/routes/outline.ts index bf20d76..9a38ed3 100644 --- a/server/src/routes/outline.ts +++ b/server/src/routes/outline.ts @@ -9,7 +9,8 @@ export const outlineRouter = Router() const volumeInput = z.object({ title: z.string().min(1).max(100), summary: z.string().max(4000).default(''), - sort: z.number().int().default(0) + sort: z.number().int().default(0), + chapter_count: z.number().int().min(1).max(2000).nullable().optional() }) outlineRouter.post('/projects/:projectId/volumes', (req, res) => { @@ -17,8 +18,8 @@ outlineRouter.post('/projects/:projectId/volumes', (req, res) => { const parsed = volumeInput.safeParse(req.body) if (!parsed.success) return res.status(400).json({ error: parsed.error.issues[0].message }) const info = db - .prepare('INSERT INTO volumes (project_id, title, summary, sort) VALUES (?,?,?,?)') - .run(projectId, parsed.data.title, parsed.data.summary, parsed.data.sort) + .prepare('INSERT INTO volumes (project_id, title, summary, sort, chapter_count) VALUES (?,?,?,?,?)') + .run(projectId, parsed.data.title, parsed.data.summary, parsed.data.sort, parsed.data.chapter_count ?? null) res.json(db.prepare('SELECT * FROM volumes WHERE id = ?').get(info.lastInsertRowid)) }) diff --git a/web/src/api/types.ts b/web/src/api/types.ts index 45cf1fa..fbe880a 100644 --- a/web/src/api/types.ts +++ b/web/src/api/types.ts @@ -48,6 +48,7 @@ export interface Volume { title: string summary: string sort: number + chapter_count: number | null } export interface Beat { diff --git a/web/src/components/OutlineView.tsx b/web/src/components/OutlineView.tsx index 3ba1e13..57a9b65 100644 --- a/web/src/components/OutlineView.tsx +++ b/web/src/components/OutlineView.tsx @@ -48,8 +48,19 @@ export default function OutlineView({ saveVolume(v.id, { title: t })} placeholder="卷名" bordered={false} style={{ fontWeight: 700, fontSize: 15 }} /> saveVolume(v.id, { summary: s })} multiline minRows={2} placeholder="本卷大纲:核心冲突、关键事件、结尾钩子" style={{ color: '#555' }} /> -
- 本卷 {bundle.chapters.filter((c) => c.volume_id === v.id).length} 章(生成细纲后自动创建) +
+ 本卷已有 {bundle.chapters.filter((c) => c.volume_id === v.id).length} 章 · 计划章数 + { + const n = parseInt(t, 10) + saveVolume(v.id, { chapter_count: Number.isFinite(n) && n > 0 ? n : null }) + }} + placeholder="自动" + size="small" + style={{ width: 64 }} + /> + (生成细纲时按此数量出章;留空则按已有章节数,都没有时默认 10)
))