
Smart Illustrator
- 84 installs
- 530 repo stars
- Updated June 26, 2026
- axtonliu/smart-illustrator
smart-illustrator is a Claude skill that generates article illustrations, slide infographics and cover images from a document using Gemini, Excalidraw and Mermaid engines.
About
This skill is a smart illustration and infographic generator with three modes: article illustration, PPT/slides infographics and cover images. It reads an article, picks illustration spots, and routes each to one of three engines, Gemini for creative or metaphor images, Excalidraw for hand-drawn concept diagrams, and Mermaid for complex structured diagrams. It supports a Bento Grid feature-showcase style and platform-specific cover sizes for YouTube, WeChat, Twitter and Xiaohongshu, and can output prompt-only or call the Gemini API to render images.
- Generates article illustrations, PPT/slides infographics and cover images
- Three engines: Gemini for creative images, Excalidraw and Mermaid for diagrams
- Supports Bento Grid style and platform-sized covers (YouTube, WeChat, Twitter, Xiaohongshu)
Smart Illustrator by the numbers
- 84 all-time installs (skills.sh)
- Ranked #811 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
smart-illustrator capabilities & compatibility
uses the Gemini API (requires a Gemini API key) to render images
- Capabilities
- image generation · presentations
- Use cases
- image generation · presentations · copywriting
- Pricing
- Bring your own API key
What smart-illustrator says it does
npx -y bun ~/.claude/skills/smart-illustrator/scripts/mermaid-export.ts \ -i {图表名}.mmd -o {图表名}.png -w 2400
| **1** | Gemini | 隐喻图、创意图、封面图、无法用图表表达的概念 | PNG |
npx skills add https://github.com/axtonliu/smart-illustrator --skill smart-illustratorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 84 |
|---|---|
| repo stars | ★ 530 |
| Last updated | June 26, 2026 |
| Repository | axtonliu/smart-illustrator ↗ |
What it does
Generate article illustrations, slide infographics and cover images from a document using Gemini, Excalidraw or Mermaid.
Who is it for?
turning an article or slide script into illustrations, infographics or platform-sized cover images
Skip if: treating the passed file as skill config (it is always the article to illustrate)
When should I use this skill?
you want to add illustrations, slide infographics or a cover image to an article
What you get
Rendered PNG illustrations, slide infographics or platform-sized cover images for a document.
- article illustrations (PNG)
- batch slide infographics
- platform-sized cover images
By the numbers
- 3 modes (article, slides, cover)
- 3-engine routing (Gemini, Excalidraw, Mermaid)
- 4 cover platforms at 2K resolution
Files
Smart Illustrator - 智能配图与 PPT 生成器
⛔ 强制规则(违反即失败)
规则 1:用户提供的文件 = 要处理的文章
/smart-illustrator SKILL_05.md → SKILL_05.md 是文章,为它配图
/smart-illustrator README.md → README.md 是文章,为它配图
/smart-illustrator whatever.md → whatever.md 是文章,为它配图无论文件名叫什么,都是要配图的文章,不是 Skill 配置。
规则 2:必须读取 style 文件
生成任何图片 prompt 前,必须读取对应的 style 文件:
| 模式 | 必须读取的文件 |
|---|---|
| 文章配图(默认) | styles/style-light.md |
| Cover 封面图 | styles/style-cover.md |
--style dark | styles/style-dark.md |
--style bento | styles/style-bento.md |
禁止自己编写 System Prompt。
❌ 错误:"你是一个专业的信息图设计师..."(自己编的) ✅ 正确:从 style 文件的代码块中提取 System Prompt
---
使用方式
文章配图模式(默认)
/smart-illustrator path/to/article.md
/smart-illustrator path/to/article.md --prompt-only # 只输出 prompt
/smart-illustrator path/to/article.md --style dark # 深色风格
/smart-illustrator path/to/article.md --no-cover # 不生成封面图PPT/Slides 模式
# 默认:直接生成图片
/smart-illustrator path/to/script.md --mode slides
# 只输出 JSON prompt(不调用 API)
/smart-illustrator path/to/script.md --mode slides --prompt-only默认行为:调用 Gemini API 生成批量信息图。 `--prompt-only`:输出 JSON prompt 并自动复制到剪贴板,可直接粘贴到 Gemini Web 手动生成。
PPT JSON 格式(--prompt-only 时输出):
{
"instruction": "请逐条生成以下 N 张独立信息图。",
"batch_rules": { "total": "N", "one_item_one_image": true, "aspect_ratio": "16:9" },
"style": "[从 styles/style-light.md 读取完整内容]",
"pictures": [
{ "id": 1, "topic": "封面", "content": "系列名称\n\n第N节:标题" },
{ "id": 2, "topic": "主题", "content": "原始内容" }
]
}Cover 模式
/smart-illustrator path/to/article.md --mode cover --platform youtube
/smart-illustrator --mode cover --platform youtube --topic "Claude 4 深度评测"平台尺寸(输出均为 2K 分辨率):
| 平台 | 代码 | 宽高比 |
|---|---|---|
| YouTube | youtube | 16:9 |
| 公众号 | wechat | 2.35:1 |
twitter | 1.91:1 | |
| 小红书 | xiaohongshu | 3:4 |
---
参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
--mode | article | article / slides / cover |
--platform | youtube | 封面图平台(仅 cover 模式) |
--topic | - | 封面图主题(仅 cover 模式) |
--prompt-only | false | 输出 prompt 到剪贴板,不调用 API(适用于所有模式) |
--style | light | 风格:light / dark / minimal / bento |
--no-cover | false | 不生成封面图 |
--ref | - | 参考图路径(可多次使用) |
-c, --candidates | 1 | 候选图数量(最多 4) |
-a, --aspect-ratio | - | 宽高比:16:9(正文配图/封面图默认)、3:2(备选横版)、3:4(仅竖屏平台) |
--engine | auto | 引擎选择:auto(自动)/ mermaid / gemini / excalidraw |
--mermaid-embed | false | Mermaid 输出为代码块而非 PNG(旧行为) |
--save-config | - | 保存到项目配置 |
--no-config | false | 禁用 config.json |
`--no-config` 范围:只禁用config.json,不影响styles/style-*.md。
---
配置文件
优先级:CLI 参数 > 项目级 > 用户级
| 位置 | 路径 |
|---|---|
| 项目级 | .smart-illustrator/config.json |
| 用户级 | ~/.smart-illustrator/config.json |
{ "references": ["./refs/style-ref-01.png"] }---
三级配图引擎
| 优先级 | 引擎 | 适用场景 | 输出 |
|---|---|---|---|
| 1 | Gemini | 隐喻图、创意图、封面图、无法用图表表达的概念 | PNG |
| 2 | Excalidraw | 概念图、对比图、简单流程(≤ 8 节点)、关系图、手绘风格示意图 | PNG |
| 3 | Mermaid | 仅限:复杂流程(> 8 节点)、多层架构图、多角色时序图、多分支决策树 | PNG |
选择逻辑:
- 需要隐喻、情感、创意表达 → Gemini
- 概念关系、对比、简单流程 → Excalidraw(大多数图表场景的首选)
- 只有节点 > 8、多层/多角色的复杂结构化图形 → Mermaid
- Mermaid 视觉表现力有限,能用 Excalidraw 就不用 Mermaid
- 唯一目标:提高文章吸引力
生成 Excalidraw 前必须读取 references/excalidraw-guide.md。
Mermaid 语义色板
每种颜色有固定含义,必须使用 `classDef` + `class` 应用:
| 语义 | 填充色 | 边框色 | 用于 |
|---|---|---|---|
| input | #d3f9d8 | #2f9e44 | 输入、起点、数据源 |
| process | #e5dbff | #5f3dc4 | 处理、推理、核心逻辑 |
| decision | #ffe3e3 | #c92a2a | 决策点、分支判断 |
| action | #ffe8cc | #d9480f | 执行动作、工具调用 |
| output | #c5f6fa | #0c8599 | 输出、结果、终点 |
| storage | #fff4e6 | #e67700 | 存储、记忆、数据库 |
| meta | #e7f5ff | #1971c2 | 标题、分组、元信息 |
classDef 写法(放在图表末尾):
classDef input fill:#d3f9d8,stroke:#2f9e44,color:#1a1a1a
classDef process fill:#e5dbff,stroke:#5f3dc4,color:#1a1a1a
classDef decision fill:#ffe3e3,stroke:#c92a2a,color:#1a1a1a
classDef action fill:#ffe8cc,stroke:#d9480f,color:#1a1a1a
classDef output fill:#c5f6fa,stroke:#0c8599,color:#1a1a1a
class A input
class B,C process
class D outputMermaid 布局规则
- 布局方向:默认
TB(上到下),横向流程用LR - 箭头分级:
-->主流程 /-.->可选/辅助路径 /==>重点强调 - 分组:用
subgraph对相关节点分组,标题简洁 - 节点文字:≤ 8 字,无 emoji,禁止
1.格式(用①或Step 1:) - 节点数量:单图 ≤ 15 个节点,复杂内容拆成多图
`--engine` 参数:
auto(默认):根据内容类型自动选择(优先级 Gemini > Excalidraw > Mermaid)gemini:强制只使用 Gemini(适合创意内容)excalidraw:强制只使用 Excalidraw(适合手绘概念图)mermaid:强制只使用 Mermaid(适合技术文档)
---
执行流程
Step 1: 分析文章
1. 读取文章内容 2. 识别配图位置(通常 3-5 个) 3. 为每个位置确定引擎(Gemini / Excalidraw / Mermaid)
Step 2: 生成图片
Mermaid(结构化图形)→ PNG
1. 生成 Mermaid 代码,保存为临时 .mmd 文件 2. 调用 mermaid-export.ts 导出高分辨率 PNG:
npx -y bun ~/.claude/skills/smart-illustrator/scripts/mermaid-export.ts \
-i {图表名}.mmd -o {图表名}.png -w 24003. 在文章中插入 PNG 图片引用 4. 保留 .mmd 源文件用于后续编辑
使用 --mermaid-embed 参数时,改为直接嵌入 Mermaid 代码块(旧行为)。
Excalidraw(手绘/概念图)→ PNG
1. 读取 references/excalidraw-guide.md 获取 JSON 规范 2. 生成 Excalidraw JSON,保存为 .excalidraw 文件 3. 调用 excalidraw-export.ts 导出 PNG:
npx -y bun ~/.claude/skills/smart-illustrator/scripts/excalidraw-export.ts \
-i {图表名}.excalidraw -o {图表名}.png -s 24. 在文章中插入 PNG 图片引用 5. 保留 .excalidraw 源文件用于后续编辑
依赖未安装时的降级:提示手动打开 excalidraw.com 导出。
Gemini(创意/视觉图形)
命令模板(必须使用 HEREDOC + prompt-file):
# Step 1: 写入 prompt
cat > /tmp/image-prompt.txt <<'EOF'
{从 style 文件提取的 System Prompt}
**内容**:{配图内容}
EOF
# Step 2: 调用脚本
GEMINI_API_KEY=$GEMINI_API_KEY npx -y bun ~/.claude/skills/smart-illustrator/scripts/generate-image.ts \
--prompt-file /tmp/image-prompt.txt \
--output {输出路径}.png \
--aspect-ratio 16:9封面图(16:9):
cat > /tmp/cover-prompt.txt <<'EOF'
{从 style-cover.md 提取的 System Prompt}
**内容**:
- 核心概念:{主题}
- 视觉隐喻:{设计}
EOF
GEMINI_API_KEY=$GEMINI_API_KEY npx -y bun ~/.claude/skills/smart-illustrator/scripts/generate-image.ts \
--prompt-file /tmp/cover-prompt.txt \
--output {文章名}-cover.png \
--aspect-ratio 16:9参数传递:用户指定的 --no-config、--ref、-c 必须传递给脚本。
Step 3: 创建带配图的文章
保存为 {文章名}-image.md,包含:
- YAML frontmatter 声明封面图
- 正文配图插入
Step 4: 输出确认
报告:生成了几张图片、输出文件列表。
---
--prompt-only 模式
当使用 --prompt-only 时,不调用 API,而是:
1. 生成 JSON prompt 2. 自动复制到剪贴板(使用 pbcopy) 3. 同时保存到文件备份
# 执行方式
echo '{生成的 JSON}' | pbcopy
echo "✓ JSON prompt 已复制到剪贴板"
# 同时保存备份
echo '{生成的 JSON}' > /tmp/smart-illustrator-prompt.json
echo "✓ 备份已保存到 /tmp/smart-illustrator-prompt.json"用户可直接粘贴到 Gemini Web 手动生成图片。
---
输出文件
article.md # 原文(不修改)
article-image.md # 带配图的文章
article-cover.png # 封面图(16:9)
article-image-01.png # Gemini 配图# Dependencies
node_modules/
.pnpm-store/
# Environment variables
.env
.env.local
.env.*.local
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
# Test outputs (optional - comment out if you want to commit examples)
test-output/
# Sensitive/private files
references/文章自动配图工具.md
# Internal development documents (kept locally, not for public repo)
CLAUDE.md
DEVELOPMENT.md
PROGRESS.md
TEST-PLAN.md
DESIGN-*.md
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
bun-debug.log*
# Temporary files
*.tmp
*.temp
.cache/
# Build outputs (if any)
dist/
build/
out/
MIT License
Copyright (c) 2025 Axton Liu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
封面学习分析 Prompt
此 prompt 用于 --learn-cover 功能,通过 Gemini Vision 分析封面图,提取设计模式。
---
你是一位 YouTube 封面图分析专家。请分析这张封面图,提取对未来封面设计有价值的模式。
请用以下 JSON 格式输出(中文):
{
"composition": "构图描述(如:左侧人物 + 右侧文字、中心聚焦、对比布局等)",
"colorScheme": "配色方案(如:深色背景 + 橙色强调、高对比冷暖搭配等)",
"textUsage": "文字使用(如:无文字、3-5个大字、数字突出等)",
"emotion": "传达的情绪(如:好奇心、紧迫感、专业感、震惊等)",
"focusPoint": "视觉焦点(如:人物表情、产品 logo、对比元素等)",
"patterns": ["值得学习的模式1", "值得学习的模式2", "..."],
"avoidPatterns": ["如果有不好的地方,列出应避免的模式"]
}{{USER_NOTE}}
只输出 JSON,不要其他内容。
---
变量说明
{{USER_NOTE}}:如果用户提供了--learn-note "备注内容",会替换为用户备注:备注内容,否则删除此行
输出格式
- 必须是纯 JSON:不能有其他文字或解释
- 语言:所有字段值都用中文
- patterns:提取 2-5 个值得学习的模式
- avoidPatterns:如果有不好的地方,列出 1-3 个应避免的模式,否则留空数组
分析要点
AI 分析时会关注:
1. 构图:元素布局、视觉平衡、阅读路径 2. 配色:色彩搭配、对比度、情绪传达 3. 文字:数量、大小、位置、可读性 4. 情绪:封面想传达的感受(好奇/震惊/专业/温暖等) 5. 焦点:视觉第一落点是什么
自定义建议
如果你想调整分析关注点,可以修改:
- JSON 字段:增删字段(需同步修改
scripts/cover-learner.ts的类型定义) - 字段说明:调整括号中的示例,引导 AI 关注不同方面
- 输出要求:调整 patterns 数量、语言等
修改后保存,下次 --learn-cover 时自动生效。
Prompts 目录说明
本目录集中管理 Smart Illustrator 的所有 AI prompt 模板。
为什么集中管理?
- 修改方便:不需要修改代码,直接编辑 Markdown 文件
- 便于迭代:方便对比不同版本的 prompt 效果
- 易于分享:其他用户可以轻松自定义 prompt
- 降低门槛:非技术用户也能调整生成策略
Prompt 文件列表
| 文件 | 用途 | 被调用位置 |
|---|---|---|
varied-styles.md | Varied 模式的两种风格提示(dramatic/minimal) | scripts/generate-image.ts |
learning-analysis.md | 封面学习分析的 AI prompt | scripts/cover-learner.ts |
其他 Prompt 在哪里?
- 风格文件:
styles/style-light.md和styles/style-dark.md定义了核心设计规则 - 品牌配色:
styles/brand-colors.md定义了配色方案 - 封面学习记录:
~/.smart-illustrator/cover-learnings.md(运行时生成)
如何自定义?
1. 修改风格提示:编辑 varied-styles.md,调整 dramatic/minimal 的具体描述 2. 修改学习分析:编辑 learning-analysis.md,调整 AI 分析封面时关注的要素
修改后无需重启,下次生成时自动生效。
注意事项
- ⚠️ 修改 prompt 后建议先测试生成效果
- ⚠️ 保持 JSON 格式的正确性(如
learning-analysis.md) - ⚠️ 不要删除必需的字段,可以调整描述和说明
Varied 模式风格提示
当使用 --varied 参数时,会生成两种不同风格的封面供选择。以下是两种风格的提示文本,会自动追加到 prompt 末尾。
Candidate 1: Dramatic & High-Contrast
风格提示(Candidate 1):dramatic & high-contrast(戏剧性高对比)
- 使用强烈的明暗对比
- 情绪张力强
- 视觉冲击力优先
Candidate 2: Minimal & Professional
风格提示(Candidate 2):minimal & professional(极简专业)
- 极简构图,留白充足
- 专业、克制、高级感
- 信息清晰优先
---
使用说明
- 自动应用:当检测到是封面生成(prompt 包含 cover/封面/youtube/thumbnail)且开启
--varied时自动使用 - 覆盖范围:这些提示会追加到用户的 prompt 之后
- 与 style 关系:这些提示是对
styles/style-dark.md的补充,不会覆盖核心规则
自定义建议
你可以修改上面两种风格的描述来调整生成效果:
- 风格 1:适合传达强烈情绪、吸引注意力的场景
- 风格 2:适合专业内容、教程类视频的场景
修改后保存,下次 --varied 生成时自动生效。
Smart Illustrator
 
