
infographic-maker-ardot
<!-- 适用场景说明(给 ardot 用户看;ardot 是手动选择触发,无关键词召回): - "信息图" / "知识卡片" / "可视化总结" / "图解" / "一图读懂" - "infographic" / "visual summary" / "knowledge card" / "illustrated summary" - 把文章 / 概念 / 数据可视化为一张图 -->把任意输入内容(文章 / 概念 / 数据 / 笔记)转化为一张视觉化、便于快速理解的手绘卡通风格信息图(默认 1:1 方形 1024×1024,与 ardot AI 生图原生比例匹配,无空白带、无裁切),直接生成到 Ardot 画布上,并截图回执。
任务
接收用户提供的内容(文章 / 主题 / 数据 / 概念),分四阶段完成:
- 内容分析与蒸馏——提炼核心主题 + 3-7 个要点 + 视觉隐喻
- Prompt 构造——按
references/style-guide.md拼装 prompt(蒸馏完直接构造,不停下等用户确认) - 画布生成——在画布上创建 1024×1024 frame + 用 ardot G("ai", prompt) 操作生成 AI 图作为 fill
- 截图回执 + 可选迭代——截图给用户看,生成后由 Step 4.3 统一问"是否调整 / 重做"
最终产物是 Ardot 画布上的 1 个 1:1 方形 frame(1024×1024)(含 AI 生成的信息图),不是本地图片文件。
🎯 为什么默认 1:1 而不是 16:9?(必读 references/ardot-imagegen-gotchas.md §五 雷 6)
实测 2026-06-29 确认:ardot 后端 AI 生图模型原生输出 1:1 正方形(1024×1024),且 G 操作 schema 没有
aspectRatio/outputSize参数。无论 prompt 里写 "landscape 16:9" 多少遍,生成出来的图都是 1:1。如果 frame 是 16:9 容器:
scaleMode=FILL→ 强制裁切 → 切边 bug(雷 1)scaleMode=FIT→ 左右各留 ~15% 空白带(letterbox)→ 比例不匹配 bug(雷 6)唯一能根治的方案:frame 跟随 AI 输出比例(1024×1024),不让协议层的"承诺-实现鸿沟"暴露给用户。
执行流程
⚠️ 必读 references/style-guide.md(提示词模板 + 色板 / 布局 / 元素 / 反例 / 完整示例) ⚠️ 必读 references/ardot-imagegen-gotchas.md(ardot 生图接口的踩坑分析;Step 3 / Step 4 开始前必读——解释了 placeholder / scaleMode / 卡死等异步生图相关 bug 的接口层根因,以及当前 skill 的防御策略依据)
注:⚠️-3 的探测 + 降级逻辑已完整内联在下面的 Step 3 中,AI 按 Step 3 字面执行即可。
Step 0 — 起手准备
调用 mcp__ardot__fetch_editor_state 获取:
- 当前活动文件 fileId(后续 batch_edit / capture_screenshot 用)
- 当前选中 / 视口位置(决定信息图插入位置参考)
- 文档大纲(避免与已有内容重叠)
初始化 checkpoint:
from helpers.checkpoint import Checkpoint
from helpers.parse_multiselect import parse_user_answer
from helpers.safe_batch_edit import safe_batch_edit
from helpers.probe_capability import probe_capability
cp = Checkpoint("infographic-maker-ardot_session")
probe_cache = {} # 给 Step 3 的多次能力探测共享
⚠️ 必读 references/style-guide.md(含色板预设 / 5 种布局 / 视觉元素库 / 文字渲染对策 / 完整示例)—— 后续 Step 1-2 都要用。
Step 1 — 内容分析与蒸馏
把用户给的输入(文章 / 主题 / 数据 / 概念)按下列规则蒸馏:
- 核心主题:单一一句话能说清的主题。
- 3-7 个要点:偏好奇数(3 / 5 / 7)以利视觉平衡。要点 ≥ 7 时聚合 / 取舍。
- 每个要点的视觉隐喻:一个具象图标 / 卡通角色 / 物件 / 场景。
- 检测语言:除非用户明确要求语言,否则匹配输入的语言(中文输入 → 信息图中文文字;英文 → 英文)。
- 识别名人:若内容提到知名人物,规划"简化卡通肖像"(非写实;避免涉政 / 涉宗教敏感人物)。
- 选择主题色板:参考
references/style-guide.md中"Color Palette Presets"7 套预设(Technology / Business / Science / Education / Health / Creative / Finance),按内容主题挑一套。 - 选择布局:参考
references/style-guide.md中 5 种布局(Grid / Flow / Radial / Timeline / Comparison),按要点关系挑一种:- 4-6 个并列要点 → Grid
- 流程 / 序列 → Flow
- 中心概念 + 子概念 → Radial
- 编年内容 → Timeline
- 对比 / 利弊 → Comparison
⚠️ 必读 references/style-guide.md 的"Color Palette Presets" 和 "Layout Patterns" 两节再做选择。
Step 2 — Prompt 构造
⚠️ 不设生成前确认关卡(方案 A):蒸馏完(Step 1)直接按下面拼 prompt 并进入 Step 3 生成,绝不停下来问用户"按此方案生成吗"。所有调整机会统一由生成后的 Step 4.3 提供("先生成、后按需改")。
📌 为什么删掉生成前确认:实测发现在生成前插入"请确认蒸馏方案"关卡时,AI 有一定概率把
ask_followup_question工具调用降级成一段纯文本"请选择:…"、随即直接结束这一轮(不触发可点选的交互控件、直接结算积分),流程就卡死在确认步、画布空空。删掉这道阻塞关卡即可根除此类偶发卡死;用户仍可在 Step 4.3 看到成品后决定是否调整或重做,不损失任何可控性。
Step 2.3 — 按 Style Guide 拼装 prompt
⚠️ 必读 references/ardot-imagegen-gotchas.md §五 雷 5 —— 关于"prompt 工程化术语被 AI 误画成内容"的踩坑(2026-06-29 实测发现)
按下列结构拼装最终 prompt(参考 references/style-guide.md 的 "Prompt Engineering Patterns" 一节):
A hand-drawn cartoon infographic about <TOPIC>, square 1:1 format, fully self-contained poster composition.
Composition framing (describe the visual mood, NOT measurement instructions):
- The whole design reads as a polished magazine cover — comfortably framed within the canvas with airy inner margins on every side.
- Important elements (title, icons, key points) sit well inside the canvas, never touching or running off the edges.
- The title appears in the upper portion of the composition, with generous airy space above it separating it from the top edge.
- The bottom row of content settles peacefully above the canvas bottom, with calming whitespace below.
- Decorative elements may float in the outer area but must remain entirely within the canvas — nothing bleeds, crops, or extends off-canvas.
Title: "<TITLE TEXT>" in bold hand-lettered style, centered horizontally, sitting in the upper portion of the canvas (not at the very top edge).
Main content organized as <LAYOUT TYPE>, comfortably arranged within the inner area:
- <Key point 1 with visual element description>
- <Key point 2 with visual element description>
- ...
Style: hand-drawn sketch illustration, thick outlines, soft pastel colors with <ACCENT> highlights, doodle icons, whitespace-rich layout, cartoon characters, no photorealistic elements. All text in hand-drawn lettering. <LANGUAGE> text only.
⚠️ Do NOT render any of the following in the image (these are layout guides, not visual content):
- No measurement rulers, no percentage labels (like "12%"), no margin indicators, no double-headed arrows showing padding
- No callout arrows pointing to edges or corners
- No instructional overlays, annotations, or "padding indicator" markers
- No safe-zone rectangles drawn as a visible frame
- No technical UI elements (grid lines, alignment guides, design specs)
The padding/margin guidance is for COMPOSITION (where you place elements), NOT for VISUAL CONTENT (what to draw).
Final framing requirements: complete poster composition with comfortable breathing room around every edge, fully self-contained, no bleeding off canvas, no cropped elements, balanced negative space, magazine-quality finish.
强制风格约束(必须出现在 prompt 中):
| 维度 | 关键词 |
|---|---|
| 风格 | hand-drawn sketch illustration, thick outlines, doodle-style icons, cartoon characters |
| 构图 | square 1:1 format, whitespace-rich layout, clear visual hierarchy, fully self-contained poster |
| 安全区 ⚠️ | comfortably framed with airy inner margins, elements settle well inside the canvas, never touching edges, magazine-cover-quality framing(描述性,不带数字) |
| 禁止"画出指令" ⚠️ | no rulers, no percentage labels, no margin indicators, no instructional arrows, no annotations, no safe-zone rectangles, no technical UI |
| 字体 | hand-lettered text, bold title text |
| 色彩 | soft pastel colors with 2-3 accent colors, no neon, no oversaturated |
| 反风格 | no photorealistic elements, no 3D renders, no stock photo style, no gradients, no glossy effects, no edge-cropped elements |
| 语言 | <LANGUAGE> text only(按 Step 1 检测语言填) |
⚠️ Safe-zone 约束(必读,必入 prompt):
AI 生图模型不感知 frame 真实宽高比——即使 prompt 说"1:1",模型实际生成的画面常常贴边布满,被画布裁切后边缘元素丢失(如标题"?"被切、底部箭头被裁)。
已踩过的两个坑:
- 坑 A(已修,由 Step 4.0.5 矫正):scaleMode=FILL 让真实图被强制裁切
- 坑 B(2026-06-29 新发现,本节修复):prompt 里写
"leave 10-12% padding on all edges"/"DO NOT place text within 120px"等工程化数字指令,AI 模型把这些指令本身画了出来——结果图上真的画了"↔ 12%"双向箭头、出现"padding indicator"标注、出现可见的"safe-zone 矩形框"。AI 模型不会区分"prompt 是约束"还是"prompt 是内容"。
修复方式(本节 prompt 已落地):
- 不用具体数字 —— 用描述性语言("comfortably framed", "airy inner margins", "magazine-cover quality")替代 "10-12%" / "120 pixels"
- 不用露骨工程指令 —— 用画面感语言("the title sits in the upper portion with airy space above")替代 "must leave 12% top margin"
- 显式负向禁止"画出指令" —— prompt 末尾加
"Do NOT render any of the following in the image: rulers, percentage labels, margin indicators, callout arrows, instructional overlays, safe-zone rectangles"等清单 - 明确区分意图 —— 在 prompt 里写明
"padding/margin guidance is for COMPOSITION (where you place elements), NOT for VISUAL CONTENT (what to draw)" - 不要用 "edge-to-edge" / "full-bleed" / "panoramic" 等暗示贴边的措辞
文字渲染对策(写进 prompt):
- 每个标签用 1-3 词的关键词,不要写句子
- 标题 3-6 词
- 能用数字(1/2/3)就别用文字
- 能用图标就别用 label
把拼装好的 prompt 暂存为变量 final_prompt(供 Step 3 使用),同时保留一份原始副本 base_prompt 供 Step 4.2.c 重试时用(每次重试重置为 base_prompt + retry_suffix,避免 prompt 累加膨胀):
final_prompt = <上面拼好的 prompt 字符串>
base_prompt = final_prompt # ⚠️ 关键:保留原始版本,供 Step 4.2.c 重试时用
Step 3 — 画布生成(含 ⚠️-3 能力探测:quality / style 参数)
⚠️ 必读 references/ardot-imagegen-gotchas.md §一、§二 —— 了解 ardot 生图接口的根因型限制(同步契约塞异步语义、无 jobId、无状态查询 API),这是 Step 3 / Step 4 整个流程的设计依据
背景:ardot 的
batch_editG 操作签名是G(nodeId, "ai" | "stock", prompt)——快照时 schema 不含 quality / style 子参数。ardot 在持续进化,运行时探测是否已加入。
Step 3.1 — 探测 ardot batch_edit 的 G 操作是否支持 quality / style 子参数
# 共享 probe_cache 让 schema 只 fetch 一次
probe_quality = probe_capability(
tool="batch_edit",
parameter="quality",
server="ardot",
cache=probe_cache,
)
probe_style = probe_capability(
tool="batch_edit",
parameter="style",
server="ardot",
cache=probe_cache,
)
Step 3.2 — 定位画布空白处
为信息图找空白位置(1024 × 1024 是 1:1 方形,与 ardot AI 生图原生比例匹配,无空白带):
# 注:locate_available_space 是 mcp__ardot__locate_available_space 工具
# 调用前不要假设它的具体返回字段——直接传宽高 + padding + direction
space = mcp__ardot__locate_available_space(
width=1024,
height=1024,
padding=100,
direction="right",
)
# space 返回的位置坐标后面 batch_edit 创建 frame 时用作 x/y
Step 3.3 — 创建 1:1 frame 容器
⚠️ 必读 references/helper-functions.md §safe_batch_edit —— batch_edit 弱事务,必须用 safe_batch_edit 包装(约束 #2)
⚠️ 防裁切关键:frame 尺寸必须严格匹配 ardot AI 生图原生输出比例(1:1),这样 G 操作生成的 1024×1024 图会完美填满 1024×1024 frame,无空白带、无裁切。
⚠️ 不要改为 16:9 (1536×1024) 或其他比例 —— AI 模型仍输出 1:1,FILL 模式会切边,FIT 模式会留空白带(见雷 1、雷 6)。
result_frame = safe_batch_edit(
operations=(
f'infoFrame=I(document, {{'
f'type: "frame", '
f'name: "Infographic - <TOPIC>", '
f'width: 1024, '
f'height: 1024, '
f'x: {space["x"]}, '
f'y: {space["y"]}, '
# 预填底色 + 占位 image fill(scaleMode FIT 防裁切)
f'fills: ['
f'{{type: "SOLID", color: {{r: 1.0, g: 0.98, b: 0.94}}, opacity: 1, visible: true, blendMode: "NORMAL"}}'
f']'
f'}})'
),
verify_node_ids=None,
)
if not result_frame.success:
raise RuntimeError(f"创建信息图 frame 失败: {result_frame.failed_ops}")
frame_id = result_frame.created_ids[0]
# 记录到 checkpoint:用户取消时可以 D(frame_id) 清理画布
cp.record(
f"已创建信息图 frame {frame_id}",
undo=lambda: safe_batch_edit(
operations=f'D("{frame_id}")',
verify_deleted=[frame_id],
),
)
Step 3.4 — 用 G 操作生成 AI 图作为 fill(按探测结果分支)
分支 A — 探测到 quality / style 参数(probe.supported == True):
# 真调(直接传探测到的子参数)
real_op = f'G("{frame_id}", "ai", "{final_prompt}"'
if probe_quality.supported:
real_op += ', quality: "high"'
if probe_style.supported:
real_op += ', style: "natural"'
real_op += ')'
result_img = safe_batch_edit(operations=real_op, verify_node_ids=None)
分支 B — 未探测到任一参数(降级路径,必须有,不允许抛 RuntimeError):
# 把语义降级进 prompt 文本
fallback_suffix = ""
if not probe_quality.supported:
fallback_suffix += ", high quality, detailed rendering"
if not probe_style.supported:
fallback_suffix += ", natural hand-drawn organic style"
prompt_with_fallback = final_prompt + fallback_suffix
result_img = safe_batch_edit(
operations=f'G("{frame_id}", "ai", "{prompt_with_fallback}")',
verify_node_ids=None,
)
如果 result_img.success == False:cp.rollback() 清理已创建的 frame,告知用户失败。
Step 3.5 — 把探测结果记进 checkpoint 日志
cp.note(probe_quality.summary()) # 例: "capability probe: batch_edit.quality = supported / fallback"
cp.note(probe_style.summary()) # 例: "capability probe: batch_edit.style = supported / fallback"
探测结果会在 Step 5 用
cp.dump_notes()输出给用户,让用户知道这次跑了真工具还是降级——保持运行时透明。⚠️ scaleMode FIT 矫正在 Step 4.0.5——必须等真实图生成完才能改,否则会被后端覆盖。详见 Step 4.0.5。
Step 4 — 等待生成完成 → 截图回执 → 自检 → 可选迭代
⚠️ 必读 references/ardot-imagegen-gotchas.md §六 + §七 —— 本章节的 placeholder 视觉特征清单和判定状态机就来自这份分析;强烈建议读完再执行 Step 4.0
⚠️ 实测踩坑(2026-06-29):ardot 的 batch_edit G 操作返回是同步的(毫秒级),但 AI 图实际生成是异步的(实测 8-120 秒之间,受后端队列和 prompt 复杂度影响,第一次实测一张图 8 秒生成完,第二次实测 23 秒仍在 placeholder)。G 调用一返回,画布上的 frame fill 已经设置好(imageHash 已分配),但真实像素还在 ardot 后端排队 / 渲染中——此时画布显示的是一个 placeholder。
⚠️ 另一个实测发现:在 placeholder 期间,fills[0].imageHash 已经分配且不再变化——即使图最终生成完,hash 也不变。也就是说 imageHash 字段无法用来判定"生成是否完成",只能靠截图视觉判定。
⚠️ 第三个实测踩坑(更严重,2026-06-29 二次验证):ardot 后端的 AI 生图 pipeline 可能静默失败——表现为 imageHash 永久保留 placeholder 的 hash、画面永远停在"生成中"。已观察到:实测累计 98 秒、轮询多次截图像素级完全一致、仍是 placeholder——这是失败而非"慢"。可能原因:内容审核拦截 / 配额耗尽 / 模型 OOM / 后端 pipeline bug。batch_edit 返回 success: true,外层 0 感知,必须靠 skill 自己识别。
⚠️ 第四个实测发现:ardot 当前没有暴露生成状态查询 API(不在 18 个 MCP 工具内)—— 实测查了 scan_exportable_resources / export_nodes 等,无一可用。只能靠截图视觉判定生成状态。
⚠️ ardot Placeholder 的实测视觉特征(2026-06-29 抓到的真容)
ardot AI 生图 placeholder 有极强的视觉指纹,AI 看截图判定时主要依据这套特征:
| 特征 | 描述 |
|---|---|
| 背景 | 大面积纯白底,占画布 > 90% |
| 居中图标 | 一个画架(easel) + 调色板 / "Q" 或 "a" 字母 Logo 的线稿,浅灰色描边、无填色 |
| 装饰 | 周围漂浮几颗 sparkle 星形 / 钻石形装饰图案 |
| 底部文字 | 正中位置写着 "生成中" 三个中文字(浅灰色) |
| 全局色调 | 所有元素都是单一浅灰色描边,没有任何彩色、没有任何手绘卡通风格元素 |
| fill 元数据 | imageHash 字段已分配(不是空),但 scaleMode 默认仍是 "FILL"(与正常生成无区别) |
⚠️ 重要:
imageHash已经分配不代表生成完成——ardot 在排队期就先占位分配 hash 了。不能用"imageHash是否为空"来判定生成状态,必须看截图视觉。
如果跳过等待直接截图,AI 看到的就是这个 placeholder,会误判为"已生成完成",进而误判"质量自检通过"。
修复:截图前必须先轮询直到生成稳定(Step 4.0),且在 4.2 的自检里再做一次 placeholder 检测作为兜底。
Step 4.0 — 等待生成稳定(⚠️ 必做关卡,截图前置)
4.0.a — 轮询直到画面稳定 OR 判定卡死(最多 90 秒)
策略:每 4 秒截一次小图。三种早退条件:
| 早退条件 | 判定 | 行动 |
|---|---|---|
| 成功:连续 2 次截图"非 placeholder + 视觉无显著变化" | 生成完成 | break 进 Step 4.0.b |
| 卡死:连续 5 次截图全是 placeholder 且像素级一致(约 20s 卡死窗口) | 后端静默失败 | break,直接进 Step 4.0.d 失败处理(不进 4.0.b 兜底) |
| 超时:累计 90 秒仍未触发上述任一 | 慢但可能还在生成 | break 进 Step 4.0.b 兜底再做最终判定 |
import time
# ⚠️ 告知用户 skill 现在在等 AI 图生成(避免以为卡死)
print("⏳ AI 图生成中,正在轮询等待(通常 8-30 秒,最长 90 秒)...")
MAX_WAIT_SEC = 90 # 上限 90s(覆盖正常 5-30s 生成的 3x margin)
POLL_INTERVAL_SEC = 4
STABLE_REQUIRED = 2 # 连续 N 次"非 placeholder + 无变化"才算稳定
STUCK_THRESHOLD = 5 # 连续 N 次"placeholder 且像素级一致"判定卡死
probe_dir = "/workspace/infographic-maker-ardot-screenshots/_polling"
stable_count = 0
stuck_count = 0
last_screenshot_path = None
elapsed = 0
exit_reason = "timeout" # success / stuck / timeout
while elapsed < MAX_WAIT_SEC:
current = mcp__ardot__capture_screenshot(
nodeIds=[frame_id],
screenShotDir=probe_dir,
)
current_path = current["screenshots"][0]["path"]
# 第一道判定:是不是 placeholder?
# AI 看 current_path 单帧,判定是否命中以下任一硬特征:
# (1) 画面 > 90% 是纯白底
# (2) 居中可见画架 / 调色板 / "Q" 或 "a" Logo 线稿(浅灰描边、无填色)
# (3) 底部正中有"生成中"三个中文字
# (4) 周围有 sparkle 星形装饰
# (5) 全图只有浅灰描边、没有任何彩色卡通元素
# 命中任一 → is_placeholder=True
is_placeholder = looks_like_ardot_placeholder(current_path)
if is_placeholder:
# 仍在生成 OR 已卡死——区分这两种:连续 5 次像素级完全一致 → 卡死
stable_count = 0
if last_screenshot_path is not None:
# AI 比对前后两张 placeholder 截图:是否"像素级完全一致"
# (不是"很像",而是几乎没有任何像素差异——sparkle 装饰位置、画架角度都一致)
is_pixel_identical = compare_placeholders_pixel_identical(
last_screenshot_path, current_path,
)
# ↑ 给 AI 看的伪函数:实际由 AI 看图判定"两张 placeholder 是否一模一样"
if is_pixel_identical:
stuck_count += 1
cp.note(f"poll: placeholder unchanged {stuck_count}/{STUCK_THRESHOLD} at {elapsed}s")
if stuck_count >= STUCK_THRESHOLD:
exit_reason = "stuck"
cp.note(f"⚠️ generation stuck on placeholder for {stuck_count * POLL_INTERVAL_SEC}s, abandoning")
break
else:
# placeholder 但有变化(少见——可能是 sparkle 在动)→ 仍在生成中
stuck_count = 0
cp.note(f"poll: placeholder still changing at {elapsed}s")
else:
cp.note(f"poll: first placeholder at {elapsed}s")
elif last_screenshot_path is not None:
# 非 placeholder——判定是否稳定
stuck_count = 0
is_stable = compare_screenshots_for_stability(last_screenshot_path, current_path)
# ↑ AI 看图:两张非 placeholder 截图是否"视觉无显著变化"
if is_stable:
stable_count += 1
cp.note(f"poll: stable streak {stable_count}/{STABLE_REQUIRED} at {elapsed}s")
if stable_count >= STABLE_REQUIRED:
exit_reason = "success"
break
else:
stable_count = 0
cp.note(f"poll: still rendering at {elapsed}s")
else:
# 首次截图且不是 placeholder——下一轮才有对比基准
cp.note(f"poll: first non-placeholder frame at {elapsed}s")
last_screenshot_path = current_path
time.sleep(POLL_INTERVAL_SEC)
elapsed += POLL_INTERVAL_SEC
cp.note(f"poll completed: exit_reason={exit_reason}, elapsed={elapsed}s")
# 按 exit_reason 走分支:
# "success" → 进 4.0.b 做最终兜底,正常路径
# "stuck" → 跳过 4.0.b,直接进 Step 4.0.d 失败处理
# "timeout" → 进 4.0.b 兜底,让单帧检查决定是失败还是慢成功
4.0.b — 最终 placeholder 兜底检查(仅当 exit_reason ∈ {"success", "timeout"} 时执行)
⚠️ 如果 4.0.a 已经判定
exit_reason == "stuck",跳过 4.0.b,直接进 Step 4.0.d。
looks_like_ardot_placeholder 与 compare_screenshots_for_stability 都是 AI 看图判定,可能误判。所以轮询结束后再做一次单帧检查:
if exit_reason in ("success", "timeout"):
final_check = mcp__ardot__capture_screenshot(
nodeIds=[frame_id],
screenShotDir=probe_dir,
)
final_path = final_check["screenshots"][0]["path"]
AI 看 final_path 单帧,严格逐项回答:
- 画面 > 90% 是纯白底? → 仍是 placeholder
- 居中有画架 / 调色板 / "Q" 或 "a" Logo 的浅灰线稿? → 仍是 placeholder
- 底部有"生成中"三个字? → 仍是 placeholder
- 整图只有浅灰描边、没有任何彩色或卡通元素? → 仍是 placeholder
- 是否看到与 prompt 主题相关的具体彩色卡通元素(标题文字 / 图标 / 卡通角色)?→ 已生成
按结果分流:
- 1-4 任一命中(仍是 placeholder):
- 如果
exit_reason == "timeout"且还没重试过:再等 15 秒,重做 4.0.b 一次(生成可能确实极慢) - 否则(已重试 / 或 exit_reason == "success" 但兜底判定矛盾):进 Step 4.0.d 失败处理
- 如果
- 第 5 项命中(已生成):进 Step 4.0.c 记录后进 Step 4.1
4.0.c — 在 checkpoint 记录等待结果(成功路径)
cp.note(f"generation wait completed: total {elapsed}s, exit_reason={exit_reason}")
Step 4.0.5 — ⚠️ 强制把 image fill 的 scaleMode 改为 FIT(防裁切,必须在生成稳定后做)
⚠️ 关卡顺序铁律:Step 3.4(G 调用)→ 4.0(等待生成稳定)→ 4.0.5(FIT 矫正,仅在 4.0 成功后) → 4.1(正式截图)→ 4.2(自检)。
📌 为什么 FIT 矫正必须在 4.0 之后(实测 2026-06-29 验证,详见 references/ardot-imagegen-gotchas.md §五 雷 2):
ardot G 操作返回是同步的,但生成是异步的。如果在 4.0 之前做 FIT 矫正,scaleMode 是基于 placeholder 设置的——当后端生成完成时会重新覆盖整个 fills 数组,把 scaleMode 重置回默认的
FILL。结果就是:你以为做了一次 FIT,实际真实图依然是 FILL 状态,自检看到的"无切边"是 placeholder 期的状态,真实图回来后又切边了。正确顺序:先等真实图稳定(imageHash 替换完毕、fills 已被后端最后一次覆盖),再做 FIT 矫正。这样矫正只生效一次,且作用在真实图上。
⚠️ 实测踩坑(2026-06-29):ardot G 操作生成的真实 AI 图,默认以 scaleMode="FILL" 应用到 frame —— 在非 1:1 frame上会强制裁切模型实际输出的 1:1 图片以填满容器,导致边缘元素被切。即使 prompt 已强约束 safe-zone,模型与目标比例的偏差仍会触发切边。
📌 本 skill 默认 frame 是 1:1(1024×1024),AI 图也是 1:1,二者匹配——FILL/FIT 模式视觉效果一样,无切边、无空白带。但 Step 4.0.5 仍强制把 scaleMode 改为 FIT 作为防御性兜底(如果未来 ardot 升级 AI 模型输出比例改变,FIT 不会切边而 FILL 会)。
修复:在 Step 4.0 通过后立刻用 U() 操作把 fills 数组中 IMAGE fill 的 scaleMode 强制改为 "FIT"。
# 先读出当前 fills(此时 imageHash 应该已经是真实图的 hash,scaleMode 是后端默认 FILL)
current = mcp__ardot__batch_read(
nodeIds=[frame_id],
properties=["fills"],
)
fills = current["nodes"][0].get("fills", [])
# 把所有 IMAGE 类型的 fill 的 scaleMode 改为 FIT
fixed_fills = []
needs_fix = False
for f in fills:
if f.get("type") == "IMAGE" and f.get("scaleMode") != "FIT":
f = {**f, "scaleMode": "FIT"}
needs_fix = True
fixed_fills.append(f)
if needs_fix:
# 用 U 写回(fills 必须以 JSON 字符串嵌入 batch_edit operations DSL)
import json
fills_json = json.dumps(fixed_fills)
safe_batch_edit(
operations=f'U("{frame_id}", {{fills: {fills_json}}})',
verify_node_ids=[frame_id],
)
cp.note("scaleMode forced to FIT (post-generation) to prevent edge cropping")
else:
cp.note("scaleMode already FIT, no fix needed")
4.0.d — 失败处理(卡死 / 兜底确认失败)
到这里说明 ardot 后端生成失败(卡在 placeholder)。不要继续 Step 4.1 截图、也不要把 placeholder 当成成品交付给用户。
📌 失败路径不需要 4.0.5 矫正——既然没有真实图,矫正无意义。FIT 矫正只在成功路径执行。
cp.note("⚠️ generation failed: ardot returned placeholder permanently")
4.0.d.1 — 调用 ask_followup_question 询问用户
⚠️ 必须分两小步——先真正调用 ask_followup_question 工具、等用户操作,拿到工具真实返回的字符串后再解析;绝对不要把"请选择:…"写成纯文本冒充工具调用(那会直接结束本轮、卡死流程),也绝对不要脑补 None / 空字符串去喂 helper。
调用 ask_followup_question:
- question:
⚠️ ardot 后端生成失败 画布上的 frame 长时间保持在"生成中"占位图状态,imageHash 不再变化。 常见原因:内容审核拦截 / 配额耗尽 / 后端 pipeline 临时故障。 你想怎么处理? - options:
["重新生成(同一 prompt 再试)", "调整 prompt 后重生(如可能涉及敏感词)", "取消并删除 frame", "保留 placeholder frame(我自己手动处理)"] - multiSelect: false
调用后立刻停下,等用户在对话框里点选。
4.0.d.2 — 拿到真实返回后才解析
ans = parse_user_answer(
<tool_return>,
options=[
"重新生成(同一 prompt 再试)",
"调整 prompt 后重生(如可能涉及敏感词)",
"取消并删除 frame",
"保留 placeholder frame(我自己手动处理)",
],
)
按用户选择分流:
| 用户选择 | 行为 |
|---|---|
cp.is_cancelled(ans) | cp.rollback() 删 frame → 结束 |
| "重新生成(同一 prompt 再试)" | 计数器 +1(同 4.2.c 切边重生),重走 Step 3.4 → 3.5 → 4.0(上限 2 次失败重试,2 次后强制提示用户改 prompt) |
| "调整 prompt 后重生" | 回到 Step 1 / Step 2 让用户调整(不删 frame,保留 frame_id 复用) |
| "取消并删除 frame" | cp.rollback() → 结束 |
| "保留 placeholder frame" | 不删 frame,跳过 4.1/4.2,直接进 Step 5 但汇报失败 + 标注"placeholder 仅占位、需手动处理" |
Step 4.1 — 正式截图(用于回执 + 自检)
⚠️ 必须在 Step 4.0 通过之后才执行。
screenshot = mcp__ardot__capture_screenshot(
nodeIds=[frame_id],
screenShotDir="/workspace/infographic-maker-ardot-screenshots",
)
Step 4.2 — 自检(占位符兜底 + 切边 + 质量)
AI 看截图自检,必须按下面顺序走完才能进入 Step 4.3:
4.2.a — Placeholder 兜底检测(⚠️ 最先做)
虽然 Step 4.0 已经轮询稳定了,仍要在自检阶段再次确认这不是 placeholder——以防 4.0 的"AI 看图比对"判断失误。
判定 placeholder 的实测硬指标(命中任一即视为仍在生成):
| # | 信号 | 描述 |
|---|---|---|
| 1 | 画架线稿 | 画面居中有一个画架(三脚架 + 顶部画板)的浅灰线稿 |
| 2 | 调色板 / Logo 线稿 | 画板内部有调色板 / "Q" 或 "a" 字母 / 几何符号的浅灰线稿 |
| 3 | "生成中" 文字 | 底部正中有"生成中"三个浅灰中文字(这是最强信号) |
| 4 | Sparkle 装饰 | 周围浮着几颗钻石形 / 四角星形的浅灰装饰 |
| 5 | 纯白主导 | 画面 > 90% 是纯白底,整图只有浅灰描边、没有任何彩色 |
| 6 | 缺彩色卡通元素 | 看不到任何彩色手绘卡通元素(cartoon characters / colorful icons / doodles) |
如果命中任一 1-6 → 回 Step 4.0 再等 15 秒后重试。最多回退 2 次;2 次后仍是 placeholder → 报错"生成失败",cp.rollback(),告知用户。
4.2.b — 切边检测 + prompt 副作用检测(基于已确认生成完成的图)
仔细看截图四边和整体,回答以下问题:
B1. 切边问题:
- 顶部:标题文字是否完整?最顶部 ~12% 区域是否有内容贴着画布上边界(如"?"上半部分丢失、字母被切)?
- 底部:最下一行内容是否完整?最底部 ~12% 区域是否有内容贴着画布下边界(如箭头被切、文字下半部分丢失)?
- 左/右边:边缘元素是否完整?
判定切边:满足以下任一条件 → 判定为"切边":
- 任何文字字符的笔画在画布边缘被截断
- 任何主体元素(卡通图标 / 角色 / 箭头)被画布边缘切到一半
- 视觉上看起来不像"完整海报",而像"海报的一部分"
⚠️ 不要把 placeholder 的灰边误判为切边 —— 那是 Step 4.2.a 该处理的。本步只在 4.2.a 通过后执行。
B2. Prompt 副作用检测(⚠️-1 实测 2026-06-29 发现):
⚠️ 必读 references/ardot-imagegen-gotchas.md §五 雷 5 —— AI 模型把"prompt 工程化术语"当成内容画了
判定 AI 是否把布局指令当成视觉内容画出来——满足以下任一条件 → 判定为"prompt 副作用":
| # | 副作用信号 | 描述 |
|---|---|---|
| 1 | 可见 padding 标注 | 画上有 "↔ 12%" / "↕ 15%" 等带百分号 + 双向箭头的标注 |
| 2 | 可见安全区矩形 | 画面中央有一个明显的浅色矩形边框(区分于内容的边界框) |
| 3 | 可见标尺 / 刻度 | 边缘画出尺子、刻度线、对齐参考线 |
| 4 | 可见 callout 箭头 | 从主体内容指向画布边缘的"标注箭头"(不是装饰用的箭头) |
| 5 | 可见技术 UI 元素 | 网格线、对齐辅助、"safe area" 文字、"padding indicator" 字样 |
两种问题处理路径相同(都进 4.2.c 重生),但 cp.note 标签不同:
edge_cropped = check_b1() # AI 看图判定,伪函数
prompt_artifact = check_b2() # AI 看图判定,伪函数
if edge_cropped or prompt_artifact:
issues = []
if edge_cropped: issues.append("edge_cropping")
if prompt_artifact: issues.append("prompt_visualization_artifact")
cp.note(f"4.2.b detected: {', '.join(issues)}")
# → 进入 Step 4.2.c 走自动重生
else:
# → 进入 Step 4.2.d 做其他质量自检
pass
4.2.c — 自动重生(最多 2 次;覆盖切边 + prompt 副作用两种问题)
如果 4.2.b 判定为切边 或 prompt 副作用:
⚠️ 重试的正确姿态:
- prompt 每次重置——不要在原 prompt 上不断追加,否则 2 次后 prompt 就膨胀成 500+ 字,反而质量下降。用
base_prompt(Step 2.3 拼装的原始版本)+ 本次 retry 的替换式 safe-zone 后缀。 - 循环控制流——用
while retry_count < 2循环重跑 Step 3.4 → 4.0 → 4.0.5 → 4.1 → 4.2;不要用伪代码GOTO(Python 没有 goto,AI 容易执行错)。 - 告知用户在做什么——重试期间可能持续 30-120 秒,如果不告知用户会以为 skill 卡死。用一句简短消息(不用 ask_followup_question):"⏳ 检测到 <edge_cropped/prompt_artifact>,正在第 <retry_count+1>/2 次调整 prompt 重生(约 30-60 秒)..."
# ⚠️ 关键:base_prompt 是 Step 2.3 拼装的原始版本,保持不变
# final_prompt 每次重生时都 = base_prompt + 当前 retry 后缀(替换,不累加)
retry_count = getattr(cp, "_retry_count", 0)
if retry_count < 2:
# 告知用户进度(避免以为 skill 卡死)
print(f"⏳ 检测到 {issues}({elapsed}s),正在第 {retry_count + 1}/2 次调整 prompt 重生(约 30-60 秒)...")
# ⚠️ prompt 重置而非追加:base_prompt + 本次 retry 的替换式后缀
retry_suffix = (
" CRITICAL FRAMING NOTE: The previous attempt had elements running off the canvas edges. "
"This time compose with even more generous whitespace around every edge — "
"imagine the design is a precious painting that must sit comfortably inside its frame "
"with substantial breathing room on all sides. Pull all content noticeably inward, "
"leaving the outer perimeter as calm decorative empty space. "
"Reminder: do NOT visualize any rulers, percentage labels, margin indicators, "
"callout arrows, or safe-zone rectangles — those are layout guides, not visual content."
)
final_prompt = base_prompt + retry_suffix
cp._retry_count = retry_count + 1
cp.note(f"4.2.c retry #{retry_count + 1}: reset prompt + safe-zone reinforcement")
# 回到 Step 3.4 重新生成 fill(同一个 frame_id,不创建新 frame)
# 重生后**必须按正确顺序**重走:Step 3.4(G)→ Step 4.0(等待稳定)→ Step 4.0.5(FIT 矫正)→ Step 4.1(截图)→ Step 4.2(自检)
# 用 while 循环控制而非伪代码 GOTO:
# while cp._retry_count <= 2 and self_check_failed:
# run_step_3_4_to_4_2()
continue_retry_loop()
else:
# 已重试 2 次仍切边 —— 转人工处理
cp.note("auto-retry exhausted (2 attempts), escalating to user in Step 4.3")
# 进入 Step 4.3 让用户决定(不再自动重生)
重试上限 = 2 次。不要无限重试 —— 每次 G 操作都消耗 ardot AI 生图配额,且模型可能始终无法满足约束。如果 2 次后仍有问题,把决定权交给用户。
⚠️ 实现要点:Step 2.3 拼装 prompt 时,除了记
final_prompt还要把它同时存到base_prompt变量,供本步 retry 用。伪代码:# Step 2.3 结尾: base_prompt = final_prompt # 保留原始版本,供 Step 4.2.c 重试时用
4.2.d — 其他质量自检
- 是否捕捉到核心主题?
- 各要点是否视觉上可区分?
- 文字是否可读、语言是否正确(手绘字体限制:AI 生图常有乱码风险)?
- 是否保持了手绘美学?无写实元素 / 3D / 库存图?
如果自检通过 → 进入 Step 4.3。 如果自检有问题但不是切边 → 进入 Step 4.3 询问用户是否调整。
Step 4.3 — 询问用户是否要调整
⚠️ 必须分两小步——先真正调用 ask_followup_question 工具、等用户点选,拿到工具真实返回的字符串后再解析;绝对不要把"请选择:…"写成纯文本冒充工具调用(那会直接结束本轮、导致停在"请选择"就没了),也绝对不要脑补 None / 空字符串去喂 helper。
Step 4.3.1 — 调用 ask_followup_question
调用 ask_followup_question 工具:
- question: "信息图已生成(见截图)。是否调整?"
- options:
["满意,结束", "调整要点", "调整色板 / 布局", "改信息密度", "完全重做(回 Step 1)"] - multiSelect:
false
调用后立刻停下,等待用户在对话框里点选。
Step 4.3.2 — 拿到真实返回后才解析
ans = parse_user_answer(
<tool_return>,
options=["满意,结束", "调整要点", "调整色板 / 布局", "改信息密度", "完全重做(回 Step 1)"],
)
判断分支:
| 用户选择 | 行为 |
|---|---|
cp.is_cancelled(ans) | cp.rollback() 删掉 frame → 结束并告知"已取消并回滚" |
| "满意,结束" | 进入 Step 5 |
| "调整要点" | 回到 Step 1(保留 frame,重做 prompt 后回到 Step 3.4 重生成 fill;重生后必须重走 Step 4.0 等待 + Step 4.0.5 FIT 矫正 + Step 4.1 截图 + Step 4.2 自检) |
| "调整色板 / 布局" | 回 Step 1 重选色板/布局,重做 prompt,回到 Step 3.4 重生成(同上重走 4.0/4.0.5/4.1/4.2) |
| "改信息密度" | 在 Step 2.3 的 prompt 上调整密度描述(如增减"more whitespace"/"add more details"),回 Step 3.4 重生成(同上重走 4.0/4.0.5/4.1/4.2) |
| "完全重做(回 Step 1)" | cp.rollback() 删掉当前 frame,从 Step 1 重头开始(开新的 cp 实例) |
关于"重生成 fill":直接对同一个
frame_id再调一次 Step 3.4 的 G 操作(ardot G 操作会替换 frame 上的 image fill)。不要再次调用 locate_available_space 或 Step 3.3 的 I 操作创建新 frame——会污染画布。⚠️ 重生后的关卡顺序不能省:每次重新调 G 操作后,必须按正确顺序重走 Step 3.4(G)→ Step 4.0(等待稳定)→ Step 4.0.5(FIT 矫正)→ Step 4.1(截图)→ Step 4.2(自检)。绝对不能在 Step 4.0 之前做 FIT 矫正——会被后端覆盖。否则会重现"调用返回但 placeholder 还在显示" + "对 placeholder 错误矫正"的 bug。
Step 5 — 报告结果 + 用户最终汇报
cp.commit() # 标记成功,清空 undo 链
# 把探测结果展示给用户(运行时透明)
probe_notes = cp.dump_notes()
print(f"""
✅ 信息图已生成
📁 画布 frame ID: {frame_id}
📐 尺寸: 1024 × 1024 (square 1:1,与 ardot AI 生图原生比例匹配)
📸 截图: /workspace/infographic-maker-ardot-screenshots/
🎨 风格: 手绘卡通 + 软色调
🗣️ 语言: <LANGUAGE>
📋 要点数: <n>
能力探测(⚠️-3):
{chr(10).join(" - " + n for n in probe_notes)}
💡 提示:
- 信息图作为 IMAGE fill 应用在 frame 上,可在 ardot 编辑器里继续手动调整
- 如需导出 PNG / PDF: mcp__ardot__export_nodes(nodeIds=["{frame_id}"], format="png" / "pdf")
""")
参考文件索引(按需读取)
⚠️ 这些文件不会自动加载,必须按上述步骤的指引主动 read_file 读取。
references/style-guide.md—— 提示词模板 + 7 套色板预设 + 5 种布局 + 视觉元素库 + 文字渲染对策 + 完整双语示例references/ardot-imagegen-gotchas.md—— ardot 生图接口的踩坑分析(接口契约根因 / placeholder 视觉特征 / 判定状态机 / 雷区清单)
Skill 包资源
scripts/helpers/checkpoint.py—— 多步操作的副作用追踪 + 取消回滚(约束 #6)scripts/helpers/parse_multiselect.py—— ask_followup_question 返回值解析(约束 #4)scripts/helpers/safe_batch_edit.py—— batch_edit 的安全封装(约束 #2)scripts/helpers/probe_capability.py—— ⚠️-3 能力探测(仅 Step 3 使用,运行时降级)