diff --git a/api/services/memoir_images/__init__.py b/api/services/memoir_images/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/services/memoir_images/parser.py b/api/services/memoir_images/parser.py new file mode 100644 index 0000000..7854f66 --- /dev/null +++ b/api/services/memoir_images/parser.py @@ -0,0 +1,49 @@ +import re +from typing import Any + +PLACEHOLDER_RE = re.compile(r"\{\{\{\{IMAGE:(.*?)\}\}\}\}") + + +def parse_image_placeholders(content: str, max_images: int) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + for match in PLACEHOLDER_RE.finditer(content or ""): + description = match.group(1).strip() + if not description: + continue + items.append( + { + "index": len(items), + "description": description, + "placeholder": match.group(0), + "start_offset": match.start(), + } + ) + if len(items) >= max_images: + break + return items + + +def build_initial_image_assets( + placeholders: list[dict[str, Any]], + provider: str, + style: str, + size: str, + now_iso: str, +) -> list[dict[str, Any]]: + return [ + { + "index": item["index"], + "placeholder": item["placeholder"], + "description": item["description"], + "prompt": None, + "url": None, + "status": "pending", + "provider": provider, + "style": style, + "size": size, + "error": None, + "created_at": now_iso, + "updated_at": now_iso, + } + for item in placeholders + ] diff --git a/api/tests/test_memoir_image_parser.py b/api/tests/test_memoir_image_parser.py new file mode 100644 index 0000000..3e29c0f --- /dev/null +++ b/api/tests/test_memoir_image_parser.py @@ -0,0 +1,45 @@ +import unittest + +from api.services.memoir_images.parser import ( + build_initial_image_assets, + parse_image_placeholders, +) + + +class MemoirImageParserTest(unittest.TestCase): + def test_parse_image_placeholders_preserves_order_and_offsets(self): + content = ( + "那条路我一直记得。\n\n" + "{{{{IMAGE:南方小镇的青石板路}}}}\n\n" + "奶奶总坐在门口。\n\n" + "{{{{IMAGE:奶奶坐在院子里的藤椅上}}}}" + ) + + items = parse_image_placeholders(content, max_images=3) + + self.assertEqual([item["index"] for item in items], [0, 1]) + self.assertEqual(items[0]["description"], "南方小镇的青石板路") + self.assertEqual(items[1]["placeholder"], "{{{{IMAGE:奶奶坐在院子里的藤椅上}}}}") + self.assertLess(items[0]["start_offset"], items[1]["start_offset"]) + + def test_build_initial_image_assets_marks_every_item_pending(self): + placeholders = [ + { + "index": 0, + "description": "南方小镇的青石板路", + "placeholder": "{{{{IMAGE:南方小镇的青石板路}}}}", + "start_offset": 10, + } + ] + + assets = build_initial_image_assets( + placeholders=placeholders, + provider="liblib", + style="watercolor", + size="1024x1024", + now_iso="2026-03-10T10:00:00Z", + ) + + self.assertEqual(assets[0]["status"], "pending") + self.assertEqual(assets[0]["provider"], "liblib") + self.assertEqual(assets[0]["url"], None)