重构回忆录为 story-first / markdown-first 架构并整合图片意图与前端 UI 修复
本次 squash merge 将 codex-story-first-image-intent 的整体改动合入 development,核心内容包括: 1. 后端数据与迁移:新增 stories、story_versions、story_image_intents、chapter_cover_intents、assets 等模型与 Alembic 迁移,建立 story-first、markdown-first、asset-first 的主数据链路。 2. 生成与任务链:引入 StoryBuilderOrchestrator、ChapterComposerOrchestrator、story_image_tasks、chapter_cover_tasks,图片生成从正文占位符改为结构化 intent -> asset -> markdown 回填。 3. 并发与一致性:为 story/chapter intent 增加 claim_token、claimed_at、attempt_count,采用数据库原子 claim 为主、Redis 锁为辅,避免重复生成、锁误删和 processing 卡死。 4. Memoir 读写路径:章节 canonical_markdown 成为正文真源,列表/详情接口补齐 markdown、cover_asset、word_count 等字段,PDF 与 asset 解析链路同步升级。 5. Memory / Retrieval:扩展 transcript ingest、chunking、evidence 检索与 story 聚合基础设施,为后续 story-first RAG 与多 agent 编排提供底座。 6. App 端体验:章节页继续走 MarkdownRenderer 阅读链,同时吸收 fix3-19 的跨平台 UI glitch 修复;更新对话页、首页、文案资源与章节列表映射逻辑。 7. 测试与文档:补充 asset resolver、story image task、章节封面派发、markdown 映射等回归测试,并加入图片占位符退役设计文档。
This commit is contained in:
65
api/tests/test_asset_resolver.py
Normal file
65
api/tests/test_asset_resolver.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""asset_resolver:旧占位符清理与 asset:// 解析。"""
|
||||
|
||||
import unittest
|
||||
|
||||
from app.features.memoir.asset_resolver import (
|
||||
collect_asset_ids_for_chapter,
|
||||
collect_asset_ids_from_markdown,
|
||||
resolve_asset_refs_in_markdown,
|
||||
split_markdown_by_asset_refs,
|
||||
strip_legacy_image_placeholders,
|
||||
)
|
||||
from app.features.memoir.models import Chapter, ChapterSection
|
||||
|
||||
|
||||
class AssetResolverTest(unittest.TestCase):
|
||||
def test_strip_legacy_image_placeholders_double_brace(self):
|
||||
md = "正文\n\n{{IMAGE:院子里的树}}\n\n结尾"
|
||||
out = strip_legacy_image_placeholders(md)
|
||||
self.assertNotIn("IMAGE", out)
|
||||
self.assertIn("正文", out)
|
||||
self.assertIn("结尾", out)
|
||||
|
||||
def test_strip_legacy_image_placeholders_quad_brace(self):
|
||||
md = "a\n\n{{{{IMAGE:描述}}}}\n\nb"
|
||||
out = strip_legacy_image_placeholders(md)
|
||||
self.assertNotIn("IMAGE", out)
|
||||
|
||||
def test_collect_and_split_asset_refs(self):
|
||||
md = "前\n\n\n\n后"
|
||||
self.assertEqual(collect_asset_ids_from_markdown(md), ["abc-123"])
|
||||
blocks = split_markdown_by_asset_refs(md, lambda aid: f"https://x/{aid}")
|
||||
self.assertEqual(len(blocks), 3)
|
||||
self.assertEqual(blocks[0]["type"], "text")
|
||||
self.assertEqual(blocks[1]["type"], "image")
|
||||
self.assertIn("https://x/abc-123", blocks[1]["url"])
|
||||
|
||||
def test_resolve_asset_refs_in_markdown(self):
|
||||
md = ""
|
||||
out = resolve_asset_refs_in_markdown(md, lambda aid: "https://cdn/u")
|
||||
self.assertIn("https://cdn/u", out)
|
||||
self.assertNotIn("asset://", out)
|
||||
|
||||
def test_collect_asset_ids_for_chapter(self):
|
||||
ch = Chapter(
|
||||
id="c1",
|
||||
user_id="u1",
|
||||
title="t",
|
||||
order_index=0,
|
||||
canonical_markdown="",
|
||||
cover_asset_id="cov1",
|
||||
)
|
||||
ch.sections = [
|
||||
ChapterSection(
|
||||
id="s1",
|
||||
chapter_id="c1",
|
||||
order_index=0,
|
||||
content="",
|
||||
)
|
||||
]
|
||||
ids = collect_asset_ids_for_chapter(ch)
|
||||
self.assertEqual(ids, {"a1", "a2", "cov1"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user