Spaces:
Running
Running
| # core/prompts.py | |
| """All prompt templates (EN + BN, per style). | |
| Stories must read like a grandmother's bedtime tale: warm, simple, rhythmic, | |
| 120–150 words, gentle sleepy ending, never scary, never a cliffhanger. | |
| Templates use {instruction}, {name_line}, {words}, {style_rule} placeholders. | |
| Bengali prompts are written natively in বাংলা — never "translate to Bengali". | |
| NOTE: This is a Step 1 skeleton. Real templates are filled in at Build Order | |
| steps 2 (EN) and 3 (BN). | |
| """ | |
| # Story-style keys exposed to the UI. EN and BN have separate style sets. | |
| STYLES: dict[str, dict[str, str]] = { | |
| "en": { | |
| "Adventure 🌟": "a gentle adventure where nothing truly scary happens", | |
| "Funny 😄": "a silly, giggly tale full of light-hearted fun", | |
| "Magical ✨": "a soft, wondrous tale with a touch of quiet magic", | |
| }, | |
| "bn": { | |
| "রূপকথা": "একটি কোমল রূপকথা যেখানে ভয় পাওয়ার কিছু নেই", | |
| "মজার": "একটি হাসিখুশি, মজার গল্প", | |
| "জাদু": "একটি কোমল, জাদুমাখা গল্প", | |
| }, | |
| } | |
| DEFAULT_WORDS = "120 to 150" | |
| # Hard cap on images woven into one story (CLAUDE.md §1: 1–4 images). | |
| MAX_IMAGES = 4 | |
| def _images_rule(language: str, num_images: int) -> str: | |
| """Count-aware instruction for weaving 0–4 pictures into ONE story cast.""" | |
| n = max(0, min(num_images, MAX_IMAGES)) | |
| if language == "bn": | |
| if n == 0: | |
| return "এবার কোনো ছবি নেই — তুমি নিজেই একটা আরামদায়ক ছোট্ট দৃশ্য কল্পনা করে নাও।" | |
| if n == 1: | |
| return "ছবিটি ভালো করে দেখো আর তাতে যা সত্যিই আছে — শিশুর নিজের আঁকা ও খেলনা — গল্পে বুনে দাও।" | |
| return ( | |
| f"{n}টি ছবি আছে। সবগুলোকে একটিই গল্পে বুনে দাও — প্রতিটি ছবি যেন একই " | |
| "গল্পের এক একটি চরিত্র, বন্ধু বা জায়গা হয়, সবাই একসাথে মিলেমিশে।" | |
| ) | |
| if n == 0: | |
| return "There is no picture this time — gently imagine a cosy little scene yourself." | |
| if n == 1: | |
| return "Use what is actually in the picture — the child's own drawing or toy." | |
| return ( | |
| f"There are {n} pictures. Weave them ALL into a SINGLE story — let each " | |
| "picture become a character, friend, or place that meets in the same gentle tale." | |
| ) | |
| # Gemma (Stacks B/C) overshoots word counts and writes lush prose; MiniCPM (Stack A) | |
| # runs terse. Nudge the cap per stack family. See CLAUDE.md §8. | |
| _WORDS_BY_STACK = {"A": "130 to 160", "B": "110 to 140", "C": "110 to 140"} | |
| # Bengali: a shorter target than English. A long Bengali span pushes the model | |
| # past its reliable range and invites invented words / script drift — keep it tight. | |
| _BN_WORDS = "80 to 110" | |
| # A tiny clean রূপকথা anchor (few-shot): locks Bengali script, register, and the | |
| # soft sleepy rhythm so the model imitates good বাংলা instead of drifting. | |
| _BN_EXAMPLE = ( | |
| "যেমন: “এক যে ছিল ছোট্ট খরগোশ, থাকত পুকুরপাড়ে। সারাদিন মাঠে খেলত, " | |
| "সন্ধ্যা হলে মায়ের কোলে ঘুমিয়ে পড়ত। শুভরাত্রি।”" | |
| ) | |
| # Lever C — two-pass. Pass 1 asks the model to DESCRIBE the image(s) in English | |
| # (its vision strength); pass 2 narrates from that text so the model spends all | |
| # its capacity on Bengali prose, not on perceiving the image at the same time. | |
| SCENE_SENTINEL = "[[SCENE_DESCRIPTION]]" | |
| DESCRIBE_PROMPT_EN = ( | |
| "Look carefully at the picture(s) a child has shared. In 3–5 simple English " | |
| "sentences, describe ONLY what you actually see: the objects, their colours, " | |
| "and the overall mood. Do not tell a story — just describe what is in the picture(s)." | |
| ) | |
| _TEMPLATE_EN = """You are a warm, loving grandmother telling a bedtime story to a small child. | |
| Look at the picture(s) the child has shared and weave what you see into {style_rule}. | |
| {name_line}The child asked: "{instruction}" | |
| {scene} | |
| Rules: | |
| - Write {words} words. Simple words a young child knows. Gentle, rhythmic, soothing. | |
| - {images_rule} | |
| - Never anything scary, sad, or a cliffhanger. | |
| - End with everyone safe, cosy, and settling down to sleep. | |
| Tell the story now, in English:""" | |
| _TEMPLATE_BN = """তুমি একজন স্নেহময়ী ঠাকুমা, ছোট্ট এক শিশুকে ঘুমপাড়ানি গল্প বলছ। | |
| শিশুটি যে ছবি(গুলি) দেখিয়েছে তা দেখো এবং সেটিকে গল্পে বুনে দাও — {style_rule}। | |
| {name_line}শিশুটি বলেছে: "{instruction}" | |
| {scene} | |
| নিয়ম: | |
| - শুধু বিশুদ্ধ বাংলা অক্ষরে লেখো — কোনো ইংরেজি বা রোমান হরফ ব্যবহার করবে না। | |
| - কেবল চেনা, সঠিক বাংলা শব্দ ব্যবহার করো — কোনো বানানো বা অর্থহীন শব্দ নয়। | |
| - {words}টি শব্দে লেখো। ছোট শিশুর চেনা সহজ শব্দ। কোমল, ছন্দময়, আদুরে। | |
| - {images_rule} | |
| - পুকুর, মাঠ, জোনাকি, চাঁদমামার মতো চেনা বাংলা ছবি ব্যবহার করো। | |
| - কখনও ভয়ের, দুঃখের বা অসমাপ্ত কিছু নয়। | |
| - সবাই নিরাপদে, আরামে, ঘুমিয়ে পড়ার মধ্য দিয়ে গল্প শেষ করো। | |
| {example} | |
| এবার বাংলায় গল্পটি বলো:""" | |
| def _scene_block(language: str, scene_description: str) -> str: | |
| """Two-pass: a grounding block holding the (English) scene description for | |
| pass 2. Empty in single-pass mode.""" | |
| if not scene_description: | |
| return "" | |
| if language == "bn": | |
| return f"\nছবিতে যা আছে তার বর্ণনা: {scene_description}\n" | |
| return f"\nWhat is in the picture: {scene_description}\n" | |
| def _scene_rule(language: str) -> str: | |
| """Two-pass replacement for the images_rule: weave the described scene in.""" | |
| if language == "bn": | |
| return "উপরের বর্ণনায় থাকা জিনিসগুলো গল্পে বুনে দাও।" | |
| return "Weave the things in the description above into the story." | |
| def build_story_prompt( | |
| instruction: str, | |
| language: str, | |
| style: str, | |
| child_name: str = "", | |
| stack_key: str = "A", | |
| num_images: int = 1, | |
| scene_description: str = "", | |
| ) -> str: | |
| """Build the full story prompt for the vision model. language: 'en' | 'bn'. | |
| num_images (0–4) drives how the prompt asks the model to weave the pictures | |
| into one shared story cast. | |
| scene_description: two-pass (Lever C). When set, this is the pass-2 prompt — | |
| it injects the (English) scene description as text and the model narrates from | |
| it with NO image attached. Pass a sentinel (SCENE_SENTINEL) here and have the | |
| Modal layer substitute the real description after the describe pass.""" | |
| lang = language if language in ("en", "bn") else "en" | |
| styles = STYLES[lang] | |
| style_rule = styles.get(style) or next(iter(styles.values())) | |
| words = _WORDS_BY_STACK.get(stack_key, DEFAULT_WORDS) | |
| # In two-pass mode the story is grounded in the description, not the raw image. | |
| images_rule = _scene_rule(lang) if scene_description else _images_rule(lang, num_images) | |
| scene = _scene_block(lang, scene_description) | |
| if lang == "bn": | |
| name_line = f"শিশুটির নাম {child_name}; গল্পে তার নাম বুনে দাও।\n" if child_name else "" | |
| instr = instruction.strip() or "আমাকে একটা গল্প বলো" | |
| return _TEMPLATE_BN.format( | |
| style_rule=style_rule, name_line=name_line, instruction=instr, | |
| words=_BN_WORDS, images_rule=images_rule, example=_BN_EXAMPLE, scene=scene, | |
| ) | |
| name_line = f"The child's name is {child_name}; weave their name in warmly.\n" if child_name else "" | |
| instr = instruction.strip() or "tell me a bedtime story" | |
| return _TEMPLATE_EN.format( | |
| style_rule=style_rule, name_line=name_line, instruction=instr, | |
| words=words, images_rule=images_rule, scene=scene, | |
| ) | |