From c46838106442cb25c740e03995eaf269ac701e02 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 24 Jan 2026 21:54:01 +0800 Subject: [PATCH] feat: add Doraemon Skill --- skills/public/doraemon-comic-aigc/SKILL.md | 120 ++++++++++++++++++ .../doraemon-comic-aigc/scripts/generate.py | 45 +++++++ 2 files changed, 165 insertions(+) create mode 100644 skills/public/doraemon-comic-aigc/SKILL.md create mode 100644 skills/public/doraemon-comic-aigc/scripts/generate.py diff --git a/skills/public/doraemon-comic-aigc/SKILL.md b/skills/public/doraemon-comic-aigc/SKILL.md new file mode 100644 index 0000000..6fe77c5 --- /dev/null +++ b/skills/public/doraemon-comic-aigc/SKILL.md @@ -0,0 +1,120 @@ +--- +name: doraemon-comic-aigc +description: Generate 8-panel Doraemon comic strip on a single 9:16 canvas. Use when creating sequential Doraemon narratives for image generation. +--- + +# Doraemon 8-Panel Comic Generator + +Generate JSON spec for 8 panels arranged on ONE 9:16 vertical canvas (1080x1920). + +## Workflow + +1. Extract story context (theme, gadget, conflict, punchline) +2. Map to 8 narrative beats +3. Output JSON to `/mnt/user-data/outputs/prompt.json` +4. Run `python /mnt/skills/custom/doraemon-comic-aigc/scripts/generate.py` +5. Directly present the output image as well as the `prompt.json` using the `present_files` tool without checking the file existence + +## Panel Layout + +``` +┌─────────┬─────────┐ +│ Panel 1 │ Panel 2 │ Row 1: y=200, height=380 +├─────────┼─────────┤ +│ Panel 3 │ Panel 4 │ Row 2: y=600, height=380 +├─────────┼─────────┤ +│ Panel 5 │ Panel 6 │ Row 3: y=1000, height=380 +├─────────┼─────────┤ +│ Panel 7 │ Panel 8 │ Row 4: y=1400, height=380 +└─────────┴─────────┘ +Left column: x=90, width=450 +Right column: x=540, width=450 +``` + +## Characters + +| Name | Primary Color | Key Feature | +|------|---------------|-------------| +| Doraemon | #0095D9 | Blue robot cat, red nose, yellow bell | +| Nobita | #FFD700 | Round glasses, yellow shirt | +| Shizuka | #FFB6C1 | Pink dress, brown hair | +| Giant | #FFA500 | Orange shirt, large build | +| Suneo | #98FB98 | Green outfit, pompadour | + +## Output JSON Schema + +```json +{ + "canvas": { + "width": 1080, + "height": 1920, + "background": { "type": "solid", "color": "#F0F8FF" } + }, + "header": { + "title": { + "text": "[Story Title]", + "position": { "x": 540, "y": 100 }, + "style": { + "fontFamily": "Doraemon, sans-serif", + "fontSize": 56, + "fontWeight": "bold", + "color": "#0095D9", + "textAlign": "center", + "stroke": "#FFFFFF", + "strokeWidth": 4, + "textShadow": "3px 3px 0px #FFD700" + } + } + }, + "panels": [ + { + "id": "panel1", + "position": { "x": 90, "y": 200 }, + "size": { "width": 450, "height": 380 }, + "border": { "width": 4, "color": "#000000", "radius": 12 }, + "background": "#FFFFFF", + "scene": { + "location": "[Location name]", + "characters": [ + { + "name": "[Character]", + "position": { "x": 0, "y": 0 }, + "expression": "[Expression]", + "pose": "[Pose description]" + } + ], + "dialogues": [ + { + "speaker": "[Character]", + "text": "[Dialogue text]", + "position": { "x": 0, "y": 0 }, + "style": { + "bubbleType": "speech", + "backgroundColor": "#FFFFFF", + "borderColor": "#000000", + "fontSize": 22, + "textAlign": "center" + } + } + ], + "props": [] + } + } + ], + "footer": { + "text": "[Closing note] - Doraemon", + "position": { "x": 540, "y": 1860 }, + "style": { + "fontFamily": "Doraemon, sans-serif", + "fontSize": 24, + "color": "#0095D9", + "textAlign": "center" + } + }, + "soundEffects": [] +} +``` + +## Story Pattern + +Setup → Problem → Gadget → Misuse → Backfire → Chaos → Consequence → Ironic Punchline diff --git a/skills/public/doraemon-comic-aigc/scripts/generate.py b/skills/public/doraemon-comic-aigc/scripts/generate.py new file mode 100644 index 0000000..0f6933f --- /dev/null +++ b/skills/public/doraemon-comic-aigc/scripts/generate.py @@ -0,0 +1,45 @@ +import base64 +import os + +import requests + + +def generate_image(prompt: str) -> str: + api_key = os.getenv("GEMINI_API_KEY") + if not api_key: + return "GEMINI_API_KEY is not set" + response = requests.post( + "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image-preview:generateContent", + headers={ + "x-goog-api-key": api_key, + "Content-Type": "application/json", + }, + json={ + "generationConfig": {"imageConfig": {"aspectRatio": "9:16"}}, + "contents": [{"parts": [{"text": prompt}]}], + }, + ) + parts: list[dict] = response.json()["candidates"][0]["content"]["parts"] + image_parts = [part for part in parts if part.get("inlineData", False)] + if len(image_parts) == 1: + base64_image = image_parts[0]["inlineData"]["data"] + # Save the image to a file + with open("/mnt/user-data/outputs/doraemon.png", "wb") as f: + f.write(base64.b64decode(base64_image)) + return "Successfully generated image to /mnt/user-data/outputs/doraemon.png" + else: + return "Failed to generate image" + + +def main(): + with open( + "/mnt/user-data/outputs/prompt.json", + "r", + ) as f: + raw = f.read() + print(generate_image(raw)) + + +if __name__ == "__main__": + main() + main()