生成剧本:技术逻辑梳理
工作流第二步「生成剧本」——在事件提取完成后,依次调用大模型生成故事骨架 → 改编策略 → 逐集剧本,写入 SQLite,供后续「生成资产 / 分镜 / 视频」使用。
1. 整体链路
上一节点 novel_chapters.event(每章 pipe 事件摘要)
│
▼
用户发送「开始生成剧本」(或点击快捷指令)
│
▼
runScriptPipeline(orchestrator.ts)
│
├─► 阶段 1:故事骨架(1 次 LLM)
│ └─ 写入 script_work_data.story_skeleton
│
├─► 阶段 2:改编策略(1 次 LLM)
│ └─ 写入 script_work_data.adaptation_strategy
│
└─► 阶段 3:逐集剧本(每章 1 次 LLM,顺序执行)
└─ 写入 scripts(name / content / script_state)
│
▼
右侧工作台展示骨架、策略、逐集剧本表 → 下游资产 / 分镜
分步说明
- 前置:全部章节
event_state = 1且event非空(事件提取完成)。 - 触发:左侧编剧助手聊天发送「开始生成剧本」→
runScriptPipeline。 - 三阶段流水线:骨架与策略各调 1 次 LLM;剧本按章 逐集顺序 调 LLM(默认 1 章 = 1 集)。
- 落库:中间产物进
script_work_data,逐集剧本进scripts;成功后工作流可推进到下一节点。
一句话(含 LLM 输入):
本节点共 2 + N 次 LLM 调用(N = 章节数)。每次 POST /chat/completions,messages 均为 [system Prompt, user 拼接块];max_tokens: 8192,stream: false。user 块的核心输入来自:
| 阶段 | system Prompt | user 主要数据来源 |
|---|---|---|
| 故事骨架 | STORY_SKELETON_PROMPT | 项目配置 + 导演手册 + 全部章节 event |
| 改编策略 | ADAPTATION_STRATEGY_PROMPT | 项目配置 + 导演手册 + 故事骨架 + 全部章节 event |
| 逐集剧本 | EPISODE_SCRIPT_PROMPT | 项目配置 + 导演手册 + 骨架 + 策略 + 本章 event + 本章原文 + 上一集剧本 |
2. 前置条件与数据依赖
2.1 入口校验
UI:src/components/AiScriptStep.tsx
未完成事件提取时只显示提示,不展示工作台。
流水线:runScriptPipeline 开头调用 isEventExtractionComplete(chapters),否则抛错「请先完成全部章节的事件提取」。
2.2 上游输入(来自「提取事件」)
| 字段 | 表 | 用途 |
|---|---|---|
chapterIndex | novel_chapters | 与 episodeIndex 一一对应(默认 1 章 = 1 集) |
chapter | novel_chapters | 集标题、剧本名 {项目名} EP{NN}:{章名} |
chapterData | novel_chapters | 逐集剧本 user 消息中的「本章原文」 |
event | novel_chapters | pipe 事件行(含涉及角色、涉及场景等),写入「章节事件表」 |
事件在 user 消息中的格式(formatChapterEvents):
第1章《惊梦重生,回到十八年前》事件:|第1章 … | 林远、王雪 | 大学男生宿舍 | … |
2.3 项目侧输入
| 数据 | 来源 | 用途 |
|---|---|---|
project.name / novelType / intro / artStyle / aspectRatio | 创建项目时填写 | 各阶段 user 的「项目配置」块 |
project.directorManual | 项目选定的导演手册 Skill ID | 加载手册正文,注入「导演手册(必须严格遵循)」块 |
config.baseUrl / apiKey | 设置页 | LLM 鉴权与地址 |
model | 界面所选模型 | 请求体 model 字段 |
3. 阶段零:触发与编排(UI + Hook)
核心文件:
src/components/AiScriptStep.tsx— 左聊天 + 右工作台布局src/hooks/useScriptAgentChat.ts— 发送消息、启动流水线、进度展示src/agents/workflowAgent/scriptGeneration/orchestrator.ts— 三阶段调度
用户发送「开始生成剧本」(或等价快捷指令)时:
shouldStartScriptPipeline识别意图 → 调用runScriptPipelineonProgress更新 UI(骨架 / 策略 / 逐集进度)onStageComplete每完成一阶段即刷新右侧工作台(骨架、策略可先看到)- 全部完成后
onWorkflowChange推进工作流节点
聊天里还可走 编剧协调 Agent(流式对话 + 修改计划),那是修订分支,见 §8。
4. 阶段一:故事骨架(1 次 LLM)
Agent:src/agents/workflowAgent/scriptGeneration/storySkeletonAgent.ts
Prompt:src/prompts/workflowAgent/scriptGeneration/storySkeleton.ts → STORY_SKELETON_PROMPT
4.1 user 消息拼装(baseUserContent)
按顺序拼接 Markdown 文本块:
- 项目配置 —
buildProjectConfigBlock(project) - 导演手册 —
buildDirectorSkillBlock(directorSkillContent)(有则注入) - 总章节数 —
ctx.chapters.length(默认 1 章 = 1 集) - 章节事件表 —
formatChapterEvents(chapters)(全部章的event) - 任务说明 — 要求先 200–300 字思路,再 Markdown 输出 6 个
##章节
4.2 最终 HTTP Body 结构
{
"model": "用户选择的模型名",
"stream": false,
"max_tokens": 8192,
"messages": [
{ "role": "system", "content": "<STORY_SKELETON_PROMPT>" },
{ "role": "user", "content": "<项目配置>\n<导演手册>\n总章节数:3\n\n## 章节事件表\n第1章《…》事件:|…|\n第2章…\n\n请基于以上事件表构建故事骨架…" }
]
}
API Trace 标签:script-skeleton
4.3 期望输出与校验
- 先 200–300 字思路阐述,再 Markdown 正文
- 必须包含 6 个二级标题:
故事核、隐线、三幕结构、分集决策、全局删减决策表、付费卡点设计 parseStorySkeletonOutput+getMissingStorySkeletonSections校验;最多重试 3 次
4.4 落库
setScriptWorkData(projectId, { storySkeleton }) → SQLite script_work_data.story_skeleton
5. 阶段二:改编策略(1 次 LLM)
Agent:adaptationStrategyAgent.ts
Prompt:adaptationStrategy.ts → ADAPTATION_STRATEGY_PROMPT
5.1 user 消息拼装
在骨架阶段基础上,user 块增加:
| 块 | 来源 |
|---|---|
| 故事骨架 | 上一阶段产出 / script_work_data.story_skeleton |
| 章节事件表 | 仍带全部章 event(与骨架阶段相同) |
5.2 最终 HTTP Body 结构
{
"model": "…",
"stream": false,
"max_tokens": 8192,
"messages": [
{ "role": "system", "content": "<ADAPTATION_STRATEGY_PROMPT>" },
{ "role": "user", "content": "<项目配置>\n<导演手册>\n\n## 故事骨架\n{骨架全文}\n\n## 章节事件表\n{全部 event}\n\n请基于故事骨架制定改编策略…" }
]
}
API Trace 标签:script-adaptation
5.3 期望输出与校验
- Markdown,5 个二级标题:
改编基调、人物改编、场景改编、分集脚本指引、衔接规则 - 最多重试 3 次 →
setScriptWorkData({ adaptationStrategy })
6. 阶段三:逐集剧本(每章 1 次 LLM,顺序执行)
Agent:episodeScriptAgent.ts
Prompt:episodeScript.ts → EPISODE_SCRIPT_PROMPT
调度:orchestrator.writeEpisodeScript — 无并发,按 chapterIndex 升序逐集生成
6.1 单集 user 消息拼装(baseUserContent)
| 块 | 来源 |
|---|---|
| 项目配置 | CreationProject |
| 导演手册 | Skill 正文 |
| 当前任务 | episodeIndex、章名 {chapter.chapter} |
| 剧本名称 | {项目名} EP{NN}:{章名}(XML name 属性须一致) |
| 故事骨架 | workData.storySkeleton |
| 改编策略 | workData.adaptationStrategy |
| 本章事件 | formatChapterEventsByIndexes(chapters, [episodeIndex]) — 仅本章 event |
| 本章原文 | chapter.chapterData |
| 上一集剧本 | scripts 中 episodeIndex - 1 的 content(第 1 集无) |
第 2 集及以后会带上第 1 集已生成的剧本正文,保证集间衔接。
6.2 最终 HTTP Body 结构(以第 1 集为例)
{
"model": "…",
"stream": false,
"max_tokens": 8192,
"messages": [
{ "role": "system", "content": "<EPISODE_SCRIPT_PROMPT>" },
{
"role": "user",
"content": "## 项目配置\n作品名称:Demo1\n…\n\n当前任务:编写第 1 集剧本(对应第 1 章《惊梦重生,回到十八年前》)\n剧本名称(name 属性):Demo1 EP01:惊梦重生,回到十八年前\n\n## 故事骨架\n{骨架全文}\n\n## 改编策略\n{策略全文}\n\n## 本章事件\n第1章《…》事件:|…|\n\n## 本章原文\n{chapterData 全文}\n\n请编写本集完整剧本,使用 <scriptItem name=\"Demo1 EP01:…\"> 包裹正文。"
}
]
}
API Trace 标签:script-episode
6.3 期望输出与解析
模型应先输出思路阐述,再用 XML 包裹剧本:
<scriptItem name="Demo1 EP01:惊梦重生,回到十八年前">
# Demo1 EP01:惊梦重生,回到十八年前
## 剧情梗概
…
## 场景1 · 内 · 大学男生宿舍 · 日
△ …
林远:…
</scriptItem>
解析链:
stripThink(raw)
→ parseEpisodeScriptOutput(text, expectedName)
→ extractScriptItems(XML)或 extractScriptMarkdownFallback
→ { name, content }
→ upsertScript → scripts 表
最多重试 3 次;失败则 script_state = 2,errorReason 存原因,不阻断后续集(当前实现是逐集 try/catch,失败集标记错误后继续下一集)。
6.4 落库与副作用
upsertScript(Rust script.rs)写入:
| 字段 | 含义 |
|---|---|
episode_index | = chapter.chapterIndex |
name | 剧本名称 |
content | Markdown 剧本正文 |
script_state | 1 成功 / 2 失败 |
副作用:maybe_advance_after_script 检查是否全部集成功 → 推进工作流;sync_episode_asset_bindings 扫描剧本绑定资产。
7. 调用大模型汇总表
| 项目 | 值 |
|---|---|
| HTTP | POST {baseUrl}/chat/completions |
| 鉴权 | Authorization: Bearer {apiKey} |
| 调用方式 | chatCompletion(src/services/chat.ts) |
| 总次数 | 2 + N(N = 章节数;修订 / 重试会额外增加) |
| 并发 | 骨架、策略、各集均为 串行(逐集剧本 for 循环) |
| max_tokens | 8192(三阶段相同) |
三阶段 LLM 输入对比
┌─────────────┬─────────────┬─────────────┐
│ 故事骨架 │ 改编策略 │ 逐集剧本 │
├───────────────────┼─────────────┼─────────────┼─────────────┤
│ 项目配置 │ ✓ │ ✓ │ ✓ │
│ 导演手册 │ ✓ │ ✓ │ ✓ │
│ 全部章节 event │ ✓ │ ✓ │ │
│ 本章 event │ │ │ ✓ │
│ 故事骨架 │ │ ✓ │ ✓ │
│ 改编策略 │ │ │ ✓ │
│ 本章原文 │ │ │ ✓ │
│ 上一集剧本 │ │ │ ✓(≥第2集)│
└───────────────────┴─────────────┴─────────────┴─────────────┘
8. 修订与重试(补充)
除首次「开始生成剧本」外,还有三条常见路径(同一套 Agent,user 块会附加「主创修改意见」等):
| 能力 | 入口 | 核心函数 |
|---|---|---|
| 重试失败集 | 工作台「重试失败集」 | regenerateFailedEpisodes — 只重跑 script_state = 2 的集 |
| 聊天修订 | 编剧助手对话 + 修改计划卡片 | applyRevisionPlan — 可按集 / 策略 / 骨架范围修订 |
| 协调对话 | 非「开始生成剧本」的普通聊天 | streamCoordinatorChat — 流式,产出修订计划 JSON |
修订时逐集 Agent 还可附带:previousDraft(当前剧本草稿)、cascadeFollowUp(连锁重生成后续集)。
9. 关键文件索引
| 职责 | 文件 |
|---|---|
| UI 入口、布局 | src/components/AiScriptStep.tsx |
| 聊天、启动流水线 | src/hooks/useScriptAgentChat.ts |
| 三阶段调度 | src/agents/workflowAgent/scriptGeneration/orchestrator.ts |
| 故事骨架 Agent | storySkeletonAgent.ts + prompts/.../storySkeleton.ts |
| 改编策略 Agent | adaptationStrategyAgent.ts + prompts/.../adaptationStrategy.ts |
| 逐集剧本 Agent | episodeScriptAgent.ts + prompts/.../episodeScript.ts |
| user 块拼装工具 | scriptGeneration/tools.ts |
| XML / Markdown 解析 | src/utils/xmlTags.ts |
| HTTP 调 LLM | src/services/chat.ts |
| 剧本持久化 | src/services/script.ts + src-tauri/src/script.rs |
| 事件 pipe 解析(详情页人物/场景标签) | src/utils/parseEventFields.ts |
| 工作台展示 | src/components/ScriptWorkspacePanel.tsx |
10. 设计要点(分享时可强调)
- 强依赖上一节点:剧本流水线不读原始粘贴全文做全局规划,而是读 结构化 event;逐集阶段同时读 本章原文 保证细节。
- 三阶段递进:先全局骨架 → 再改编策略 → 最后逐集落地;后阶段输入包含前阶段全部产出。
- 1 章 1 集:
chapterIndex与episodeIndex对齐;逐集时带入上一集剧本做衔接。 - 导演手册贯穿:三阶段 system + user 均可注入 Skill 正文,优先级高于模型默认习惯。
- 格式硬校验 + 重试:骨架/策略查 Markdown 章节齐全;剧本查
<scriptItem>XML(含 Markdown 兜底)。 - 增量可见:每阶段完成即写库并刷新 UI,不必等全部 N 集结束。
11. 与下游的关系
scripts.content→ 生成资产(从剧本提取人物/场景/道具)、制作分镜、生成视频novel_chapters.event中的人物/场景 → 剧本详情页标签展示(parseEventCharactersAndScenes)- 全部集
script_state = 1后工作流进入「生成资产」等后续节点