"""Local deterministic demo adapters for portfolio Docker demos.""" from __future__ import annotations from typing import Literal from app.services.adapters.base import BaseAdapter from app.services.adapters.registry import AdapterRegistry from app.services.adapters.storybook.primary import Storybook, StorybookPage from app.services.adapters.text.models import StoryOutput def _compact_topic(data: str) -> str: parts = [ item.strip(" ,,。.!!??") for item in data.replace("\n", " ").split() if item.strip(" ,,。.!!??") ] if parts: return "、".join(parts[:3]) return data.strip()[:20] or "星光森林" def _demo_image_data_url(label: str) -> str: return ( "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' " "viewBox='0 0 1200 800'%3E%3Crect width='1200' height='800' " "fill='%23fef3c7'/%3E%3Ccircle cx='250' cy='180' r='130' " "fill='%23f59e0b'/%3E%3Cpath d='M120 600C320 420 520 700 " "720 500S1020 420 1100 560' fill='none' stroke='%237c3aed' " "stroke-width='42'/%3E%3C/svg%3E" ) @AdapterRegistry.register("text", "demo") class DemoTextAdapter(BaseAdapter[StoryOutput]): """Generate a stable local story without external AI services.""" adapter_type = "text" adapter_name = "demo" async def execute( self, input_type: Literal["keywords", "full_story"], data: str, education_theme: str | None = None, memory_context: str | None = None, **kwargs, ) -> StoryOutput: theme = education_theme or "勇气与想象力" topic = _compact_topic(data) title = f"{topic}的晚安冒险" protagonist = "小星" if memory_context and "名字" in memory_context: protagonist = "故事里的小朋友" story_text = ( f"睡前,{protagonist}把“{topic}”写在一张小纸条上,轻轻放进枕头下面。" f"月光刚好照进房间,纸条变成了一只会发光的小船,邀请{protagonist}去寻找" f"关于“{theme}”的答案。\n\n" f"小船穿过云朵河,来到一片会唱歌的森林。森林里的小鹿说:真正的勇敢不是" f"一点也不害怕,而是害怕的时候,还愿意牵住朋友的手往前走。{protagonist}" f"听完以后,把一颗星星种进土里,星星长成了照亮回家路的小灯。\n\n" f"回到房间时,纸条已经变成了一枚金色书签。{protagonist}把它夹进故事书," f"决定明天也带着好奇心和温柔,继续新的冒险。" ) return StoryOutput( mode="generated" if input_type == "keywords" else "enhanced", title=title, story_text=story_text, cover_prompt_suggestion=f"温暖水彩儿童绘本封面,主题:{topic},{theme}", ) async def health_check(self) -> bool: return True @property def estimated_cost(self) -> float: return 0.0 @AdapterRegistry.register("image", "demo") class DemoImageAdapter(BaseAdapter[str]): """Return a compact SVG data URL as a generated-image placeholder.""" adapter_type = "image" adapter_name = "demo" async def execute(self, prompt: str, **kwargs) -> str: return _demo_image_data_url(prompt) async def health_check(self) -> bool: return True @property def estimated_cost(self) -> float: return 0.0 @AdapterRegistry.register("storybook", "demo") class DemoStorybookAdapter(BaseAdapter[Storybook]): """Generate a stable local storybook without external AI services.""" adapter_type = "storybook" adapter_name = "demo" async def execute( self, keywords: str, page_count: int = 6, education_theme: str | None = None, memory_context: str | None = None, **kwargs, ) -> Storybook: theme = education_theme or "勇气" topic = _compact_topic(keywords) page_count = max(4, min(page_count, 8)) page_texts = [ f"小星在枕头下发现一张写着“{topic}”的星光地图。", "地图带他来到云朵河边,一只小船正在等他上船。", f"森林里的小鹿告诉他:{theme}藏在每一次温柔的选择里。", "小星把一颗星星种进土里,星星长成了回家的灯。", "他把今天的发现画进故事本,准备明天讲给家人听。", "月亮轻轻合上窗帘,房间里只剩下甜甜的梦。", "星光地图变成书签,陪他继续下一次冒险。", "每个勇敢的小问题,都能长出一个温暖的故事。", ] pages = [ StorybookPage( page_number=index + 1, text=page_texts[index], image_prompt=f"温暖儿童绘本插画,第 {index + 1} 页,{topic},{theme}", ) for index in range(page_count) ] return Storybook( title=f"{topic}的星光绘本", main_character="小星", art_style="温暖水彩", pages=pages, cover_prompt=f"温暖水彩儿童绘本封面,{topic},{theme}", ) async def health_check(self) -> bool: return True @property def estimated_cost(self) -> float: return 0.0