[中文文档](README.zh-CN.md)
🆕 v1.4.0 — Tri-Engine System (Feb 2026)
>
New Excalidraw engine for hand-drawn concept diagrams. Three-tier priority: Gemini → Excalidraw → Mermaid. All diagram engines now output PNG by default. Details →
!Tri-Engine Architecture
Intelligent article illustration Skill for Claude Code with tri-engine system: automatically selects Gemini (for creative visuals), Excalidraw (for hand-drawn diagrams), or Mermaid (for structured diagrams) based on content type.
Status
Status: Experimental
>
- This is a public prototype that works for my demos, but does not yet cover all input scales and edge cases.
- Output quality varies based on model version and input structure; results may fluctuate.
- My primary focus is demonstrating how tools and systems work together, not maintaining this codebase.
- If you encounter issues, please submit a reproducible case (input + output file + steps to reproduce).
Why Smart Illustrator?
Creating illustrations for articles is time-consuming: manual design takes hours, stock photos lack context, and generic AI tools don't understand article structure. Smart Illustrator combines intelligent position detection, tri-engine system (Gemini + Excalidraw + Mermaid), and cover learning to generate contextual illustrations in minutes.
Who it's for: Newsletter writers, YouTube creators, technical bloggers, course instructors.
When to use: When you need high-quality illustrations for articles, YouTube thumbnails with best practices, or consistent visual style across content series.
Background: the Make workflow version (auto-illustrate + WeChat publish)
Before packaging this into a Skill, I had already built an end-to-end Make workflow: search → write → auto-illustrate → format/publish (WeChat Official Account).
Full walkthrough (workflow logic & design trade-offs): https://youtu.be/TbyJ3imLuXQ
Features
- Tri-Engine System: Auto-selects Gemini, Excalidraw, or Mermaid based on content type
- Smart Position Detection: Analyzes article structure to identify optimal illustration points
- 10+ Illustration Types: flowchart, sequence, mindmap, concept, comparison, scene, metaphor...
- Extensible Style System: Light, Dark, Minimal, Cover, and custom styles
- Cover Mode: Generate high-CTR YouTube thumbnails with best practices built-in
- Multi-Platform Sizes: YouTube, WeChat, Twitter, Xiaohongshu presets
- Resume Generation: Skip already-generated images, regenerate specific ones
- Brand Customizable: Modify
styles/to apply your brand style - Multiple Backends: Gemini API for creative visuals (2K resolution), Excalidraw for hand-drawn diagrams, Mermaid CLI for structured diagrams — all output PNG by default
What Are Skills?
Skills are prompt-based extensions for Claude Code that give Claude specialized capabilities. Unlike MCP servers that require complex setup, skills are simple markdown files that Claude loads on demand.
Installation
Prerequisites
- Claude Code CLI installed
- Bun runtime (for scripts)
- Mermaid CLI (for Mermaid diagrams):
npm install -g @mermaid-js/mermaid-cli - Excalidraw export dependencies (optional, for Excalidraw diagrams):
cd ~/.claude/skills/smart-illustrator/scripts && npm install && npx playwright install firefox - Gemini API Key (optional, for creative visuals): https://aistudio.google.com/apikey
Option A: Manual Installation (Recommended)
# Clone to Claude Code Skills directory
git clone https://github.com/axtonliu/smart-illustrator.git ~/.claude/skills/smart-illustratorOption B: Copy Individual Files
# If you only want the skill without scripts
cp -r smart-illustrator/SKILL.md ~/.claude/skills/smart-illustrator/
cp -r smart-illustrator/styles ~/.claude/skills/smart-illustrator/Usage
Basic Usage
# Analyze article and auto-generate illustrations (default)
/smart-illustrator path/to/article.md
# Output prompts only, don't auto-generate images
/smart-illustrator path/to/article.md --prompt-only
# Specify style (loads from styles/ directory)
/smart-illustrator path/to/article.md --style light # Light style (default)
/smart-illustrator path/to/article.md --style dark # Dark tech style
/smart-illustrator path/to/article.md --style minimal # Minimal style
# List available styles
/smart-illustrator --list-styles
# Without cover image
/smart-illustrator path/to/article.md --no-cover
# Specify number of illustrations
/smart-illustrator path/to/article.md --count 5Parameters
| Parameter | Default | Description |
|---|---|---|
--mode | article | Mode: article, slides, or cover |
--engine | auto | Engine: auto, gemini, excalidraw, or mermaid |
--mermaid-embed | false | Embed Mermaid code blocks instead of exporting PNG |
--platform | youtube | Cover platform: youtube/wechat/twitter/xiaohongshu/landscape/square |
--topic | - | Cover topic (alternative to article path, cover mode only) |
--description | - | Cover visual direction (cover mode only) |
--prompt-only | false | Output prompts only, don't call API to generate images |
--style | light | Style name, loads styles/style-{name}.md |
--list-styles | - | List all available styles in styles/ directory |
--no-cover | false | Skip cover image generation (article mode) |
--count | auto | Number of illustrations (auto-determined by article length) |
Illustration Count Guidelines
| Article Length | Suggested Count |
|---|---|
| Short (< 1000 words) | 1-2 images |
| Medium (1000-3000 words) | 2-4 images |
| Long (> 3000 words) | 4-6 images |
| Tutorials/Guides | 1 per major step |
Output Files
article.md # Original
article-image.md # Article with illustrations (main output)
article-cover.png # Cover image (16:9)
article-image-01.png # Content illustration (3:4)
article-image-02.png
article-image-03.pngManual Script Usage
generate-image.ts (Single Image)
export GEMINI_API_KEY=your_key
# From prompt text
npx -y bun ~/.claude/skills/smart-illustrator/scripts/generate-image.ts \
--prompt "A concept diagram showing..." \
--output image.png
# From prompt file
npx -y bun ~/.claude/skills/smart-illustrator/scripts/generate-image.ts \
--prompt-file prompt.md \
--output image.png| Option | Description |
|---|---|
-p, --prompt | Image description text |
-f, --prompt-file | Read prompt from file |
-o, --output | Output path (default: generated.png) |
-m, --model | Model (default: gemini-3-pro-image-preview) |
batch-generate.ts (Batch Generation)
export GEMINI_API_KEY=your_key
npx -y bun ~/.claude/skills/smart-illustrator/scripts/batch-generate.ts \
--config slides.json \
--output-dir ./images \
--prefix SKILL_01| Option | Description |
|---|---|
-c, --config | JSON config file (required) |
-o, --output-dir | Output directory (default: ./illustrations) |
-m, --model | Model (default: gemini-3-pro-image-preview) |
-d, --delay | Delay between requests in ms (default: 2000) |
-p, --prefix | Filename prefix (default: from config filename) |
-r, --regenerate | Regenerate specific images (e.g., "3" or "3,5,7") |
-f, --force | Force regenerate all images (ignore existing) |
Resume Generation: By default, the script skips images that already exist in the output directory. This allows resuming interrupted generation without re-generating completed images.
Output: {prefix}-01.png, {prefix}-02.png, etc.
mermaid-export.ts (Mermaid to PNG)
# From .mmd file
npx -y bun ~/.claude/skills/smart-illustrator/scripts/mermaid-export.ts \
--input diagram.mmd \
--output diagram.png
# From inline content
npx -y bun ~/.claude/skills/smart-illustrator/scripts/mermaid-export.ts \
--content "flowchart LR
A[Start] --> B[End]" \
--output simple.png \
--theme dark| Option | Description |
|---|---|
-i, --input | Input .mmd file path |
-c, --content | Mermaid diagram content (alternative) |
-o, --output | Output path (default: output.png) |
-t, --theme | Theme: light (default) or dark |
-w, --width | Image width in pixels |
-H, --height | Image height in pixels |
PPT/Slides Generation Mode
Beyond article illustrations, this skill can generate batch infographics for PPT/Keynote slides.
When to Use
| Mode | Use Case | Output |
|---|---|---|
| Article Mode | Blog posts, newsletters | 3-5 illustrations inserted in article |
| Slides Mode | Video B-roll, presentations | 8-15 standalone infographics |
JSON Format for Batch Generation
Use pictures[] array format with explicit batch rules:
{
"instruction": "请为我绘制 7 张图片(generate 7 images)。你是一位「信息图绘制者」。请逐条执行 pictures 数组:每个 id 对应 1 张独立的 16:9 信息图,严禁合并,严禁只输出文字描述。",
"batch_rules": {
"total": 7,
"one_item_one_image": true,
"aspect_ratio": "16:9",
"do_not_merge": true
},
"fallback": "如果无法一次生成全部图片:请输出 7 条独立的单图绘图指令...",
"style": "[Complete style prompt - see styles/style-light.md]",
"pictures": [
{ "id": 1, "topic": "封面", "content": "Course Name\n\nSection Title\n\nLearning objectives..." },
{ "id": 2, "topic": "核心概念", "content": "[Raw content]" }
]
}Critical Rules
1. Use `pictures[]` array - Array structure helps Gemini enter "loop execution" mode for batch generation.
2. Add image trigger phrase - Must include "请为我绘制 N 张图片(generate N images)" to trigger image generation mode.
3. Role as "绘制者" not "导演" - Use "信息图绘制者" (illustrator) not "视觉导演" (director) to trigger actual drawing behavior.
4. Separate instruction from style - instruction = what to do + role; style = visual rules only.
5. Pass complete style - Include the full style prompt from styles/style-light.md, don't summarize.
6. Content granularity - Judge by information density, not mechanically by H2 headers.
Cover Slide Branding (PPT Mode)
For course/series content, the cover slide (id: 1) should include:
{
"id": 1,
"topic": "封面",
"content": "Agent Skills 完全指南\n\n第4节:渐进式披露与 Description 优化\n\n学习目标:理解 Progressive Disclosure 机制"
}Structure:
- Series name: e.g., "Agent Skills 完全指南"
- Section number: e.g., "第4节"
- Section title: e.g., "渐进式披露与 Description 优化"
- Learning objectives (optional)
Output Options (Applies to All Modes)
--prompt-only is a global option that works with Article, Slides, Cover, and all other modes:
| Output Mode | Parameter | Description | API Required |
|---|---|---|---|
| Generate Images | Default | Calls Gemini API to generate images | ✅ Yes |
| Output JSON Prompt | --prompt-only | Copy to Gemini Web for manual generation | ❌ No |
Example Combinations:
# Slides mode + generate images (needs API)
/smart-illustrator script.md --mode slides
# Slides mode + output JSON only (no API)
/smart-illustrator script.md --mode slides --prompt-only
# Article mode + generate images (needs API)
/smart-illustrator article.md
# Article mode + output JSON only (no API)
/smart-illustrator article.md --prompt-onlyManual Batch Generation (after JSON prompt output):
export GEMINI_API_KEY=your_key
npx -y bun ~/.claude/skills/smart-illustrator/scripts/batch-generate.ts \
--config slides.json \
--output-dir ./imagesSee references/slides-prompt-example.json for a complete example.
💡 Tip: Batch image generation with JSON in Gemini doesn't have 100% success rate. If generation fails:
1. Retry 1-3 times (usually succeeds)
2. If retries fail, copy individual items from pictures array and generate one by one---
Configuration Files (Style Reuse)
Save common parameters to configuration files for consistent style across series content (courses, newsletters).
Configuration File Locations
Priority: CLI Arguments > Project Config > User Config
| Location | Path | Purpose |
|---|---|---|
| Project | {working-dir}/.smart-illustrator/config.json | Project-specific style (e.g., course series) |
| User | ~/.smart-illustrator/config.json | User's global default style |
Configuration File Format
{
"references": [
"./refs/style-ref-01.png",
"./refs/style-ref-02.png"
]
}Supported options:
references: Array of reference image paths (relative paths are resolved relative to config file directory)
Usage Examples
# Initial setup: configure style for course series
cd ~/my-course
/smart-illustrator article-01.md --ref ./refs/style-1.png --save-config
# Subsequent generation: auto-apply config
/smart-illustrator article-02.md # Automatically uses reference images
# Temporary override: use different reference
/smart-illustrator article-03.md --ref ./other-ref.png
# Completely ignore config
/smart-illustrator article-04.md --no-config
# Save to user-level config (global default)
/smart-illustrator article.md --ref ./my-style.png --save-config-globalConfiguration Loading Rules
1. Read user-level config (if exists) 2. Read project-level config (if exists, overrides user-level) 3. Apply command-line arguments (overrides config files)
Typical scenarios:
- Course series: Save
.smart-illustrator/config.jsonin project directory for unified style across chapters - Personal default: Save
~/.smart-illustrator/config.jsonin user directory as global default - Temporary adjustment: Use
--refparameter to temporarily override config without modifying saved settings
---
Cover Mode (YouTube Thumbnails)
Generate high-CTR cover images for YouTube, WeChat, Twitter, and more. Built on YouTuber best practices research.
# Generate YouTube thumbnail from article
/smart-illustrator path/to/article.md --mode cover --platform youtube
# Generate with specific topic
/smart-illustrator --mode cover --platform youtube --topic "Claude 4 Deep Review"
# Generate with visual direction
/smart-illustrator --mode cover --platform wechat --description "Comparison diagram + tech aesthetic"Supported Platforms
All outputs are 2K resolution (e.g., ~2816×1584 for 16:9).
| Platform | Code | Aspect Ratio |
|---|---|---|
| YouTube | youtube | 16:9 |
wechat | 2.35:1 | |
| Twitter/X | twitter | 1.91:1 |
| Xiaohongshu | xiaohongshu | 3:4 |
| Landscape | landscape | 16:9 |
| Square | square | 1:1 |
Design Principles (from references/cover-best-practices.md)
1. 3-Second Rule: Instantly convey topic and value 2. High Contrast: Dark background + bright subject 3. Single Focus: Only one visual center 4. Minimal Text: 3-6 words, bold sans-serif 5. Curiosity Gap: Make viewers want to click
Visual Metaphors for Tech Content
| Concept | Metaphor |
|---|---|
| AI Assistant | Two collaborative hands, chat bubbles |
| Efficiency | Upward arrows, stairs, rocket trail |
| Automation | Gears, assembly line nodes |
| Learning/Growth | Seed → tree, ascending stairs |
| Problem → Solution | Maze exit, completed puzzle |
---
Smart Position Detection
The skill analyzes article structure to identify optimal illustration points:
| Signal | Illustration Value |
|---|---|
| Abstract concept first appears | High - helps build mental model |
| Process/step description | High - visual is clearer than text |
| Comparison/choice discussion | High - side-by-side is clear |
| Data/statistics reference | Medium - numbers visualized have impact |
| Section transition point | Medium - provides visual breathing room |
| Emotional/story climax | Medium - enhances resonance |
---
Tri-Engine System
The skill automatically selects the best rendering engine based on content, with three-tier priority:
| Priority | Engine | Best For | Output |
|---|---|---|---|
| 1 | Gemini | Creative visuals (metaphors, scenes, infographics) | PNG (2K) |
| 2 | Excalidraw | Hand-drawn concept diagrams, comparisons, simple flows | PNG |
| 3 | Mermaid | Complex structured diagrams (flowcharts, sequences, architectures) | PNG |
Selection logic:
- Needs metaphor, emotion, or creative expression → Gemini
- Needs hand-drawn / informal style, or simple concept relationships → Excalidraw
- Complex structured flows / architectures → Mermaid
Illustration Types
| Type | Engine | Best For | Syntax/Style |
|---|---|---|---|
process | Mermaid | Complex workflows | flowchart |
architecture | Mermaid | System components | block-beta |
sequence | Mermaid | API calls, interactions | sequenceDiagram |
mindmap | Mermaid | Knowledge structure | mindmap |
state | Mermaid | State transitions | stateDiagram |
concept | Excalidraw / Gemini | Abstract concepts | Hand-drawn / Center-radial |
comparison | Excalidraw / Gemini | A vs B, contrasts | Hand-drawn / Left-right split |
data | Gemini | Statistics, trends | Infographic style |
scene | Gemini | Stories, scenarios | Narrative illustration |
metaphor | Gemini | Analogies, symbols | Creative visual |
cover | Gemini | Article cover | 16:9 dark tech |
Type × Composition Reference
| Type | Recommended Composition | Elements |
|---|---|---|
| concept | Center-radial, hierarchy | Core icon + surrounding factors |
| process | Horizontal/vertical flow | Nodes + arrows + labels |
| comparison | Left-right / top-bottom split | Two columns + corresponding items |
| data | Chart-style | Numbers prominent + graphical |
| scene | Narrative illustration | Characters + environment + action |
| summary | Card grid, bullet points | Structured layout |
| metaphor | Analogy visual | Creative visual metaphor |
Style System
Built-in Styles
| Style | File | Best For |
|---|---|---|
| Light | styles/style-light.md | Content illustrations (default) |
| Dark | styles/style-dark.md | Cover images, marketing |
| Minimal | styles/style-minimal.md | Technical docs, whitepapers |
| Cover | styles/style-cover.md | YouTube thumbnails, social covers (cover mode) |
Content Illustrations: Light Style
- 3:4 portrait format
- Light gray background
#F8F9FA - Flat geometric + thin lines
- See
styles/style-light.md
Cover Images: Dark Tech Style
- 16:9 landscape format
- Deep blue gradient background
- Line icons + glassmorphism
- No text
- See
styles/style-dark.md
Custom Styles
Add your own style by creating styles/style-{name}.md and use it with --style {name}.
File Structure
smart-illustrator/
├── SKILL.md # Skill definition (Claude Code entry)
├── CLAUDE.md # Project rules (style sync, JSON format)
├── README.md
├── README.zh-CN.md # Chinese documentation
├── LICENSE
├── scripts/
│ ├── generate-image.ts # Gemini single image generation
│ ├── batch-generate.ts # Gemini batch generation (2K, resume support)
│ ├── mermaid-export.ts # Mermaid diagram to PNG export
│ ├── excalidraw-export.ts # Excalidraw diagram to PNG export
│ └── package.json # Script dependencies (Excalidraw export)
├── styles/
│ ├── brand-colors.md # Brand palette (customizable)
│ ├── style-light.md # Light style Gemini prompt (default)
│ ├── style-dark.md # Dark style Gemini prompt
│ ├── style-minimal.md # Minimal style Gemini prompt
│ └── style-cover.md # Cover/thumbnail style (cover mode)
└── references/
├── slides-prompt-example.json # PPT mode JSON format example
├── cover-best-practices.md # YouTube thumbnail best practices
└── excalidraw-guide.md # Excalidraw JSON specificationCustomization
Want to use your own brand style?
Option 1: Modify Existing Styles
1. Edit styles/brand-colors.md with your colors 2. Sync color values in styles/style-*.md files 3. Done! Your Skill now has your own brand identity.
Option 2: Add New Styles
1. Create styles/style-{name}.md (e.g., style-corporate.md) 2. Follow the format in existing style files 3. Use with --style {name}
Example: Custom Brand Palette
Edit styles/brand-colors.md:
## Core / 核心色
| Your Brand Color | `#XXXXXX` | Your main color |
## Accent / 点缀色
| Your Accent | `#XXXXXX` | Your accent color |Option 3: Customize AI Prompts
All AI prompts are centralized in prompts/ directory for easy customization:
prompts/
├── README.md # Prompt management guide
├── varied-styles.md # Style hints for Varied mode
└── learning-analysis.md # Cover learning analysis promptTo customize:
1. Edit the Markdown files directly (no code changes needed) 2. Changes take effect immediately on next generation 3. See prompts/README.md for detailed instructions
Difference from Style System:
- Style files (
styles/*.md): Define core design rules (composition, colors, constraints) - Prompt templates (
prompts/*.md): Define generation strategies (style hints, analysis focus) - Both work together: styles set the foundation, prompts add nuance
Configuration Reference
Mermaid Engine Parameters
| Parameter | Value | Description |
|---|---|---|
-s | 3 | 3x scale for high-resolution output |
-w | 1600 | 1600px width |
-b | white | White background (light style) |
-t | neutral | Neutral theme |
Full command:
mmdc -i input.mmd -o output.png -s 3 -w 1600 -b whiteMermaid Best Practices:
This skill follows the style guidelines from mermaid-visualizer:
- Use
subgraph id["Display Name"]format for groups with spaces - Reference nodes by ID, not display text
- Avoid
number. spacepatterns (use①②③or(1)(2)(3)instead) - Apply consistent color coding per layer/category
- Use
direction LRinside subgraphs for horizontal layouts
Gemini Engine Parameters
| Parameter | Value | Description |
|---|---|---|
| Model | gemini-3-pro-image-preview | Best for illustrations |
| Resolution | 2K (2816×1536) | High-res output via imageConfig.imageSize |
| Content Aspect | 3:4 portrait | Optimized for article embedding |
| Cover Aspect | 16:9 landscape | Platform-ready cover format |
| Cover Text | None | Clean visual, title shown by platform |
Brand Color Palette (Default: Axton Brand)
| Name | Hex | Usage |
|---|---|---|
| Deep Space Violet | #2F2B42 | Core color, dark backgrounds |
| Amber | #F59E0B | Accent, highlights, Skills layer |
| Sky Blue | #38BDF8 | Accent, secondary highlights, Agents layer |
| Light Gray | #F8F9FA | Light backgrounds, neutral elements |
Style Files
| File | Purpose | Aspect |
|---|---|---|
styles/style-light.md | Content illustrations (default) | 3:4 portrait |
styles/style-dark.md | Cover images | 16:9 landscape |
styles/style-minimal.md | Technical docs | 3:4 portrait |
styles/style-cover.md | YouTube/social covers | Platform-specific |
styles/brand-colors.md | Color palette reference | - |
Cost
When using Gemini API for auto-generation:
| Model | Price | Quality |
|---|---|---|
gemini-3-pro-image-preview | $0.134/image ≈ ¥1/image | 2K (default) |
Get API Key: https://aistudio.google.com/apikey
Contributing
Contributions welcome (low-maintenance project):
- Reproducible bug reports (input + output + steps + environment)
- Documentation improvements
- Small PRs (fixes/docs)
Note: Feature requests may not be acted on due to limited maintenance capacity.
Acknowledgments
This project builds upon these excellent tools:
- Claude Code - Anthropic's AI coding assistant
- Mermaid - Diagramming and charting tool
- Mermaid CLI - Command line interface for Mermaid
- Excalidraw - Virtual whiteboard for hand-drawn diagrams
- Playwright - Browser automation (for Excalidraw PNG export)
- Gemini API - Google's image generation API
- Bun - Fast JavaScript runtime
License
MIT License - see LICENSE for details.
---
Author
Axton Liu - AI Educator & Creator
- Website: axtonliu.ai
- YouTube: @AxtonLiu
- Twitter/X: @axtonliu
Learn More
- MAPS™ AI Agent Course - Systematic AI agent skills training
- Agent Skills Resource Library - Claude Code Skills collection and guides
- Claude Skills: A Systematic Guide - Complete methodology
- AI Elite Weekly Newsletter - Weekly AI insights
- Free AI Course - Get started with AI
---
MIT License © Axton Liu
Smart Illustrator - 智能配图生成器
English | 简体中文
 
🆕 v1.4.0 — 三引擎系统(2026 年 2 月)
>
新增 Excalidraw 手绘概念图引擎。三级优先级:Gemini → Excalidraw → Mermaid。所有图表引擎默认输出 PNG。查看详情 →
!三引擎架构
Claude Code 的智能文章配图 Skill,采用三引擎系统:根据内容类型自动选择 Gemini(创意视觉)、Excalidraw(手绘图表)或 Mermaid(结构化图表)。
状态说明
状态:实验性
>
- 这是一个公开原型,适用于我的演示场景,但尚未覆盖所有输入规模和边缘情况。
- 输出质量因模型版本和输入结构而异,结果可能有波动。
- 我的主要目标是展示工具和系统如何协同工作,而非维护这个代码库。
- 如遇问题,请提交可复现的案例(输入 + 输出文件 + 复现步骤)。
为什么选择 Smart Illustrator?
为文章配图很费时间:手动设计需要数小时,图库图片缺乏定制性,通用 AI 工具不理解文章结构。Smart Illustrator 结合智能位置检测、三引擎系统(Gemini + Excalidraw + Mermaid)和封面学习,几分钟内生成符合上下文的插图。
适合谁用: Newsletter 作者、YouTube 创作者、技术博客作者、课程讲师。
什么时候用: 需要为文章生成高质量插图、基于最佳实践生成 YouTube 封面图、或保持内容系列视觉风格一致时。
背景:我之前的 Make 工作流版本(含自动配图 + 自动发公众号)
在把这条链路做成 Skill 之前,我已经用 Make 跑通过完整工作流: 搜索 → 写作 → 自动配图 → 微信公众号排版/发布。
如果你更想看"工作流版"的实现原理与设计取舍,这期视频讲得很细: https://youtu.be/TbyJ3imLuXQ
功能特性
- 三引擎系统:根据内容类型自动选择 Gemini、Excalidraw 或 Mermaid
- 智能位置识别:分析文章结构,识别最佳配图位置
- 10+ 配图类型:流程图、时序图、思维导图、概念图、对比图、场景图、隐喻图...
- 可扩展风格系统:浅色、深色、极简、封面,支持自定义风格
- 封面图模式:生成高点击率的 YouTube 缩略图,内置最佳实践
- 多平台尺寸:YouTube、公众号、Twitter、小红书等预设尺寸
- 断点续传:跳过已生成的图片,支持重新生成指定图片
- 品牌可定制:修改
styles/目录即可应用你的品牌风格 - 多后端支持:Gemini API 生成创意视觉(2K 分辨率)、Excalidraw 生成手绘图表、Mermaid CLI 生成结构化图表 — 全部默认输出 PNG
什么是 Skills?
Skills 是 Claude Code 的 prompt 扩展,赋予 Claude 专业能力。与需要复杂配置的 MCP 服务器不同,Skills 是简单的 Markdown 文件,Claude 按需加载。
安装
前置要求
- 已安装 Claude Code CLI
- Bun 运行时(用于脚本)
- Mermaid CLI(用于 Mermaid 图表):
npm install -g @mermaid-js/mermaid-cli - Excalidraw 导出依赖(可选,用于 Excalidraw 图表):
cd ~/.claude/skills/smart-illustrator/scripts && npm install && npx playwright install firefox - Gemini API Key(可选,用于创意视觉):https://aistudio.google.com/apikey
方式 A:手动安装(推荐)
# 克隆到 Claude Code Skills 目录
git clone https://github.com/axtonliu/smart-illustrator.git ~/.claude/skills/smart-illustrator方式 B:复制单独文件
# 如果只需要 Skill 本身,不需要脚本
cp -r smart-illustrator/SKILL.md ~/.claude/skills/smart-illustrator/
cp -r smart-illustrator/styles ~/.claude/skills/smart-illustrator/使用方法
基本用法
# 分析文章并自动生成配图(默认)
/smart-illustrator path/to/article.md
# 只输出 prompt,不自动生成图片
/smart-illustrator path/to/article.md --prompt-only
# 指定风格(从 styles/ 目录加载)
/smart-illustrator path/to/article.md --style light # 浅色风格(默认)
/smart-illustrator path/to/article.md --style dark # 深色高对比风格
/smart-illustrator path/to/article.md --style minimal # 极简风格
# 列出可用风格
/smart-illustrator --list-styles
# 不生成封面图
/smart-illustrator path/to/article.md --no-cover
# 指定配图数量
/smart-illustrator path/to/article.md --count 5参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
--mode | article | 模式:article、slides 或 cover |
--engine | auto | 引擎:auto、gemini、excalidraw 或 mermaid |
--mermaid-embed | false | 嵌入 Mermaid 代码块而非导出 PNG |
--platform | youtube | 封面平台:youtube/wechat/twitter/xiaohongshu/landscape/square |
--topic | - | 封面主题(可替代文章路径,仅 cover 模式) |
--description | - | 封面视觉方向(仅 cover 模式) |
--prompt-only | false | 只输出 prompt,不调用 API 生成图片 |
--style | light | 风格名称,加载 styles/style-{name}.md |
--list-styles | - | 列出 styles/ 目录下所有可用风格 |
--no-cover | false | 不生成封面图(article 模式) |
--count | 自动 | 配图数量(根据文章长度自动判断) |
--ref | - | 参考图路径(可多次使用,最多 3 张) |
--ref-weight | 1.0 | 参考图权重(0.0-1.0,暂未实现) |
-c, --candidates | 1 | 生成候选图数量(最多 4 张),用于 Quality Router |
配图数量建议
| 文章长度 | 建议数量 |
|---|---|
| 短文(< 1000 字) | 1-2 张 |
| 中篇(1000-3000 字) | 2-4 张 |
| 长文(> 3000 字) | 4-6 张 |
| 教程/指南 | 每个主要步骤 1 张 |
输出文件
article.md # 原文
article-image.md # 带配图的文章(核心输出)
article-cover.png # 封面图(16:9)
article-image-01.png # 正文配图(3:4)
article-image-02.png
article-image-03.png脚本手动使用
generate-image.ts(单张图片)
export GEMINI_API_KEY=your_key
# 从 prompt 文本生成
npx -y bun ~/.claude/skills/smart-illustrator/scripts/generate-image.ts \
--prompt "一张展示概念的图..." \
--output image.png
# 从 prompt 文件生成
npx -y bun ~/.claude/skills/smart-illustrator/scripts/generate-image.ts \
--prompt-file prompt.md \
--output image.png| 选项 | 说明 |
|---|---|
-p, --prompt | 图片描述文本 |
-f, --prompt-file | 从文件读取 prompt |
-o, --output | 输出路径(默认:generated.png) |
-m, --model | 模型(默认:gemini-3-pro-image-preview) |
-r, --ref | 参考图路径(可多次使用,最多 3 张) |
--ref-weight | 参考图权重(0.0-1.0,暂未实现) |
-c, --candidates | 生成候选图数量(最多 4 张) |
--size | 图片尺寸:default 或 2k |
--provider | API 提供商:openrouter 或 gemini |
batch-generate.ts(批量生成)
export GEMINI_API_KEY=your_key
npx -y bun ~/.claude/skills/smart-illustrator/scripts/batch-generate.ts \
--config slides.json \
--output-dir ./images \
--prefix SKILL_01| 选项 | 说明 |
|---|---|
-c, --config | JSON 配置文件(必需) |
-o, --output-dir | 输出目录(默认:./illustrations) |
-m, --model | 模型(默认:gemini-3-pro-image-preview) |
-d, --delay | 请求间隔毫秒数(默认:2000) |
-p, --prefix | 文件名前缀(默认:从配置文件名提取) |
-r, --regenerate | 重新生成指定图片(如 "3" 或 "3,5,7") |
-f, --force | 强制重新生成所有图片(忽略已存在的) |
断点续传:默认会跳过输出目录中已存在的图片。这允许中断后继续生成,无需重新生成已完成的图片。
输出:{prefix}-01.png、{prefix}-02.png 等。
mermaid-export.ts(Mermaid 转 PNG)
# 从 .mmd 文件导出
npx -y bun ~/.claude/skills/smart-illustrator/scripts/mermaid-export.ts \
--input diagram.mmd \
--output diagram.png
# 从内联内容导出
npx -y bun ~/.claude/skills/smart-illustrator/scripts/mermaid-export.ts \
--content "flowchart LR
A[开始] --> B[结束]" \
--output simple.png \
--theme dark| 选项 | 说明 |
|---|---|
-i, --input | 输入 .mmd 文件路径 |
-c, --content | Mermaid 图表内容(替代方式) |
-o, --output | 输出路径(默认:output.png) |
-t, --theme | 主题:light(默认)或 dark |
-w, --width | 图片宽度(像素) |
-H, --height | 图片高度(像素) |
PPT/Slides 生成模式
除了文章配图,此 Skill 还能为 PPT/Keynote 生成批量信息图。
适用场景
| 模式 | 用途 | 输出 |
|---|---|---|
| Article 模式 | 博客、Newsletter | 3-5 张配图插入文章 |
| Slides 模式 | 视频 B-roll、演示文稿 | 8-15 张独立信息图 |
批量生成的 JSON 格式
使用 pictures[] 数组格式,配合明确的批处理规则:
{
"instruction": "请为我绘制 7 张图片(generate 7 images)。你是一位「信息图绘制者」。请逐条执行 pictures 数组:每个 id 对应 1 张独立的 16:9 信息图,严禁合并,严禁只输出文字描述。",
"batch_rules": {
"total": 7,
"one_item_one_image": true,
"aspect_ratio": "16:9",
"do_not_merge": true
},
"fallback": "如果无法一次生成全部图片:请输出 7 条独立的单图绘图指令...",
"style": "[完整的 style prompt - 见 styles/style-light.md]",
"pictures": [
{ "id": 1, "topic": "封面", "content": "课程名称\n\n章节标题\n\n学习目标..." },
{ "id": 2, "topic": "核心概念", "content": "[原始内容]" }
]
}关键规则
1. 使用 `pictures[]` 数组 - 数组结构有助于 Gemini 进入"逐条执行"模式进行批量生成。
2. 添加图片生成触发词 - 必须包含"请为我绘制 N 张图片(generate N images)"来触发图片生成模式。
3. 角色设定为"绘制者"而非"导演" - 使用"信息图绘制者"(illustrator)而非"视觉导演"(director)来触发实际绘图行为。
4. instruction 与 style 分离 - instruction = 任务 + 角色;style = 纯视觉规则。
5. 传递完整 style - 包含 styles/style-light.md 的完整内容,不要简化。
6. 内容粒度 - 按信息密度判断,而非机械地按 H2 标题分割。
封面品牌规则(PPT 模式)
对于课程/系列内容,封面 slide(id: 1)应包含:
{
"id": 1,
"topic": "封面",
"content": "Agent Skills 完全指南\n\n第4节:渐进式披露与 Description 优化\n\n学习目标:理解 Progressive Disclosure 机制"
}结构:
- 系列名称:如 "Agent Skills 完全指南"
- 章节序号:如 "第4节"
- 章节标题:如 "渐进式披露与 Description 优化"
- 学习目标(可选)
输出方式选项(适用于所有模式)
--prompt-only 是全局选项,适用于 Article、Slides、Cover 等所有模式:
| 输出方式 | 参数 | 说明 | 需要 API |
|---|---|---|---|
| 直接生成图片 | 默认 | 调用 Gemini API 生成图片 | ✅ 需要 |
| 输出 JSON Prompt | --prompt-only | 复制到 Gemini Web 手动生成 | ❌ 不需要 |
示例组合:
# Slides 模式 + 直接生成图片(需要 API)
/smart-illustrator script.md --mode slides
# Slides 模式 + 只输出 JSON(无需 API)
/smart-illustrator script.md --mode slides --prompt-only
# Article 模式 + 直接生成图片(需要 API)
/smart-illustrator article.md
# Article 模式 + 只输出 JSON(无需 API)
/smart-illustrator article.md --prompt-only手动批量生成(用于 JSON prompt 输出后):
export GEMINI_API_KEY=your_key
npx -y bun ~/.claude/skills/smart-illustrator/scripts/batch-generate.ts \
--config slides.json \
--output-dir ./images完整示例见 references/slides-prompt-example.json。
💡 使用提示:在 Gemini 中使用 JSON 批量生成图片并非 100% 成功率。如果遇到生成失败,可以:
1. 重试 1-3 次,通常能成功
2. 如果多次重试仍失败,将 JSON 中的 pictures 逐条复制到 Gemini 单独生成参考图模式(Style-lock)
使用参考图引导生成图片的视觉风格,确保系列图片风格一致。
# 使用单张参考图
/smart-illustrator article.md --ref style-ref.png
# 使用多张参考图(最多 3 张)
/smart-illustrator article.md --ref ref1.png --ref ref2.png
# 结合 Quality Router 使用
/smart-illustrator article.md --ref style-ref.png -c 2限制:
- 参考图功能仅支持 Gemini API(OpenRouter 不支持多模态输入)
- 使用参考图时会自动从 OpenRouter 切换到 Gemini
- 最多 3 张参考图
- 参考图建议尺寸:512-1024px,过大会增加 API 延迟和费用
---
Quality Router(多候选图生成)
生成多张候选图供用户选择,提高最终图片质量。
# 生成 2 张候选图
/smart-illustrator article.md --candidates 2
# 简写形式
/smart-illustrator article.md -c 2
# 结合参考图使用
/smart-illustrator article.md --ref style-ref.png -c 2输出:
- 单候选(默认):
output.png - 多候选:
output-1.png、output-2.png
工作流程: 1. 按指定数量生成图片 2. 输出所有候选图路径 3. 用户选择最佳结果
建议:
- 日常使用 1 张(节省成本)
- 重要场景(封面图、课程宣传)使用 2 张
- 最多支持 4 张
---
配置文件(风格复用)
通过配置文件保存常用参数,实现系列内容(课程、Newsletter)的风格一致性。
配置文件位置
优先级:CLI 参数 > 项目级配置 > 用户级配置
| 位置 | 路径 | 用途 |
|---|---|---|
| 项目级 | {工作目录}/.smart-illustrator/config.json | 特定项目的风格配置(如某个课程系列) |
| 用户级 | ~/.smart-illustrator/config.json | 用户全局默认风格 |
配置文件格式
{
"references": [
"./refs/style-ref-01.png",
"./refs/style-ref-02.png"
]
}支持的配置项:
references:参考图路径数组(相对路径相对于配置文件所在目录)
使用示例
# 首次配置:为课程系列设置风格
cd ~/my-course
/smart-illustrator article-01.md --ref ./refs/style-1.png --save-config
# 之后生成:自动使用配置
/smart-illustrator article-02.md # 自动应用参考图
# 临时覆盖:使用不同参考图
/smart-illustrator article-03.md --ref ./other-ref.png
# 完全忽略配置
/smart-illustrator article-04.md --no-config
# 保存到用户级配置(全局默认)
/smart-illustrator article.md --ref ./my-style.png --save-config-global配置加载规则
1. 读取用户级配置(如果存在) 2. 读取项目级配置(如果存在,覆盖用户级) 3. 应用命令行参数(覆盖配置文件)
典型场景:
- 课程系列:项目目录下保存
.smart-illustrator/config.json,所有章节自动使用统一风格 - 个人默认:用户目录下保存
~/.smart-illustrator/config.json,作为全局默认风格 - 临时调整:使用
--ref参数临时覆盖配置,不影响保存的配置
---
Cover 模式(封面图生成)
为 YouTube、公众号、Twitter 等生成高点击率封面图,内置 YouTuber 最佳实践。
# 从文章生成 YouTube 封面
/smart-illustrator path/to/article.md --mode cover --platform youtube
# 指定主题生成
/smart-illustrator --mode cover --platform youtube --topic "Claude 4 深度评测"
# 指定视觉方向
/smart-illustrator --mode cover --platform wechat --description "对比图 + 科技感"支持的平台
所有输出均为 2K 分辨率(如 16:9 约为 2816×1584)。
| 平台 | 代码 | 宽高比 |
|---|---|---|
| YouTube | youtube | 16:9 |
| 公众号 | wechat | 2.35:1 |
| Twitter/X | twitter | 1.91:1 |
| 小红书 | xiaohongshu | 3:4 |
| 通用横版 | landscape | 16:9 |
| 通用方形 | square | 1:1 |
封面设计最佳实践
本项目内置 YouTube 封面设计最佳实践(基于头部 YouTuber 研究)。
核心原则:
- 3 秒法则:极短时间传达主题+价值+紧迫感
- 高对比设计:深背景+亮主体,明度差 ≥50%
- 视觉焦点唯一:一张封面只有一个重点
- 文字精简有力:3-6 字,粗体无衬线
- 好奇心缺口:让人不得不点
详细指南:完整的设计原则、平台规格、A/B 测试清单见 references/cover-best-practices.md
💡 这些实践由 @axtonliu 研究总结。
如在项目中使用,欢迎注明来源并分享你的成果。
技术内容的视觉隐喻
| 概念 | 隐喻 |
|---|---|
| AI 助手 | 协作的两只手、对话气泡组合 |
| 效率提升 | 向上箭头、阶梯、火箭轨迹 |
| 自动化 | 齿轮组、流水线节点 |
| 学习成长 | 种子→大树、阶梯上升 |
| 问题解决 | 迷宫出口、拼图完成 |
封面学习功能(持续优化)
从高表现封面中学习,持续提升生成质量:
# 学习一张高表现封面
/smart-illustrator --learn-cover ./my-best-thumbnail.png
# 带备注学习(如 CTR 数据)
/smart-illustrator --learn-cover ./cover.png --learn-note "CTR 8.5%,震惊表情效果好"
# 查看学习记录
/smart-illustrator --show-learnings
# 生成多风格封面(自动应用学习模式)
/smart-illustrator --mode cover --platform youtube --topic "Claude 4 评测" --varied学习工作流程:
1. AI 自动分析:构图、配色、文字使用、情绪表达、视觉焦点 2. 提取成功模式:记录值得复用的设计要素 3. 持久化学习:保存到 ~/.smart-illustrator/cover-learnings.md 4. 自动应用:后续生成封面时自动加载学到的模式
Varied 模式(多风格生成):
- 一次生成 2 张不同风格的封面供选择
- Candidate 1:戏剧性高对比(强视觉冲击,适合娱乐/震惊类内容)
- Candidate 2:极简专业(克制高级,适合技术/教育类内容)
- 两种风格都会自动应用历史学习模式
Prompt 自定义(高级)
所有 AI prompt 集中在 prompts/ 目录,便于自定义和迭代:
prompts/
├── README.md # Prompt 管理说明
├── varied-styles.md # Varied 模式的两种风格提示
└── learning-analysis.md # 封面学习分析 prompt修改 prompt:
- 直接编辑 Markdown 文件,无需改代码
- 修改后自动生效,无需重启
- 详细说明见
prompts/README.md
与风格系统关系:
- 风格文件(
styles/*.md):定义核心设计规则(构图、配色、禁忌等) - Prompt 模板(
prompts/*.md):定义生成策略(风格提示、分析重点等) - 两者配合使用,风格文件是基础,prompt 模板是补充
---
智能位置识别
Skill 分析文章结构,识别最佳配图位置:
| 信号 | 配图价值 |
|---|---|
| 抽象概念首次出现 | 高 - 帮助建立心智模型 |
| 流程/步骤描述 | 高 - 可视化比文字更清晰 |
| 对比/选择论述 | 高 - 左右对比一目了然 |
| 数据/统计引用 | 中 - 数字可视化更有冲击力 |
| 章节转折点 | 中 - 提供视觉喘息 |
| 情感/故事高潮 | 中 - 增强共鸣 |
---
三引擎系统
Skill 根据内容自动选择最佳渲染引擎,三级优先级:
| 优先级 | 引擎 | 适用场景 | 输出 |
|---|---|---|---|
| 1 | Gemini | 创意视觉(隐喻图、场景图、信息图) | PNG (2K) |
| 2 | Excalidraw | 手绘概念图、对比图、简单流程 | PNG |
| 3 | Mermaid | 复杂结构化图表(流程图、时序图、架构图) | PNG |
选择逻辑:
- 需要隐喻、情感、创意表达 → Gemini
- 需要手绘/非正式风格,或简单概念关系 → Excalidraw
- 复杂结构化流程/架构 → Mermaid
配图类型
| 类型 | 引擎 | 适用场景 | 语法/风格 |
|---|---|---|---|
process | Mermaid | 复杂工作流 | flowchart |
architecture | Mermaid | 系统组件 | block-beta |
sequence | Mermaid | API 调用、交互 | sequenceDiagram |
mindmap | Mermaid | 知识结构 | mindmap |
state | Mermaid | 状态转换 | stateDiagram |
concept | Excalidraw / Gemini | 抽象概念 | 手绘 / 中心辐射 |
comparison | Excalidraw / Gemini | A vs B、对比 | 手绘 / 左右分栏 |
data | Gemini | 统计、趋势 | 信息图风格 |
scene | Gemini | 故事、场景 | 叙事插画 |
metaphor | Gemini | 类比、象征 | 创意视觉 |
cover | Gemini | 文章封面 | 16:9 深色高对比 |
类型 × 构图参考
| 类型 | 推荐构图 | 元素 |
|---|---|---|
| concept | 中心辐射、层级结构 | 核心图标 + 周围要素 |
| process | 横向/纵向流程 | 节点 + 箭头 + 标签 |
| comparison | 左右/上下分栏 | 两栏 + 对应项 |
| data | 图表风格 | 数字突出 + 图形化 |
| scene | 叙事插画 | 人物 + 环境 + 动作 |
| summary | 卡片网格、要点列表 | 结构化布局 |
| metaphor | 类比画面 | 创意视觉隐喻 |
风格系统
内置风格
| 风格 | 文件 | 适用场景 |
|---|---|---|
| 浅色 | styles/style-light.md | 正文配图(默认) |
| 深色 | styles/style-dark.md | 封面图、营销 |
| 极简 | styles/style-minimal.md | 技术文档、白皮书 |
| 封面 | styles/style-cover.md | YouTube 缩略图、社交封面(cover 模式) |
正文配图:浅色风格
- 3:4 竖版格式
- 浅灰白背景
#F8F9FA - 扁平几何 + 细线勾边
- 详见
styles/style-light.md
封面图:深色高对比风格
- 16:9 横版格式
- 深空紫背景
- 线性图标 + 玻璃拟态
- 无文字
- 详见
styles/style-dark.md
自定义风格
创建 styles/style-{name}.md 文件,然后使用 --style {name} 调用。
文件结构
smart-illustrator/
├── SKILL.md # Skill 定义(Claude Code 入口)
├── CLAUDE.md # 项目规则(风格同步、JSON 格式)
├── README.md # 英文文档
├── README.zh-CN.md # 中文文档
├── LICENSE
├── scripts/
│ ├── generate-image.ts # Gemini 单张图片生成
│ ├── batch-generate.ts # Gemini 批量生成(2K、断点续传)
│ ├── mermaid-export.ts # Mermaid 图表导出为 PNG
│ ├── excalidraw-export.ts # Excalidraw 图表导出为 PNG
│ └── package.json # 脚本依赖(Excalidraw 导出)
├── styles/
│ ├── brand-colors.md # 品牌色板(可自定义)
│ ├── style-light.md # 浅色风格 Gemini Prompt(默认)
│ ├── style-dark.md # 深色风格 Gemini Prompt
│ ├── style-minimal.md # 极简风格 Gemini Prompt
│ └── style-cover.md # 封面图风格(cover 模式)
└── references/
├── slides-prompt-example.json # PPT 模式 JSON 格式示例
├── cover-best-practices.md # YouTube 封面图最佳实践
└── excalidraw-guide.md # Excalidraw JSON 规范自定义
想使用自己的品牌风格?
方式 1:修改现有风格
1. 编辑 styles/brand-colors.md 中的颜色值 2. 同步更新 styles/style-*.md 文件中的颜色 3. 完成!你的 Skill 现在有了自己的品牌标识。
方式 2:添加新风格
1. 创建 styles/style-{name}.md(如 style-corporate.md) 2. 参考现有风格文件的格式编写 3. 使用 --style {name} 调用
示例:自定义品牌色板
编辑 styles/brand-colors.md:
## 核心色
| 你的品牌色 | `#XXXXXX` | 主色调 |
## 点缀色
| 你的强调色 | `#XXXXXX` | 强调色 |配置参考
Mermaid 引擎参数
| 参数 | 值 | 说明 |
|---|---|---|
-s | 3 | 3 倍缩放,高清输出 |
-w | 1600 | 1600px 宽度 |
-b | white | 白色背景(浅色风格) |
-t | neutral | 中性主题 |
完整命令:
mmdc -i input.mmd -o output.png -s 3 -w 1600 -b whiteMermaid 最佳实践:
本 Skill 遵循 mermaid-visualizer 的风格指南:
- 使用 ID 引用节点,不使用显示文本
- 避免
数字. 空格模式(改用①②③或(1)(2)(3)) - 按层级应用一致的颜色编码
Gemini 引擎参数
| 参数 | 值 | 说明 |
|---|---|---|
| 模型 | gemini-3-pro-image-preview | 最适合配图 |
| 分辨率 | 2K (2816×1536) | 通过 imageConfig.imageSize 设置高分辨率 |
| 正文配图比例 | 3:4 竖版 | 适合嵌入文章 |
| 封面图比例 | 16:9 横版 | 平台就绪格式 |
| 封面图文字 | 无 | 纯视觉,标题由平台显示 |
品牌色板(默认:Axton Brand)
| 名称 | 色值 | 用途 |
|---|---|---|
| 深空紫 | #2F2B42 | 核心色,深色背景 |
| 琥珀橙 | #F59E0B | 强调色,高亮,Skills 层 |
| 天空蓝 | #38BDF8 | 强调色,次级高亮,Agents 层 |
| 浅灰白 | #F8F9FA | 浅色背景,中性元素 |
风格文件
| 文件 | 用途 | 比例 |
|---|---|---|
styles/style-light.md | 正文配图(默认) | 3:4 竖版 |
styles/style-dark.md | 封面图 | 16:9 横版 |
styles/style-minimal.md | 技术文档 | 3:4 竖版 |
styles/brand-colors.md | 色板参考 | - |
成本
使用 Gemini API 自动生成时:
| 模型 | 价格 | 质量 |
|---|---|---|
gemini-3-pro-image-preview | $0.134/张 ≈ ¥1/张 | 2K(默认) |
获取 API Key:https://aistudio.google.com/apikey
贡献
欢迎贡献(低维护项目):
- 可复现的 bug 报告(输入 + 输出 + 步骤 + 环境)
- 文档改进
- 小型 PR(修复/文档)
注意:由于维护能力有限,功能请求可能不会被处理。
致谢
本项目基于以下优秀工具构建:
- Claude Code - Anthropic 的 AI 编程助手
- Mermaid - 图表绘制工具
- Mermaid CLI - Mermaid 命令行接口
- Excalidraw - 手绘风格虚拟白板
- Playwright - 浏览器自动化(用于 Excalidraw PNG 导出)
- Gemini API - Google 的图片生成 API
- Bun - 快速 JavaScript 运行时
许可证
MIT 许可证 - 详见 LICENSE。
---
作者
Axton Liu - AI 教育者 & 创作者
- 网站:axtonliu.ai
- YouTube:@AxtonLiu
- Twitter/X:@axtonliu
了解更多
- MAPS™ AI 系统化训练营 - 系统化 AI Agent 技能训练
- Agent Skills 资源库 - Claude Code Skills 合集与指南
- Claude Skills 万字长文:从指令到资产的系统化构建指南 - 完整方法论
- AI 精英周刊 Newsletter - 每周 AI 洞察
- 免费 AI 课程 - 开始学习 AI
---
MIT License © Axton Liu
YouTube 封面图最佳实践
基于头部 YouTuber 研究总结的高点击率封面设计原则。
核心原则:3 秒法则
观众在 3 秒内决定是否点击。封面必须在极短时间内传达: 1. 这是关于什么的?(主题) 2. 对我有什么价值?(利益点) 3. 为什么要现在看?(紧迫感/好奇心)
---
一、高对比设计
色彩对比
| 策略 | 说明 | 示例 |
|---|---|---|
| 深色背景 + 亮色主体 | 主体更突出 | 深紫背景 + 白色文字 |
| 互补色组合 | 视觉冲击力强 | 橙 + 蓝、紫 + 黄 |
| 避免同色系 | 防止视觉混淆 | 不用蓝背景 + 蓝文字 |
明度对比
- 主要元素与背景明度差 ≥ 50%
- 文字必须清晰可读(即使在手机小屏幕)
---
二、人脸与表情
为什么人脸有效
- 人类大脑对面部有本能关注
- 表情传递情绪,建立情感连接
- 眼神方向可引导视线
表情类型与场景
| 表情 | 适用场景 | 效果 |
|---|---|---|
| 惊讶/震惊 | 揭秘、新发现 | 触发好奇心 |
| 困惑/疑问 | 问题解答 | 引发共鸣 |
| 兴奋/开心 | 好消息、成功案例 | 传递正能量 |
| 认真/专注 | 教程、深度内容 | 建立专业感 |
注意事项
- 表情要真实自然,过度夸张会让人反感
- 技术内容可不用人脸,改用视觉隐喻
---
三、文字设计
字数控制
| 平台 | 建议字数 | 原因 |
|---|---|---|
| YouTube | 3-6 个字 | 手机端缩略图很小 |
| 公众号 | 5-10 个字 | 封面通常更大 |
字体选择
- 粗体无衬线字体(如 思源黑体 Bold)
- 避免花哨字体,影响可读性
- 中文用方正、思源;英文用 Montserrat、Poppins
排版技巧
- 文字靠左或居中,避免右下角(会被时间戳遮挡)
- 关键词放大/变色强调
- 文字描边或加阴影增强可读性
---
四、好奇心缺口(Curiosity Gap)
让观众"不得不点"的心理技巧:
有效策略
| 策略 | 示例 | 原理 |
|---|---|---|
| 数字对比 | "从 0 到 10K" | 量化结果引发好奇 |
| 问题悬念 | "为什么 90% 的人都错了?" | 挑战认知 |
| 揭秘暗示 | "没人告诉你的真相" | FOMO 心理 |
| 转折暗示 | "我以为...结果..." | 故事性吸引 |
避免陷阱
- ❌ 标题党(承诺无法兑现)
- ❌ 虚假紧迫感
- ❌ 过度夸张("史上最强")
- ✅ 诚实但有吸引力
---
五、视觉层级
三层结构
1. 第一层:主体(人脸/核心视觉)- 占 40-60% 2. 第二层:关键文字 - 3-6 字 3. 第三层:背景/氛围 - 不抢注意力
视觉焦点
- 只有一个视觉焦点
- 其他元素服务于焦点
- 使用引导线将视线引向焦点
---
六、平台尺寸规格
| 平台 | 尺寸 | 比例 | 安全区域 |
|---|---|---|---|
| YouTube | 1280×720 | 16:9 | 右下角避开时长标签 |
| 公众号封面 | 900×383 | 2.35:1 | 居中重要内容 |
| Twitter/X 卡片 | 1200×628 | 1.91:1 | 中心区域 |
| 小红书 | 1080×1440 | 3:4 | 上半部放重点 |
---
七、A/B 测试检查清单
发布前用这个清单检查:
- [ ] 缩小到手机屏幕大小,文字是否清晰?
- [ ] 3 秒内能否理解主题?
- [ ] 颜色对比是否足够?
- [ ] 是否有且只有一个视觉焦点?
- [ ] 标题是否触发好奇心?
- [ ] 是否避开了平台标签区域?
---
八、技术内容特殊策略
对于 AI/编程/技术类内容,人脸不是必须的,可以用:
替代方案
| 策略 | 说明 | 适用 |
|---|---|---|
| 视觉隐喻 | 用具象图形表达抽象概念 | 概念解释 |
| 对比图 | Before/After 效果 | 工具评测 |
| 数字突出 | 放大关键数据 | 性能对比 |
| 界面截图 | 产品实际界面 | 教程演示 |
| 图标组合 | 多个 icon 组成概念 | 工具合集 |
视觉隐喻速查
| 概念 | 隐喻 |
|---|---|
| AI 助手 | 协作的两只手、对话气泡 |
| 效率提升 | 沙漏、火箭、向上箭头 |
| 自动化 | 齿轮、流水线、连接节点 |
| 数据/分析 | 图表、放大镜、仪表盘 |
| 学习/成长 | 阶梯、种子→大树 |
| 问题→解决 | 迷宫出口、拼图完成 |
---
参考案例
高点击率封面特征
1. Fireship - 极简图标 + 大字标题 + 高对比色 2. Theo - 人脸表情 + 简短文字 + 紫色品牌色 3. MKBHD - 产品特写 + 干净背景 + 品牌一致性
共同点
- 品牌色彩一致
- 简洁不杂乱
- 文字精简有力
- 视觉焦点清晰
Excalidraw Export Dialog - Playwright Selectors Reference
Source: Excalidraw source code analysis (2026-02-14)
Repository: https://github.com/excalidraw/excalidraw
This document provides actionable Playwright selectors for automating the Excalidraw export dialog controls.
Component Structure
The ImageExportDialog uses custom <Switch> and <RadioGroup> components that render standard HTML form controls.
---
1. Scale Selector
Component: <RadioGroup name="exportScale" />
Available Values
1= 1× scale2= 2× scale3= 3× scale
Playwright Selectors
// Select the radio group container
const scaleGroup = page.locator('.RadioGroup');
// Select a specific scale by value (via label text)
await page.locator('.RadioGroup__choice:has-text("2×") input').click();
// OR: Select by input name and check by index
const scale2x = page.locator('input[name="exportScale"]').nth(1); // 0-indexed
await scale2x.click();
// OR: Select by active state (to verify current selection)
const activeScale = page.locator('.RadioGroup__choice.active input');Recommended Approach
async function setExportScale(page: Page, scale: 1 | 2 | 3) {
await page.locator(`.RadioGroup__choice:has-text("${scale}×") input`).click();
}---
2. Dark Mode Toggle
Component: <Switch name="exportDarkModeSwitch" />
Playwright Selectors
// Select by input name (most reliable)
const darkModeSwitch = page.locator('input[name="exportDarkModeSwitch"]');
// OR: Select by ID (same as name)
const darkModeSwitch = page.locator('#exportDarkModeSwitch');
// Check if currently enabled
const isChecked = await darkModeSwitch.isChecked();
// Toggle the switch
await darkModeSwitch.click();
// Set to specific state
if (!await darkModeSwitch.isChecked()) {
await darkModeSwitch.click(); // Enable
}Recommended Approach
async function setDarkMode(page: Page, enabled: boolean) {
const toggle = page.locator('input[name="exportDarkModeSwitch"]');
const current = await toggle.isChecked();
if (current !== enabled) {
await toggle.click();
}
}---
3. Background Toggle
Component: <Switch name="exportBackgroundSwitch" />
Playwright Selectors
// Select by input name
const backgroundSwitch = page.locator('input[name="exportBackgroundSwitch"]');
// OR: Select by ID
const backgroundSwitch = page.locator('#exportBackgroundSwitch');
// Check current state
const hasBackground = await backgroundSwitch.isChecked();
// Toggle
await backgroundSwitch.click();Recommended Approach
async function setBackground(page: Page, enabled: boolean) {
const toggle = page.locator('input[name="exportBackgroundSwitch"]');
const current = await toggle.isChecked();
if (current !== enabled) {
await toggle.click();
}
}---
4. Embed Scene Toggle
Component: <Switch name="exportEmbedSwitch" />
Playwright Selectors
// Select by input name
const embedSwitch = page.locator('input[name="exportEmbedSwitch"]');
// OR: Select by ID
const embedSwitch = page.locator('#exportEmbedSwitch');
// Check if scene data will be embedded
const willEmbed = await embedSwitch.isChecked();
// Toggle
await embedSwitch.click();Recommended Approach
async function setEmbedScene(page: Page, enabled: boolean) {
const toggle = page.locator('input[name="exportEmbedSwitch"]');
const current = await toggle.isChecked();
if (current !== enabled) {
await toggle.click();
}
}---
Complete Implementation Example
import { Page } from 'playwright';
interface ExportDialogOptions {
scale?: 1 | 2 | 3;
darkMode?: boolean;
background?: boolean;
embedScene?: boolean;
}
async function configureExportDialog(
page: Page,
options: ExportDialogOptions
): Promise<void> {
// Wait for dialog to be visible
await page.locator('.ImageExportDialog').waitFor({ state: 'visible' });
// Set scale
if (options.scale !== undefined) {
await page
.locator(`.RadioGroup__choice:has-text("${options.scale}×") input`)
.click();
}
// Set dark mode
if (options.darkMode !== undefined) {
const toggle = page.locator('input[name="exportDarkModeSwitch"]');
const current = await toggle.isChecked();
if (current !== options.darkMode) {
await toggle.click();
}
}
// Set background
if (options.background !== undefined) {
const toggle = page.locator('input[name="exportBackgroundSwitch"]');
const current = await toggle.isChecked();
if (current !== options.background) {
await toggle.click();
}
}
// Set embed scene
if (options.embedScene !== undefined) {
const toggle = page.locator('input[name="exportEmbedSwitch"]');
const current = await toggle.isChecked();
if (current !== options.embedScene) {
await toggle.click();
}
}
// Small delay to ensure state updates propagate
await page.waitForTimeout(300);
}
// Usage
await configureExportDialog(page, {
scale: 2,
darkMode: false,
background: true,
embedScene: false,
});---
Export Button Selectors
After configuring the dialog, you need to trigger the export:
// PNG export button
const pngButton = page.locator('button[aria-label="Export to PNG"]');
await pngButton.click();
// SVG export button
const svgButton = page.locator('button[aria-label="Export to SVG"]');
await svgButton.click();---
Important Notes
No data-testid Attributes
Excalidraw does not provide data-testid attributes for these controls. The selectors rely on:
nameattributes (for switches)- CSS class names (for radio groups)
aria-label(for export format buttons)
Dialog Visibility
Always wait for the export dialog to be visible before interacting with controls:
await page.locator('.ImageExportDialog').waitFor({ state: 'visible' });State Verification
For switches, always check current state before toggling to avoid race conditions:
const current = await toggle.isChecked();
if (current !== desiredState) {
await toggle.click();
}Timing Considerations
- After clicking toggles, allow ~300ms for React state updates
- After setting all options, wait before triggering export
- Use
page.waitForEvent('download')for reliable file capture
---
Related Controls
Main Menu Trigger
const menuButton = page.locator('[data-testid="main-menu-trigger"]');Export Menu Item
const exportButton = page.locator('[data-testid="image-export-button"]');File Load Button
const loadButton = page.locator('[data-testid="load-button"]');---
Testing Checklist
- [ ] Scale selector changes preview size
- [ ] Dark mode toggle affects preview theme
- [ ] Background toggle shows/hides canvas background
- [ ] Embed scene toggle affects file size (embedded = larger)
- [ ] Export button triggers download with correct settings
- [ ] Settings persist across dialog open/close (within session)
---
References
- Excalidraw source: ImageExportDialog.tsx
- Switch component: Switch.tsx
- RadioGroup component: RadioGroup.tsx
Excalidraw JSON 规范(Smart Illustrator 用)
生成 Excalidraw 图表时必须遵循此规范。
JSON 顶层结构
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [...],
"appState": { "gridSize": null, "viewBackgroundColor": "#ffffff" },
"files": {}
}文件扩展名:.excalidraw
元素模板
每个元素必须包含以下字段(禁止添加 frameId、index、versionNonce、rawText):
{
"id": "unique-id",
"type": "rectangle",
"x": 100, "y": 100,
"width": 200, "height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"roundness": { "type": 3 },
"seed": 123456789,
"version": 1,
"isDeleted": false,
"boundElements": null,
"updated": 1,
"link": null,
"locked": false
}boundElements必须为null(不是[])updated必须为1(不是时间戳)strokeStyle:"solid"(实线,默认)|"dashed"(虚线)|"dotted"(点线)。虚线适合表示可选路径、异步流、弱关联等
Text 元素额外属性
{
"text": "显示文本",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "center",
"verticalAlign": "middle",
"containerId": null,
"originalText": "显示文本",
"autoResize": true,
"lineHeight": 1.25
}Arrow 元素
箭头需要额外的 points 数组和端点绑定:
{
"type": "arrow",
"points": [[0, 0], [200, 0]],
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow"
}设计规则
文字
- 所有文本必须使用
fontFamily: 5(Excalifont 手写字体) - 双引号
"→『』,圆括号()→「」 - 字号下限:标题 20-28px,副标题 18-20px,正文 16-18px,注释 14px,绝对禁止 < 14px
lineHeight: 1.25- 禁止 Emoji
文字居中估算
独立 text 元素的 x 是左边缘,需手动计算:
- 英文:
estimatedWidth = text.length * fontSize * 0.5 - 中文:
estimatedWidth = text.length * fontSize * 1.0 - 居中:
x = centerX - estimatedWidth / 2
布局
- 画布范围:0-1200 x 0-800
- 最小形状尺寸:带文字的矩形/椭圆 ≥ 120x60px
- 元素间距:≥ 20-30px
- 四周留白:50-80px padding
色板
文字颜色(strokeColor)
| 用途 | 色值 |
|---|---|
| 标题 | #1e40af |
| 副标题/连接线 | #3b82f6 |
| 正文 | #374151 |
| 强调 | #f59e0b |
形状填充色(backgroundColor, fillStyle: "solid")
| 色值 | 语义 |
|---|---|
#a5d8ff | 输入、数据源、主要节点 |
#b2f2bb | 成功、输出、已完成 |
#ffd8a8 | 警告、待处理、外部依赖 |
#d0bfff | 处理中、中间件、特殊项 |
#ffc9c9 | 错误、关键、告警 |
#fff3bf | 备注、决策、规划 |
#c3fae8 | 存储、数据、缓存 |
#eebefa | 分析、指标、统计 |
区域背景色(大矩形 + opacity: 30)
| 色值 | 语义 |
|---|---|
#dbe4ff | 前端/UI 层 |
#e5dbff | 逻辑/处理层 |
#d3f9d8 | 数据/工具层 |
对比度规则
- 白底文字最浅不低于
#757575 - 浅色填充上用深色变体文字(如浅绿底用
#15803d) - 避免浅灰文字(
#b0b0b0、#999)出现在白底上
Common Mistakes to Avoid
- 文字偏移 — text 的
x是左边缘不是中心,必须用居中公式 - 元素重叠 — 放置前检查与周围元素 ≥ 20px 间距
- 画布留白不足 — 四周留 50-80px padding
- 标题没有居中 — 标题应居中于下方图表整体宽度
- 箭头标签溢出 — 长文字标签超出短箭头,保持标签简短或加大箭头
- 对比度不够 — 文字色不低于
#757575 - 字号太小 — 正文最小 16px,绝对禁止 < 14px
{
"instruction": "请为我绘制 7 张图片(generate 7 images)。你是一位「信息图绘制者」。请逐条执行 pictures 数组:每个 id 对应 1 张独立的 16:9 信息图,严禁合并,严禁只输出文字描述。",
"batch_rules": {
"total": 7,
"one_item_one_image": true,
"aspect_ratio": "16:9",
"do_not_merge": true
},
"fallback": "如果无法一次生成全部图片:请输出 7 条独立的单图绘图指令(编号 1-7),每条可单独执行,必须包含完整 style 和水印要求。",
"style": "你要做的不是把文字摆上去,而是:**先提炼结构 → 再选择隐喻 → 用视觉流程讲清楚 → 文字只做点睛**。\n\n---\n\n# 1) 工作流程(必须遵守)\n\n当收到用户内容后,按以下顺序完成:\n\n1. 提炼:输出 1 句\"核心结论\" + 3–6 个\"关键点\"\n2. 选型:判断更适合的结构(流程 / 对比 / 分层 / 递进 / 因果 / 清单 / 象限等)\n3. 视觉化:为结构选择一个直观隐喻(避免俗套科技符号)\n4. 排版:先定大块布局与阅读路径,再放文字与点缀\n5. 自检:清理不该出现的文字与多余英文,控制手绘占比\n\n**关于标题**:每张图的标题文字由你根据内容自行提炼,要求简洁有力、适合视觉呈现。topic 字段只是主题方向参考,不必照搬\n\n---\n\n# 2) 版式与阅读路径\n\n- 默认阅读路径:**从左到右**,或严格符合逻辑顺序(从上到下也可,但必须清晰)\n- 大量留白:画面要\"呼吸\",不拥挤\n- 文字形态:关键词 / 短语优先;句子只在\"结论句\"或\"手绘注释\"中少量出现\n\n---\n\n# 3) 统一视觉语言(核心风格)\n\n你的画面由两种语言组成,并且必须\"主次分明\":\n\n## 3.1 规整层(主层)\n\n- 扁平几何矢量风格、对齐规整、结构清楚\n- 细线勾边,允许非常轻微的玻璃拟态微光/轻微投影(克制、干净)\n\n## 3.2 手绘层(强调层)\n\n- 手绘线条 / 涂鸦笔触 / 小卡通图标 / 圈选 / 箭头 / 手写短注释\n- 承担:强调、提示、幽默感、视线引导\n- 允许出现短语或句子,但必须 \"更小、更少、更轻\",不能抢主结构\n\n---\n\n# 4) 手绘占比控制(重点:别让手绘泛滥)\n\n你的目标是\"手绘做调味\",不是\"手绘做主体\"。必须同时满足以下硬约束:\n\nA) **数量上限**(强制)\n\n- 每页手绘元素总数 ≤ 8 个\n\nB) **面积上限**(强制)\n\n- 手绘层覆盖面积 ≤ 15% 画面\n- 任意单个手绘元素面积 ≤ 5% 画面\n\nC) **功能约束**(强制)\n\n- 手绘元素必须\"有用途\":只能用于\n 1) 指向关系(箭头)\n 2) 高亮重点(圈选/下划线)\n 3) 补充一句提醒(短注释)\n 4) 小图标增强记忆点(极简、少量)\n- 不允许用手绘去画大块容器、主流程框、主要结构骨架\n\n---\n\n# 5) 色彩规范(浅色模式)\n\n- 背景:浅灰白 #F8F9FA\n- 标题:深空紫 #2F2B42\n- 正文/辅助:石板灰 #64748B\n- 主色:天空蓝 #38BDF8(用于结构关键线、节点、主图标)\n- 强调色:琥珀橙 #F59E0B(用于关键区域高亮/行动点)\n- 禁止:霓虹色、渐变、复杂纹理\n- 质感:轻微的玻璃拟态微光/轻微投影(克制),整体干净、专业、亲和\n\n---\n\n# 6) 语言规则(中文为主,英文克制)\n\n- 画面文字以**中文为主**\n- **特殊术语保留英文**(例如:Prompt / Token / Context / RAG / Agent / Workflow)\n- 除术语外,能用中文就用中文;不要为了\"显得专业\"而加英文\n- 如果需要出现英文解释:\n - 优先放在括号中,且长度不超过 3 个单词\n - 同一页英文总量尽量控制在 6 个单词以内(术语不算在内)\n\n---\n\n# 7) 禁忌符号(强制)\n\n严禁使用俗套科技符号与视觉陈词滥调:\n\n- 机器人 / 大脑 / 电路板 / 火箭 以及任何类似\"烂大街科技图标\"。\n\n---\n\n# 8) 防止出现\"字体名称/设计说明\"(强制清理)\n\n- 画面中禁止出现任何\"设计元信息\",包括:\n 字体名、字号、颜色值、对齐说明、占比说明、模板注释等\n- 输出前必须自检并删除这些内容:\n - 任何包含:Inter / Noto / Regular / Font / Typeface 的文本\n - 任何包含 #XXXXXX 色值、px、pt 的文本\n- 如果你需要考虑字体,只能在内部决定,**不能写在画面里**\n\n---\n\n# 9) 字体规范(关键:极细字体)\n\n- **核心要求**:所有文字都必须采用「超细线体」(Ultra-thin / Hairline weight)渲染\n- **视觉描述**:文字笔画要像"发丝"一样细,看起来非常轻盈、精致、充满空气感\n- **整体感觉**:要像高端建筑图纸上的细线标注,而不是厚重的海报大字\n- **强制禁止**:严禁出现任何中等字重(Medium)或粗体(Bold)。哪怕是标题,也只能通过字号放大来区分,字重必须保持极细\n\n---\n\n# 10) 水印(必须)\n\n图片左下角必须包含水印:\n\n© Axton | axtonliu.ai\n\n颜色:石板灰 #64748B\n\n大小:小而清晰,不抢画面",
"pictures": [
{
"id": 1,
"topic": "封面",
"content": "Agent Skills 完全指南\n\n第4节:渐进式披露与 Description 优化\n\n学习目标:理解 Progressive Disclosure 机制,掌握 Description 优化技巧"
},
{
"id": 2,
"topic": "问题的根源:Description",
"content": "你有没有遇到过这种情况:\n- 明明装了一个 Skill,但 Claude 就是不用它?\n- 或者反过来,你随便说一句话,Claude 就莫名其妙调用了一个不相关的 Skill?\n\n这两个问题的根源都在一个地方:Description。"
},
{
"id": 3,
"topic": "渐进式披露",
"content": "假设你有10个 Skills,每个完整内容 2000 tokens。如果全部加载:10 × 2000 = 20000 tokens。还没开始干活,上下文空间就用掉一半了。\n\nSkills 的解决方案叫:渐进式披露(Progressive Disclosure)——只在需要的时候,才加载需要的内容。\n\n你可以把它理解成:先建索引,再按需取正文。\n\n这个机制已经被 Google、Cursor、VS Code、OpenAI Codex 等产品采用。Skills 要规模化使用,必须按需加载——这已经是行业共识。"
},
{
"id": 4,
"topic": "三层加载机制",
"content": "第一层:索引阶段——启动时扫描元数据。Claude 启动时只读取名字和描述,每个 Skill 大概 100 tokens。10个 Skills 也就 1000 tokens,很轻量。\n\n第二层:路由阶段——对话中匹配相关性。当你输入一句话,Claude 会分析你的意图,然后拿去匹配所有 Skills 的 description。Description 就是路由规则,决定了你的 Skill 会不会被选中。\n\n第三层:执行阶段——需要时才完整加载。只有 Claude 判断确实需要用某个 Skill,才会读取完整的 SKILL.md。references 文件夹里的文件也是用到哪个才读哪个。\n\n这就是为什么你可以有几十个 Skills,却不会把上下文挤爆。"
},
{
"id": 5,
"topic": "Description 决定三件事",
"content": "既然 Description 是路由规则,那它写得好不好,直接决定了三件事:\n\n1. 该触发的时候能不能触发——写得太模糊,Claude 不知道什么时候该用\n2. 不该触发的时候会不会乱触发——写得太宽泛,它会乱触发\n3. 多个 Skill 撞车时会不会选错——写得和其他 Skill 太像,它会选错\n\n很多人以为\"Skill 不好用\",其实是 description 写得像一句废话。"
},
{
"id": 6,
"topic": "Description 四要素",
"content": "坏的 description:\"处理文件\"、\"帮忙处理文档\"——太模糊了,几乎什么都能匹配,等于什么都不匹配。\n\n好的 description 有四个要素:\n\n| 要素 | 说明 | 示例 |\n| 输入类型 | 具体是什么文件/内容 | \"SRT 字幕文件\" |\n| 核心功能 | 要输出什么结果 | \"转换为文章格式\" |\n| 触发词 | 用户可能怎么说 | \"字幕转文章\"、\"处理这个 SRT\" |\n| 特殊能力 | 有什么特别的功能 | \"自动检测语言\" |\n\n做到这四点,Claude 就能在你需要的时候,精准找到正确的 Skill。"
},
{
"id": 7,
"topic": "触发冲突处理",
"content": "多个 Skill 的 description 太像,导致 Claude 不知道选哪个。\n\n解决方案有三个:\n\n方案一:增加特异性(最推荐)\n从根本上让两个 Skill 的适用场景分开。把\"处理文件\"改成具体的\"处理 PDF 文档\"或\"处理 SRT 字幕\"。\n\n方案二:使用排除词\n\"处理图片文件(PNG、JPG、WEBP)。不用于处理文档、视频或字幕。\"\n明确告诉 Claude 什么情况不要用。\n\n方案三:手动指定(调试时用)\n直接说:\"使用 srt-workflow skill 处理这个文件\"。这是兜底方案。"
}
]
}
#!/usr/bin/env npx -y bun
/**
* Batch Image Generation Script
*
* Generates multiple images from a JSON config file.
* Supports the unified JSON format (same as web version).
*
* Usage:
* npx -y bun batch-generate.ts --config slides.json --output-dir ./images
*
* Config format (unified with web version):
* {
* "instruction": "请为我绘制 N 张图片...",
* "batch_rules": { "total": N, "one_item_one_image": true, "aspect_ratio": "16:9" },
* "style": "完整的 style prompt 字符串...",
* "pictures": [
* { "id": 1, "topic": "封面", "content": "..." },
* { "id": 2, "topic": "...", "content": "..." }
* ]
* }
*/
import { writeFile, readFile, mkdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join, dirname, basename } from 'node:path';
const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
// New unified format (same as web version)
interface PictureConfig {
id: number;
topic: string;
content: string;
}
interface BatchRules {
total: number;
one_item_one_image?: boolean;
aspect_ratio?: string;
do_not_merge?: boolean;
}
interface UnifiedConfig {
instruction?: string;
batch_rules?: BatchRules;
fallback?: string;
style: string;
pictures: PictureConfig[];
}
// Legacy format (for backward compatibility)
interface LegacyIllustration {
id: number;
prompt: string | object;
filename: string;
type?: string;
position?: string;
}
interface LegacyConfig {
style?: {
mode?: string;
background?: string;
primary?: string;
accent?: string[];
};
instructions?: string;
illustrations: LegacyIllustration[];
}
type BatchConfig = UnifiedConfig | LegacyConfig;
interface GeminiResponse {
candidates?: Array<{
content?: {
parts?: Array<{
text?: string;
inlineData?: {
mimeType: string;
data: string;
};
}>;
};
}>;
error?: {
message: string;
code: number;
};
}
function isUnifiedConfig(config: BatchConfig): config is UnifiedConfig {
return 'pictures' in config && Array.isArray(config.pictures);
}
function buildPromptFromUnified(picture: PictureConfig, style: string): string {
// Combine style + topic + content into a single prompt
return `${style}
---
请为以下内容生成一张信息图:
**主题方向**: ${picture.topic}
**内容**:
${picture.content}`;
}
function buildPromptFromLegacy(
illustration: LegacyIllustration,
style?: LegacyConfig['style']
): string {
let prompt = '';
if (style) {
prompt += `Style: ${style.mode || 'light'} mode, `;
prompt += `background ${style.background || '#F8F9FA'}, `;
prompt += `primary color ${style.primary || '#2F2B42'}, `;
if (style.accent) {
prompt += `accent colors ${style.accent.join(', ')}. `;
}
}
if (typeof illustration.prompt === 'string') {
prompt += illustration.prompt;
} else {
prompt += JSON.stringify(illustration.prompt);
}
return prompt;
}
async function generateImage(
prompt: string,
model: string,
apiKey: string
): Promise<Buffer | null> {
const url = `${GEMINI_API_BASE}/${model}:generateContent?key=${apiKey}`;
const requestBody = {
contents: [
{
parts: [
{
text: `Generate an image: ${prompt}`
}
]
}
],
generationConfig: {
responseModalities: ['IMAGE', 'TEXT'],
imageConfig: {
imageSize: '2K'
}
}
};
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
const data: GeminiResponse = await response.json();
if (data.error) {
throw new Error(`Gemini API Error: ${data.error.message}`);
}
if (!data.candidates?.[0]?.content?.parts) {
return null;
}
for (const part of data.candidates[0].content.parts) {
if (part.inlineData?.data) {
return Buffer.from(part.inlineData.data, 'base64');
}
}
return null;
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
function printUsage(): never {
console.log(`
Batch Image Generation Script
Usage:
npx -y bun batch-generate.ts --config slides.json --output-dir ./images
Options:
-c, --config <path> JSON config file (unified format, same as web version)
-o, --output-dir <path> Output directory (default: ./illustrations)
-m, --model <model> Model to use (default: gemini-3-pro-image-preview)
-d, --delay <ms> Delay between requests in ms (default: 2000)
-p, --prefix <text> Filename prefix (default: from config filename)
-r, --regenerate <ids> Regenerate specific images (e.g., "3" or "3,5,7")
-f, --force Force regenerate all images (ignore existing)
-h, --help Show this help
Resume Generation:
By default, the script skips images that already exist in the output directory.
This allows you to resume interrupted generation without re-generating completed images.
Use --force to regenerate all images, or --regenerate to regenerate specific ones.
Environment:
GEMINI_API_KEY Required. Get from https://aistudio.google.com/apikey
Config File Format (Unified - same JSON as web version):
{
"instruction": "请为我绘制 7 张图片(generate 7 images)...",
"batch_rules": {
"total": 7,
"one_item_one_image": true,
"aspect_ratio": "16:9",
"do_not_merge": true
},
"fallback": "如果无法一次生成全部图片...",
"style": "完整的 style prompt(从 styles/style-light.md 复制)...",
"pictures": [
{ "id": 1, "topic": "封面", "content": "Agent Skills 完全指南\\n\\n第1节:..." },
{ "id": 2, "topic": "核心概念", "content": "Skills 是什么..." }
]
}
Output Filenames:
{prefix}-{id:02d}.png (e.g., SKILL_01-01.png, SKILL_01-02.png)
`);
process.exit(0);
}
async function main() {
const args = process.argv.slice(2);
let configPath: string | null = null;
let outputDir = './illustrations';
let model = 'gemini-3-pro-image-preview';
let delay = 2000;
let prefix: string | null = null;
let forceRegenerate = false;
let regenerateIds: Set<number> | null = null;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
switch (arg) {
case '-h':
case '--help':
printUsage();
break;
case '-c':
case '--config':
configPath = args[++i];
break;
case '-o':
case '--output-dir':
outputDir = args[++i];
break;
case '-m':
case '--model':
model = args[++i];
break;
case '-d':
case '--delay':
delay = parseInt(args[++i], 10);
break;
case '-p':
case '--prefix':
prefix = args[++i];
break;
case '-f':
case '--force':
forceRegenerate = true;
break;
case '-r':
case '--regenerate':
regenerateIds = new Set(
args[++i].split(',').map(id => parseInt(id.trim(), 10))
);
break;
}
}
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
console.error('Error: GEMINI_API_KEY environment variable is required');
console.error('Get your API key from: https://aistudio.google.com/apikey');
process.exit(1);
}
if (!configPath) {
console.error('Error: --config is required');
process.exit(1);
}
const configContent = await readFile(configPath, 'utf-8');
const config: BatchConfig = JSON.parse(configContent);
// Auto-detect prefix from config filename if not specified
if (!prefix) {
prefix = basename(configPath, '.json').replace(/-slides$/, '');
}
await mkdir(outputDir, { recursive: true });
// Handle unified format vs legacy format
if (isUnifiedConfig(config)) {
// Unified format (new)
const total = config.pictures.length;
let success = 0;
let failed = 0;
let skipped = 0;
console.log(`\nBatch Image Generation (Unified Format)`);
console.log(`=======================================`);
console.log(`Model: ${model}`);
console.log(`Total: ${total} images`);
console.log(`Prefix: ${prefix}`);
console.log(`Output: ${outputDir}`);
console.log(`Delay: ${delay}ms between requests`);
if (forceRegenerate) {
console.log(`Mode: Force regenerate all`);
} else if (regenerateIds) {
console.log(`Mode: Regenerate specific IDs: ${[...regenerateIds].join(', ')}`);
} else {
console.log(`Mode: Resume (skip existing)`);
}
console.log();
let needsDelay = false;
for (const picture of config.pictures) {
const filename = `${prefix}-${String(picture.id).padStart(2, '0')}.png`;
const outputPath = join(outputDir, filename);
// Check if we should skip this image
const fileExists = existsSync(outputPath);
const shouldRegenerate = regenerateIds?.has(picture.id);
const shouldSkip = fileExists && !forceRegenerate && !shouldRegenerate;
if (shouldSkip) {
console.log(`[${picture.id}/${total}] Skipping: ${filename} (already exists)`);
skipped++;
continue;
}
// Add delay before generation (except for first image)
if (needsDelay) {
console.log(` Waiting ${delay}ms...`);
await sleep(delay);
}
console.log(`[${picture.id}/${total}] Generating: ${filename}`);
console.log(` Topic: ${picture.topic}`);
if (shouldRegenerate) {
console.log(` (Regenerating as requested)`);
}
try {
const prompt = buildPromptFromUnified(picture, config.style);
const imageBuffer = await generateImage(prompt, model, apiKey);
if (imageBuffer) {
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, imageBuffer);
console.log(` ✓ Saved (${(imageBuffer.length / 1024).toFixed(1)} KB)`);
success++;
needsDelay = true;
} else {
console.log(` ✗ No image generated`);
failed++;
needsDelay = true;
}
} catch (error) {
console.log(` ✗ Error: ${error instanceof Error ? error.message : error}`);
failed++;
needsDelay = true;
}
}
console.log(`\n=======================================`);
if (skipped > 0) {
console.log(`Complete: ${success} generated, ${skipped} skipped, ${failed} failed`);
} else {
console.log(`Complete: ${success}/${total} succeeded, ${failed} failed`);
}
console.log(`Output directory: ${outputDir}`);
} else {
// Legacy format (backward compatibility)
const legacyConfig = config as LegacyConfig;
if (!legacyConfig.illustrations || legacyConfig.illustrations.length === 0) {
console.error('Error: No illustrations in config');
process.exit(1);
}
const total = legacyConfig.illustrations.length;
let success = 0;
let failed = 0;
let skipped = 0;
console.log(`\nBatch Image Generation (Legacy Format)`);
console.log(`======================================`);
console.log(`Model: ${model}`);
console.log(`Total: ${total} images`);
console.log(`Output: ${outputDir}`);
if (forceRegenerate) {
console.log(`Mode: Force regenerate all`);
} else if (regenerateIds) {
console.log(`Mode: Regenerate specific IDs: ${[...regenerateIds].join(', ')}`);
} else {
console.log(`Mode: Resume (skip existing)`);
}
console.log();
let needsDelay = false;
for (const illustration of legacyConfig.illustrations) {
const outputPath = join(outputDir, illustration.filename);
// Check if we should skip this image
const fileExists = existsSync(outputPath);
const shouldRegenerate = regenerateIds?.has(illustration.id);
const shouldSkip = fileExists && !forceRegenerate && !shouldRegenerate;
if (shouldSkip) {
console.log(`[${illustration.id}/${total}] Skipping: ${illustration.filename} (already exists)`);
skipped++;
continue;
}
// Add delay before generation (except for first image)
if (needsDelay) {
await sleep(delay);
}
console.log(`[${illustration.id}/${total}] Generating: ${illustration.filename}`);
if (shouldRegenerate) {
console.log(` (Regenerating as requested)`);
}
try {
const prompt = buildPromptFromLegacy(illustration, legacyConfig.style);
const imageBuffer = await generateImage(prompt, model, apiKey);
if (imageBuffer) {
await mkdir(dirname(outputPath), { recursive: true });
await writeFile(outputPath, imageBuffer);
console.log(` ✓ Saved (${(imageBuffer.length / 1024).toFixed(1)} KB)`);
success++;
needsDelay = true;
} else {
console.log(` ✗ No image generated`);
failed++;
needsDelay = true;
}
} catch (error) {
console.log(` ✗ Error: ${error instanceof Error ? error.message : error}`);
failed++;
needsDelay = true;
}
}
console.log(`\n======================================`);
if (skipped > 0) {
console.log(`Complete: ${success} generated, ${skipped} skipped, ${failed} failed`);
} else {
console.log(`Complete: ${success}/${total} succeeded, ${failed} failed`);
}
console.log(`Output directory: ${outputDir}`);
}
}
main();
/**
* Configuration management for Smart Illustrator
* Handles loading and saving style configurations
*/
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join, dirname, resolve, isAbsolute } from 'path';
import { homedir } from 'os';
export interface Config {
style?: string;
references?: string[];
watermark?: string;
}
const PROJECT_CONFIG_DIR = '.smart-illustrator';
const PROJECT_CONFIG_FILE = 'config.json';
const USER_CONFIG_DIR = join(homedir(), '.smart-illustrator');
const USER_CONFIG_FILE = join(USER_CONFIG_DIR, 'config.json');
/**
* Load configuration from files
* Priority: project-level > user-level > defaults
*/
export function loadConfig(cwd: string = process.cwd()): Config {
const projectConfigPath = join(cwd, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
const userConfigPath = USER_CONFIG_FILE;
let config: Config = {};
// Load user-level config first
if (existsSync(userConfigPath)) {
try {
const userConfig = JSON.parse(readFileSync(userConfigPath, 'utf-8'));
config = { ...config, ...userConfig };
console.log(`✓ Loaded user config: ${userConfigPath}`);
} catch (error) {
console.warn(`⚠ Failed to load user config: ${error}`);
}
}
// Load project-level config (overrides user-level)
if (existsSync(projectConfigPath)) {
try {
const projectConfig = JSON.parse(readFileSync(projectConfigPath, 'utf-8'));
config = { ...config, ...projectConfig };
console.log(`✓ Loaded project config: ${projectConfigPath}`);
} catch (error) {
console.warn(`⚠ Failed to load project config: ${error}`);
}
}
// Resolve reference paths relative to project root (for project config) or user home (for user config)
if (config.references && config.references.length > 0) {
const baseDir = existsSync(projectConfigPath)
? cwd // Project config: resolve relative to project root
: USER_CONFIG_DIR; // User config: resolve relative to user config dir
config.references = config.references.map(ref => {
// Handle ~ (home directory) expansion
if (ref.startsWith('~/')) {
return join(homedir(), ref.slice(2));
}
if (isAbsolute(ref)) {
return ref;
}
return resolve(baseDir, ref);
});
}
return config;
}
/**
* Save configuration to file
*/
export function saveConfig(
config: Config,
options: { global?: boolean; cwd?: string } = {}
): void {
const { global = false, cwd = process.cwd() } = options;
let configPath: string;
let configDir: string;
if (global) {
configPath = USER_CONFIG_FILE;
configDir = USER_CONFIG_DIR;
} else {
configDir = join(cwd, PROJECT_CONFIG_DIR);
configPath = join(configDir, PROJECT_CONFIG_FILE);
}
// Create directory if not exists
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}
// Convert absolute reference paths to relative (for project-level config)
const configToSave = { ...config };
if (!global && configToSave.references && configToSave.references.length > 0) {
configToSave.references = configToSave.references.map(ref => {
if (isAbsolute(ref)) {
// Try to make it relative to project root (cwd)
const rel = relative(cwd, ref);
// Only use relative path if it doesn't go outside the project
if (!rel.startsWith('..') && !isAbsolute(rel)) {
return rel;
}
}
return ref;
});
}
writeFileSync(configPath, JSON.stringify(configToSave, null, 2), 'utf-8');
console.log(`✓ Saved ${global ? 'user' : 'project'} config: ${configPath}`);
}
/**
* Merge command-line arguments with loaded config
* CLI arguments take precedence
*/
export function mergeConfig(
loadedConfig: Config,
cliArgs: Partial<Config>
): Config {
const merged: Config = { ...loadedConfig };
// CLI args override config file
if (cliArgs.style !== undefined) {
merged.style = cliArgs.style;
}
if (cliArgs.references !== undefined && cliArgs.references.length > 0) {
merged.references = cliArgs.references;
}
if (cliArgs.watermark !== undefined) {
merged.watermark = cliArgs.watermark;
}
return merged;
}
// Helper: path.relative for cross-platform
function relative(from: string, to: string): string {
// Simple implementation - for production use path.relative from 'path'
const fromParts = from.split(/[/\\]/);
const toParts = to.split(/[/\\]/);
let i = 0;
while (i < fromParts.length && i < toParts.length && fromParts[i] === toParts[i]) {
i++;
}
const upCount = fromParts.length - i;
const remainingPath = toParts.slice(i).join('/');
if (upCount === 0) {
return remainingPath || '.';
}
return '../'.repeat(upCount) + remainingPath;
}
#!/usr/bin/env npx -y bun
/**
* Cover Learning Module
*
* Analyzes high-performing cover images and extracts patterns.
* Learnings are saved to ~/.smart-illustrator/cover-learnings.md
* and automatically loaded when generating new covers.
*/
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { join, basename, extname, dirname, resolve } from 'node:path';
import { homedir } from 'node:os';
const USER_CONFIG_DIR = join(homedir(), '.smart-illustrator');
const LEARNINGS_FILE = join(USER_CONFIG_DIR, 'cover-learnings.md');
// Gemini API for image analysis
const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
const ANALYSIS_MODEL = 'gemini-2.0-flash';
interface CoverAnalysis {
date: string;
source: string;
elements: {
composition: string;
colorScheme: string;
textUsage: string;
emotion: string;
focusPoint: string;
};
patterns: string[];
avoidPatterns: string[];
}
interface LearningsData {
analyses: CoverAnalysis[];
distilledPatterns: {
highCTR: string[];
avoid: string[];
};
lastUpdated: string;
}
/**
* Load learning analysis prompt from prompts/learning-analysis.md
*/
async function loadLearningAnalysisPrompt(note?: string): Promise<string> {
const promptsDir = resolve(dirname(new URL(import.meta.url).pathname), '../prompts');
const analysisPromptPath = resolve(promptsDir, 'learning-analysis.md');
try {
let content = await readFile(analysisPromptPath, 'utf-8');
// Remove README sections (everything before first ---)
const promptStart = content.indexOf('---');
if (promptStart !== -1) {
const promptEnd = content.indexOf('---', promptStart + 3);
if (promptEnd !== -1) {
content = content.slice(promptStart + 3, promptEnd).trim();
}
}
// Replace {{USER_NOTE}} variable
if (note) {
content = content.replace('{{USER_NOTE}}', `用户备注:${note}`);
} else {
content = content.replace(/\n*{{USER_NOTE}}\n*/g, '');
}
return content;
} catch (error) {
console.warn('Warning: Failed to load learning analysis prompt, using default');
// Fallback to default prompt
return `你是一位 YouTube 封面图分析专家。请分析这张封面图,提取对未来封面设计有价值的模式。
请用以下 JSON 格式输出(中文):
{
"composition": "构图描述(如:左侧人物 + 右侧文字、中心聚焦、对比布局等)",
"colorScheme": "配色方案(如:深色背景 + 橙色强调、高对比冷暖搭配等)",
"textUsage": "文字使用(如:无文字、3-5个大字、数字突出等)",
"emotion": "传达的情绪(如:好奇心、紧迫感、专业感、震惊等)",
"focusPoint": "视觉焦点(如:人物表情、产品 logo、对比元素等)",
"patterns": ["值得学习的模式1", "值得学习的模式2", "..."],
"avoidPatterns": ["如果有不好的地方,列出应避免的模式"]
}
${note ? `用户备注:${note}` : ''}
只输出 JSON,不要其他内容。`;
}
}
/**
* Load existing learnings from file
*/
export async function loadLearnings(): Promise<LearningsData | null> {
if (!existsSync(LEARNINGS_FILE)) {
return null;
}
try {
const content = await readFile(LEARNINGS_FILE, 'utf-8');
return parseLearningsMarkdown(content);
} catch (error) {
console.warn('Warning: Failed to load cover learnings:', error);
return null;
}
}
/**
* Parse markdown learnings file to structured data
*/
function parseLearningsMarkdown(content: string): LearningsData {
const analyses: CoverAnalysis[] = [];
const distilledPatterns = {
highCTR: [] as string[],
avoid: [] as string[]
};
// Extract distilled patterns
const highCTRMatch = content.match(/### 高 CTR 共性\n([\s\S]*?)(?=\n###|\n---|\n## |$)/);
if (highCTRMatch) {
distilledPatterns.highCTR = highCTRMatch[1]
.split('\n')
.filter(line => line.startsWith('- '))
.map(line => line.slice(2).trim());
}
const avoidMatch = content.match(/### 应避免的模式\n([\s\S]*?)(?=\n###|\n---|\n## |$)/);
if (avoidMatch) {
distilledPatterns.avoid = avoidMatch[1]
.split('\n')
.filter(line => line.startsWith('- '))
.map(line => line.slice(2).trim());
}
// Extract last updated
const lastUpdatedMatch = content.match(/\*\*最后更新\*\*:(.+)/);
const lastUpdated = lastUpdatedMatch ? lastUpdatedMatch[1].trim() : new Date().toISOString().split('T')[0];
return { analyses, distilledPatterns, lastUpdated };
}
/**
* Analyze a cover image using Gemini Vision
*/
export async function analyzeCoverImage(
imagePath: string,
note?: string
): Promise<CoverAnalysis | null> {
const geminiKey = process.env.GEMINI_API_KEY;
if (!geminiKey) {
console.error('Error: GEMINI_API_KEY is required for cover analysis');
return null;
}
// Read and encode image
const absolutePath = imagePath.startsWith('/') ? imagePath : join(process.cwd(), imagePath);
if (!existsSync(absolutePath)) {
console.error(`Error: Image not found: ${absolutePath}`);
return null;
}
const imageBuffer = await readFile(absolutePath);
const base64Image = imageBuffer.toString('base64');
const ext = extname(imagePath).toLowerCase();
const mimeType = ext === '.png' ? 'image/png'
: ext === '.jpg' || ext === '.jpeg' ? 'image/jpeg'
: ext === '.webp' ? 'image/webp'
: 'image/png';
console.log(`Analyzing cover image: ${basename(imagePath)}`);
// Load analysis prompt from prompts/learning-analysis.md
const analysisPrompt = await loadLearningAnalysisPrompt(note);
try {
const response = await fetch(
`${GEMINI_API_BASE}/${ANALYSIS_MODEL}:generateContent?key=${geminiKey}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contents: [{
parts: [
{
inlineData: {
mimeType,
data: base64Image
}
},
{ text: analysisPrompt }
]
}],
generationConfig: {
temperature: 0.3,
maxOutputTokens: 1024
}
})
}
);
const data = await response.json() as any;
if (data.error) {
console.error('Gemini API error:', data.error.message);
return null;
}
const textContent = data.candidates?.[0]?.content?.parts?.[0]?.text;
if (!textContent) {
console.error('No analysis result from Gemini');
return null;
}
// Parse JSON from response
const jsonMatch = textContent.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
console.error('Failed to parse analysis result');
return null;
}
const parsed = JSON.parse(jsonMatch[0]);
return {
date: new Date().toISOString().split('T')[0],
source: basename(imagePath),
elements: {
composition: parsed.composition || '',
colorScheme: parsed.colorScheme || '',
textUsage: parsed.textUsage || '',
emotion: parsed.emotion || '',
focusPoint: parsed.focusPoint || ''
},
patterns: parsed.patterns || [],
avoidPatterns: parsed.avoidPatterns || []
};
} catch (error) {
console.error('Analysis failed:', error);
return null;
}
}
/**
* Save analysis to learnings file
*/
export async function saveLearning(analysis: CoverAnalysis): Promise<void> {
// Ensure config directory exists
if (!existsSync(USER_CONFIG_DIR)) {
await mkdir(USER_CONFIG_DIR, { recursive: true });
}
// Load existing learnings
let existingContent = '';
let distilledPatterns = {
highCTR: [] as string[],
avoid: [] as string[]
};
if (existsSync(LEARNINGS_FILE)) {
existingContent = await readFile(LEARNINGS_FILE, 'utf-8');
const parsed = parseLearningsMarkdown(existingContent);
distilledPatterns = parsed.distilledPatterns;
}
// Add new patterns to distilled list
for (const pattern of analysis.patterns) {
if (!distilledPatterns.highCTR.includes(pattern)) {
distilledPatterns.highCTR.push(pattern);
}
}
for (const pattern of analysis.avoidPatterns) {
if (!distilledPatterns.avoid.includes(pattern)) {
distilledPatterns.avoid.push(pattern);
}
}
// Build new analysis entry
const newEntry = `
### ${analysis.date}: ${analysis.source}
- **构图**: ${analysis.elements.composition}
- **配色**: ${analysis.elements.colorScheme}
- **文字**: ${analysis.elements.textUsage}
- **情绪**: ${analysis.elements.emotion}
- **焦点**: ${analysis.elements.focusPoint}
- **学到的模式**:
${analysis.patterns.map(p => ` - ${p}`).join('\n')}
${analysis.avoidPatterns.length > 0 ? `- **应避免**:\n${analysis.avoidPatterns.map(p => ` - ${p}`).join('\n')}` : ''}
`;
// Build full content
const today = new Date().toISOString().split('T')[0];
const fullContent = `# 封面图学习记录
**最后更新**:${today}
---
## 提炼的模式(自动汇总)
### 高 CTR 共性
${distilledPatterns.highCTR.map(p => `- ${p}`).join('\n') || '- (暂无记录)'}
### 应避免的模式
${distilledPatterns.avoid.map(p => `- ${p}`).join('\n') || '- (暂无记录)'}
---
## 学习记录
${newEntry}
${existingContent.includes('## 学习记录')
? existingContent.split('## 学习记录')[1]
: ''}
`;
await writeFile(LEARNINGS_FILE, fullContent, 'utf-8');
console.log(`✓ Learning saved to: ${LEARNINGS_FILE}`);
}
/**
* Get learnings as prompt supplement for cover generation
*/
export async function getLearningsPrompt(): Promise<string | null> {
const learnings = await loadLearnings();
if (!learnings || learnings.distilledPatterns.highCTR.length === 0) {
return null;
}
let prompt = '\n\n## 从历史高表现封面学到的模式(请参考)\n\n';
if (learnings.distilledPatterns.highCTR.length > 0) {
prompt += '### 推荐的模式\n';
prompt += learnings.distilledPatterns.highCTR.map(p => `- ${p}`).join('\n');
prompt += '\n\n';
}
if (learnings.distilledPatterns.avoid.length > 0) {
prompt += '### 应避免的模式\n';
prompt += learnings.distilledPatterns.avoid.map(p => `- ${p}`).join('\n');
prompt += '\n';
}
return prompt;
}
// CLI interface
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
console.log(`
Cover Learner - Analyze and learn from high-performing cover images
Usage:
npx -y bun cover-learner.ts <image-path> [--note "optional note"]
npx -y bun cover-learner.ts --show # Show current learnings
Examples:
npx -y bun cover-learner.ts my-best-thumbnail.png
npx -y bun cover-learner.ts cover.png --note "CTR 8.5%, 这个封面效果很好"
npx -y bun cover-learner.ts --show
`);
process.exit(0);
}
if (args.includes('--show')) {
const learnings = await loadLearnings();
if (!learnings) {
console.log('No learnings found yet. Analyze some covers first!');
console.log(`Learnings file: ${LEARNINGS_FILE}`);
} else {
console.log(await readFile(LEARNINGS_FILE, 'utf-8'));
}
process.exit(0);
}
const imagePath = args[0];
let note: string | undefined;
const noteIndex = args.indexOf('--note');
if (noteIndex !== -1 && args[noteIndex + 1]) {
note = args[noteIndex + 1];
}
const analysis = await analyzeCoverImage(imagePath, note);
if (analysis) {
await saveLearning(analysis);
console.log('\n--- 分析结果 ---');
console.log(`构图: ${analysis.elements.composition}`);
console.log(`配色: ${analysis.elements.colorScheme}`);
console.log(`文字: ${analysis.elements.textUsage}`);
console.log(`情绪: ${analysis.elements.emotion}`);
console.log(`焦点: ${analysis.elements.focusPoint}`);
console.log('\n学到的模式:');
analysis.patterns.forEach(p => console.log(` ✓ ${p}`));
if (analysis.avoidPatterns.length > 0) {
console.log('\n应避免:');
analysis.avoidPatterns.forEach(p => console.log(` ✗ ${p}`));
}
}
}
// Only run main() when executed directly (not when imported as a module)
// Bun supports import.meta.main for this check
if (import.meta.main) {
main().catch(console.error);
}
{
"name": "smart-illustrator-scripts",
"version": "0.1.0",
"private": true,
"type": "module",
"dependencies": {
"excalidraw-brute-export-cli": "0.4.0"
}
}
Style: Minimal / 极简风格
极简主义风格,适合专业文档和技术说明。
默认色板:brand-colors.md → Minimal Slate / 极简石板适用场景
- 技术文档配图
- 白皮书插图
- 专业报告
- 简洁演示
Gemini System Prompt
You are a minimalist infographic designer. Create ultra-clean, professional illustrations.
**Format**: 3:4 vertical (portrait)
**Visual Style**:
- Ultra-minimal: only essential elements
- Thin monochrome lines (single weight)
- Simple geometric shapes (circles, squares, triangles)
- Maximum whitespace (60%+ of canvas)
- No decorative elements
**Color Palette**:
- Background: Pure white #FFFFFF
- Primary: Deep slate #1E293B
- Secondary: Medium slate #475569
- Accent: Single accent color (choose one based on content):
- Technical: Steel blue #3B82F6
- Creative: Coral #F97316
- Growth: Emerald #10B981
- No gradients, no shadows
**Typography**:
- Minimal text, large whitespace
- Sans-serif, thin weight
- Black or dark gray only
**DO NOT use**:
- Multiple accent colors
- Gradients or shadows
- Decorative borders or patterns
- Illustrations or icons with fills
- Any "busy" elementsPrompt 模板
Create a minimalist infographic.
[插入上方 System Prompt]
**Content**:
- Topic: [主题]
- Key elements: [最多 3 个关键元素]
- Accent color: [steel blue/coral/emerald]
**Composition**: Centered, symmetrical, maximum breathing room.设计原则
| 维度 | 要求 |
|---|---|
| 元素数量 | 最少化(3-5 个) |
| 颜色 | 单色 + 一个点缀 |
| 构图 | 居中、对称 |
| 留白 | 60% 以上 |
Related skills
FAQ
What three engines does smart-illustrator use?
Gemini for creative or metaphor images, Excalidraw for hand-drawn concept diagrams, and Mermaid for complex structured diagrams, all output as PNG.
What cover platforms and sizes are supported?
YouTube (16:9), WeChat (2.35:1), Twitter (1.91:1) and Xiaohongshu (3:4), all output at 2K resolution.