基于 AI Agent 的童话编剧与绘本生成器(七)出图体验优化与绘本 PDF 导出
一、imaging 按页进度:告别 80% 黑盒
第六篇之前,imaging 阶段的进度长时间卡在 80%,前端只显示「正在绘制绘本插图…」。8 页 × Qwen + VL 重试,用户等 10~20 分钟完全不知道卡在第几页。
1.1 后端:stage_message + 按页推进
在 generation_tasks 表中新增 stage_message 字段。每画完一页,Job 就更新进度和文案:
# job_sqlite.py
def update_imaging_page(self, *, job_id, page_index, total_pages):
progress = 80 + int(19 * page_index / total_pages) # 80% → 99%
message = f"正在绘制第 {page_index}/{total_pages} 页插图"
self.update_stage(..., stage_message=message)
image_generator.py 的出图循环里增加回调:
def render_story_images_from_payloads(story_id, page_payloads, *, on_page_progress=None):
for payload in page_payloads:
page_num = int(payload["page_num"])
if on_page_progress:
on_page_progress(page_num, total)
result = svc.generate_page(gen_payload)
job_orchestrator 把回调接入 SQLite:
def on_page(page_index, total_pages):
self._jobs.update_imaging_page(job_id=job_id, page_index=page_index, total_pages=total_pages)
render_story_images_from_payloads(story_id, page_payloads, on_page_progress=on_page)
1.2 前端:轮询 + 文案
create.html 在 imaging 阶段优先读取 job.stage_message:
statusText.textContent = stageText(job.status, job.progress, job.stage_message);
// 例如:正在绘制第 3/8 页插图(91%)
二、双角色出图:配角也要有位置描述
问题在哪
双角色页的常见写法是:
-
主角:
action来自分镜的character_action,信息够具体; -
配角:要么复用同一段
character_action,要么只有「与主角同框互动」这类泛化描述。
结果 Qwen 会优先画信息更满的主角,配角容易被画小、画没、或只留一个模糊轮廓——不是参考图没传,而是 prompt 里配角「该怎么站在画面里」没说清楚。
改法:在 payload 层给每人写「可画的位置 action」
story_characters.py 里做了两件事:
① action_for_character:按角色序号补位置描述
-
主角:沿用分镜的
character_action -
配角:如果原文没写「与…一起」,则补「与{主角}同框互动」
-
双角色页还会带上左右侧信息(见下)
② apply_dual_character_layout:双角色页强制 left/right
_DUAL_REF_SLOTS = ("left character", "right character")
def apply_dual_character_layout(characters):
if len(characters) != 2:
return characters
for idx, item in enumerate(characters):
item["slot"] = _DUAL_REF_SLOTS[idx] # ref 顺序 = 左 = 主角,右 = 配角
side = "画面左侧" if idx == 0 else "画面右侧"
if side not in item.get("action", ""):
item["action"] = f"{side},{item.get('action') or '自然互动'}"
return characters
效果对比:
| 角色 | 优化前 | 优化后 |
|---|---|---|
| 主角 | 小狮子踮脚张望远方 |
画面左侧,小狮子踮脚张望远方,全身入镜 |
| 配角 | 小狮子踮脚张望远方(复制主角) |
画面右侧,与勇敢小狮子同框互动,身体完整可见 |
这样传给 Qwen 的是两个独立、带位置的动作短语,而不是「一个详细 + 一个敷衍」。
generator 侧也同步加了双角色英文构图约束(
dual_layout),作为同一思路的第二层加固,但核心的提示词优化发生在 payload 层。
三、绘本 PDF:左图右文,完整故事按镜对齐
旧版 PDF 只有文字(封面 + 每页 page.text),没有插图,不像绘本。
新版 pdf_export.py 采用横版 A4 跨页布局:
| 部分 | 内容 |
|---|---|
| 封面 | 标题、作者、故事摘要、第 1 页插图 |
| 内页 | 左侧:data/images/{story_id}/{页码}.png右侧:该镜 page.text + 章节标题 |
核心循环逻辑:
for page in detail.pages:
img_path = data_root() / "images" / story_id / f"{page.index}.png"
draw_image_in_box(img_path, margin, margin, left_w, content_h) # 左半幅
draw_wrapped_text(text=_page_reading_text(page), x=right_x, ...) # 右半幅
如果某一页文字较长,会自动续页(同一张插图 +「(续)」标题),避免正文被截断。
导出入口不变:done.html → POST /api/v1/stories/{id}/export-pdf → 下载 data/exports/{id}.pdf。
全部页的正文拼在一起就是完整故事;每页插图落在对应分镜位置,PDF 和阅读页使用的是同一套 page.text。
四、删书时清理磁盘插图
从书库里删除一本绘本,SQLite 记录没了,但 data/images/ 和 data/character_refs/ 里的 PNG 会一直占用磁盘。我加了 story_assets.py,在删书接口里一并清理出图文件:
def purge_story_image_assets(story_id: str) -> dict[str, bool]:
"""删除 images/{story_id}/ 与 character_refs/{story_id}/"""
...
触发点:
-
DELETE /api/v1/stories/{story_id}— 删除单本 -
DELETE /api/v1/stories/history/all— 清空历史
保留不动的内容:page_attempts 出图审计 JSON、image_corpus.jsonl RAG 语料(删书不影响以后新书的出图质量)。
五、当前链路
create.html → POST /jobs
↓
scripting / storyboarding / imaging(按页更新 stage_message)
↓
Qwen 出图(双角色构图 + full-bleed)→ data/images/{story_id}/
↓
reader.html 阅读
↓
done.html → export-pdf(左图右文 PDF)
↓
删书 → purge_story_image_assets() 清磁盘
六、小结
第七篇主要做了四件事:
-
体验:imaging 按页进度 + 前端长轮询 + 默认关闭热重载;
-
成图:双角色页为配角补上独立的、带左右位置的 action 描述,告别「配角被画没」;
-
交付:PDF 左图右文,插图落在分镜对应的故事位置;
-
运维:删书时自动清理
images/与character_refs/。
仍待改进:
-
workflow 分步骤模式尚未接入
stage_message细粒度进度
更多推荐

所有评论(0)