Skip to content

feat(editor): per-scene AI polish with mode picker + undo - #62

Merged
shanchuann merged 1 commit into
mainfrom
feat/scene-ai-polish
Jun 7, 2026
Merged

shanchuann merged 1 commit into
mainfrom
feat/scene-ai-polish

Conversation

@shanchuann

Copy link
Copy Markdown
Owner

功能描述

`mode_refine` 这个 LLM stage 之前只在改编时跑一次,作者改完想"让 AI 重新润色这一场"没有入口——只能整文档重跑。这版在编辑器场景头部加 AI 整体润色 toolbar:选 `保真度` × `扩写量` → 点 `润色场景` → 跑 `POST /api/v1/refine/scene` → 用结果替换该场景所有 beats。一键"撤销"回退到润色前。

实现思路

后端 `POST /api/v1/refine/scene`:

  • 入参:完整 YAML + scene_id + fidelity + expansion
  • 出参:refined beats 列表 + 模型名
  • 错误码全部走"detail.code + message"结构:
    • 503 `NO_LLM_PROVIDER` 没配 key
    • 400 `INVALID_YAML` YAML 解析失败
    • 404 `SCENE_NOT_FOUND` scene_id 找不到
    • 422 `EMPTY_SCENE` beats 数组为空
    • 422 `INVALID_SCENE` 场景字段不合法
    • 502 `REFINE_FAILED` LLM 调用走完了但 `ai_refine_scene_beats` 返回 None(已经在 logs 里写 stage_reject)

实现上:YAML→dict→挑出对应 scene→重建 Scene/Beat/Character pydantic 实例→喂给已有的 `ai_refine_scene_beats`。`Paragraph` 用 scene.summary 做一个 stub(refine prompt 主要用 beats + roster + summary,原始 paragraph 用不到)。

前端:

  • `SceneView` 头部新增 `ScenePolishToolbar`,虚线边框 + 生成色背景区分于普通节拍
  • 两个 ``:保真度 high/medium/low、扩写量 minimal/balanced/rich `{medium, balanced}` 默认模式时按钮禁用 + tooltip 提示"请改一个参数"(这模式是 no-op,后端会拒) 润色返回后整段 beats splice 替换——expansion=rich 可能改变 beat 数量,逐 beat patch 会让 YAML 错位 替换前往 `polishUndoRef` 存一份 yaml 快照;`undoSceneId` mirror 到 state 让渲染时能用 撤销只保留一级(够用),换场景后 undo 按钮自动隐藏 测试方式 后端 `pytest`: 318 passed(原 314 + 4 新增 refine_scene 错误路径) `py -m ruff check`: clean 前端 `tsc -b`: clean `eslint`: 0 新增(1 条 main 已有的历史告警) 测试覆盖(4 条 endpoint): 无 provider → 503 invalid YAML → 400 未知 scene_id → 404 空 beats → 422 人眼验证: 打开任意有 5+ beats 的场景 默认 `保真度·中 / 扩写量·均衡` 按钮应该灰 改成 `扩写量·丰富` → 按钮亮 → 点击 转圈 5-15s 后 beats 整段刷新 撤销按钮出现 → 点击 → 回到改前 依赖与复用声明 无新增第三方依赖。复用: 已有的 `ai_refine_scene_beats` LLM stage(同一个 prompt + validator + reject logging) 已有的 `Scene` / `Beat` / `Character` pydantic 模型 前端 `patchScreenplayYaml` 同款 js-yaml round-trip 思路 PR V 的 `log_stage_reject` 自动覆盖 502 的诊断(`logs/story2script.log` 里 `stage=refine reason=...` 一行一目了然) Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

The mode_refine LLM stage was wired into the pipeline run but invisible
in the editor — once the draft was generated, the author had no way
to ask "redo this scene with low fidelity / rich expansion" without
regenerating the whole document. Exposes a per-scene polish toolbar
that hits a new POST /api/v1/refine/scene endpoint.

Backend: new endpoint deserializes the YAML, finds the scene by id,
reconstructs the Scene / Beat / Character pydantic instances the
existing ai_refine_scene_beats helper expects, and returns the
refined beat list. 503 when no LLM provider is configured, 404 on
unknown scene id, 422 on empty beats, 502 when the LLM call returns
nothing usable (rule_fallback fired). Tests cover the four error
paths without burning quota.

Frontend: a dashed-bordered toolbar at the top of each scene with
two selects (fidelity / expansion) + 润色场景 button + 撤销 button.
The default (medium, balanced) mode disables the button because the
backend would no-op it anyway. A polish snapshots the pre-polish
YAML to a ref for one-level undo so the author can try a mode,
hate it, and revert without losing their other edits.

Beats are spliced back wholesale rather than per-beat patched —
expansion=rich can change the beat count, so a positional patch
would leave the YAML inconsistent. The full splice round-trips
through js-yaml.dump to keep the file structurally clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shanchuann
shanchuann force-pushed the feat/scene-ai-polish branch from d3ef3c1 to 43c3ab8 Compare June 7, 2026 09:28
@shanchuann
shanchuann merged commit c82627d into main Jun 7, 2026
2 checks passed
@shanchuann
shanchuann deleted the feat/scene-ai-polish branch June 7, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant