Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions cat-cafe-skills/image-generation/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
name: image-generation
description: >
AI 图片生成:原生 tool call(Codex/Antigravity)或浏览器自动化(Gemini/ChatGPT)。
AI 图片生成:原生 tool call(Codex/Antigravity)、OpenAI 兼容图像网关(无原生 tool 的猫在已注入网关 key 时首选)或浏览器自动化(Gemini/ChatGPT)。
Use when: 需要 AI 生成概念图、UI 参考、像素画素材、完整 PPT 页面、复杂架构图、信息图或视觉 mock。
Not for: 已有图片的展示(用 media_gallery rich block)、硬要求可编辑/native text 的 PPT/图表(用 PPT/HTML 管线)。
Output: 生成图片自动发布,或作为完整视觉 mock / 图像素材进入后续交付。
---

# AI 图片生成 Skill

> 用途:生成 AI 图片——优先原生 tool call,降级浏览器自动化
> 用途:生成 AI 图片——优先原生 tool call,无原生 tool 走 API 网关,最后降级浏览器自动化
> 适用猫猫:所有猫

## 何时使用
Expand Down Expand Up @@ -73,9 +73,11 @@ Codex `image_gen` 不只是“概念图/素材生成器”。当前实测能力
├─ 是(Codex / Antigravity)→ 用原生 tool call(§ 原生路径)
│ 优势:快、自动发布到气泡、无需浏览器
├─ 否(Claude / 其他)→ 能 shell out 到有能力的 CLI 吗?
│ ├─ 是 → 借用(§ 跨引擎借用)
│ └─ 否 → 浏览器自动化(§ 浏览器路径)
├─ 否(Claude / Kimi / 其他)→ 部署配置了图像网关 key 吗?
│ ├─ 是 → API 网关出图(§ API 网关路径)
│ ├─ 否 → 能 shell out 到有能力的 CLI 吗?
│ │ ├─ 是 → 借用(§ 跨引擎借用)
│ │ └─ 否 → 浏览器自动化(§ 浏览器路径)
└─ 需要特定风格控制 / inpainting / 局部编辑?
└─ 是 → 即使有原生能力也走浏览器路径
Expand All @@ -86,6 +88,7 @@ Codex `image_gen` 不只是“概念图/素材生成器”。当前实测能力
|------|------|------|---------|-------------|
| **原生** | **Codex CLI** | 内置 `image_gen` tool call | `~/.codex/generated_images/<sessionId>/` | ✅ scanner 自动拾取 |
| **原生** | **Antigravity** | 内置 `generate_image` tool call | `~/.gemini/antigravity/brain/<cascadeId>/` | ✅ GENERATE_IMAGE step 自动拾取 |
| **API 网关** | OpenAI 兼容网关 | `scripts/imggen-call.mjs`(需注入网关 key) | 直接写入 uploadDir | 需手动发 `media_gallery` 富块 |
| 浏览器 | Gemini Web | Chrome MCP 自动化 | 本地下载目录 | 需手动 `publishGeneratedImage()` |
| 浏览器 | ChatGPT Web | Chrome MCP 自动化 | 本地下载目录 | 需手动 `publishGeneratedImage()` |

Expand Down Expand Up @@ -144,6 +147,36 @@ codex exec "生成一张猫咖全景图,手绘水彩风格"

---

## API 网关路径(无原生 tool 且已注入网关 key 时首选)

**谁能用**:任何能跑 Bash 的猫(Claude / Kimi / opencode 系),前提是部署配置了图像网关。

**模型分工**(以 `GET $IMAGE_GATEWAY_BASE_URL/models` 实际返回为准——key 属于哪个分组决定能用哪些模型):

| 模型 | 引擎 | 何时选 |
|------|------|--------|
| `gpt-image-2` | OpenAI 系 | 默认;含中文文字必选(Google 系中文乱码,实测) |
| `gpt-image-gemini-3-flash` | Gemini 系 | 快速草图 / 批量变体 |
| `gpt-image-gemini-3-pro` | Gemini 系 | 高质量成稿、无中文文字 |

**出图**(prompt 走 stdin,避免引号/换行破坏 JSON body):

```bash
node cat-cafe-skills/image-generation/scripts/imggen-call.mjs --model gpt-image-2 <<'PROMPT'
<你的完整 prompt,引号和换行随便写>
PROMPT
```

脚本负责:缺 env 立即中止、按需选模型、`--max-time 300` 等价超时、b64 解码落盘到 uploadDir(不进上下文)、打印 `/uploads/` 相对路径。参数与错误处理见 `scripts/imggen-call.mjs --help`。

**进气泡**:把脚本打印的 `/uploads/<文件名>.png` 交给 `cat_cafe_create_rich_block` 发 media_gallery,块结构见 `refs/rich-blocks.md`(url 必须是 `/uploads/` 相对路径,不要本地绝对路径)。

**GOTCHA**:
- 出图慢是常态(实测单张 ~100s、响应 >1.5MB),脚本超时给 300s;120s 会在下载途中被掐断
- `model_not_found` ≠ 网关挂了:是**这把 key 的分组**没有该模型渠道,先 `GET /models` 核对(不要重试硬闯——参见 clowder-ai#1236 的卡死教训)

---

## 浏览器路径(降级 / 需要风格控制时使用)

### Gemini 画图(浏览器)
Expand Down
142 changes: 142 additions & 0 deletions cat-cafe-skills/image-generation/scripts/imggen-call.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env node
// OpenAI 兼容图像网关出图:prompt 从 stdin 读,图片落到 uploadDir,打印 /uploads/ 相对路径。
// 用法:node imggen-call.mjs [--model <name>] [--size 1024x1024] [--upload-dir <abs>] <<'PROMPT' ... PROMPT

import { createHash } from 'node:crypto';
import { mkdir, writeFile } from 'node:fs/promises';
import { isAbsolute, join, resolve } from 'node:path';

const TIMEOUT_MS = 300_000; // 实测单张 ~100s、响应 >1.5MB,120s 会在下载途中被掐断
const DEFAULT_MODEL = 'gpt-image-2';
const DEFAULT_SIZE = '1024x1024';

function fail(what, why, how) {
console.error(`imggen 失败:${what}\n原因:${why}\n修复:${how}`);
process.exit(1);
}

if (process.argv.includes('--help')) {
console.log(
[
'用法:node imggen-call.mjs [选项] <<\'PROMPT\' ... PROMPT',
'',
' --model <name> 默认 gpt-image-2;含中文文字必选 gpt-image-2(Google 系中文乱码)',
' --size <WxH> 默认 1024x1024',
' --upload-dir <path> 绝对路径,覆盖 UPLOAD_DIR;必须是当前 API 正在服务的那一份 uploads/',
'',
'环境变量(由部署者通过账号 envVars 注入,脚本不含任何硬编码凭据):',
' IMAGE_GATEWAY_BASE_URL OpenAI 兼容网关根,需含 /v1,例 https://host/v1',
' IMAGE_GATEWAY_API_KEY 该网关的 key;key 所属分组决定可用模型',
' UPLOAD_DIR /uploads/ 静态服务的根目录(绝对路径)',
].join('\n'),
);
process.exit(0);
}

function readFlag(name) {
const i = process.argv.indexOf(name);
return i === -1 ? undefined : process.argv[i + 1];
}

const baseUrl = process.env.IMAGE_GATEWAY_BASE_URL?.replace(/\/+$/, '');
if (!baseUrl) {
fail(
'API 网关路径不可用',
'未注入 IMAGE_GATEWAY_BASE_URL',
'向 operator 报告网关未配置(账号 envVars),不要猜地址;或改走原生/浏览器路径',
);
}
if (!process.env.IMAGE_GATEWAY_API_KEY) {
fail(
'API 网关路径不可用',
'未注入 IMAGE_GATEWAY_API_KEY',
'向 operator 报告网关 key 未配置(账号 envVars),不要猜 key',
);
}

const uploadDir = readFlag('--upload-dir') ?? process.env.UPLOAD_DIR;
if (!uploadDir) {
fail(
'无法确定落盘目录',
'未注入 UPLOAD_DIR,且未传 --upload-dir',
'传当前 API 正在服务的 uploads/ 绝对路径(默认为 <运行时>/packages/api/uploads);不要用 worktree 内的副本,否则气泡里会裂图',
);
}
if (!isAbsolute(uploadDir)) {
fail(
'落盘目录不合法',
`UPLOAD_DIR/--upload-dir 必须是绝对路径,收到 "${uploadDir}"`,
'相对路径按当前 cwd 解析,会写到 API 没在服务的目录;改传绝对路径',
);
}

const prompt = await new Promise((res, rej) => {
let buf = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (c) => {
buf += c;
});
process.stdin.on('end', () => res(buf.trim()));
process.stdin.on('error', rej);
});
if (!prompt) {
fail('没有 prompt', 'stdin 为空', "用 heredoc 传入:node imggen-call.mjs <<'PROMPT' ... PROMPT");
}

const model = readFlag('--model') ?? DEFAULT_MODEL;
const size = readFlag('--size') ?? DEFAULT_SIZE;
const endpoint = `${baseUrl}/images/generations`;

let payload;
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.IMAGE_GATEWAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ model, prompt, n: 1, size }),
signal: AbortSignal.timeout(TIMEOUT_MS),
});
const text = await res.text();
try {
payload = JSON.parse(text);
} catch {
fail(
`网关返回非 JSON(${endpoint}, HTTP ${res.status})`,
text.slice(0, 300),
'HTTP 404 通常是 IMAGE_GATEWAY_BASE_URL 少了 /v1;其他状态码把原文报给 operator',
);
}
} catch (err) {
const why = err?.name === 'TimeoutError' ? `超过 ${TIMEOUT_MS / 1000}s 未返回` : String(err?.message ?? err);
fail(`请求网关失败(${endpoint})`, why, '确认网关可达与 base URL 正确后重试一次;连续失败报给 operator,不要循环硬闯');
}

if (payload.error) {
const msg = payload.error.message ?? JSON.stringify(payload.error);
fail(
`模型 ${model} 出图被网关拒绝`,
msg,
`model_not_found 表示这把 key 的分组没有该渠道:先 GET ${baseUrl}/models 核对逐字模型名,换模型或换 key 分组;不要重试硬闯(clowder-ai#1236)`,
);
}

const b64 = payload.data?.[0]?.b64_json;
if (!b64) {
fail(
`模型 ${model} 未返回图片数据`,
`响应缺少 data[0].b64_json,顶层字段:${Object.keys(payload).join(', ') || '(空)'}`,
'确认该模型走 b64 返回;若网关只给 url,把响应结构报给 operator',
);
}

// 幂等 stem:同一 prompt+model+size 重复出图复用同一文件,对齐 F172 buildPublicationStem 的 sha256 前 8 位约定
const stem = `imggen-${model}-${createHash('sha256').update(`${model}|${size}|${prompt}`).digest('hex').slice(0, 8)}`.replace(
/[^a-zA-Z0-9._-]/g,
'-',
);
const bytes = Buffer.from(b64, 'base64');
await mkdir(uploadDir, { recursive: true });
await writeFile(join(resolve(uploadDir), `${stem}.png`), bytes);
console.log(`/uploads/${stem}.png ${bytes.length} bytes`);
7 changes: 4 additions & 3 deletions cat-cafe-skills/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1133,10 +1133,11 @@ skills:
image-generation:
category: "创作与媒体"
description: >
通过 Chrome MCP 浏览器自动化,在 Gemini / ChatGPT 上生成图片并下载。
AI 图片生成三路径:原生 tool call(Codex/Antigravity)、OpenAI 兼容图像网关
(需部署注入网关 key)、Chrome MCP 浏览器自动化(Gemini/ChatGPT)。
Use when: 需要生成概念图、UI 参考图、像素画素材、猫猫头像、任何图片资产。
Not for: Pencil .pen 文件内的简单形状/布局(用 pencil-design)。
Output: 生成的图片文件(PNG)保存到项目 assets/ 目录。
Output: 生成的图片发布到 /uploads/ 并以 media_gallery 进气泡,或保存到项目 assets/ 目录。
triggers:
- "生成图片"
- "画一张"
Expand All @@ -1147,7 +1148,7 @@ skills:
not_for:
- "Pencil 内部设计"
- "简单形状布局"
output: "Generated PNG saved to assets/"
output: "Generated image published to /uploads/ (media_gallery) or saved to assets/"
next: []
sop_step: null
merged_from: null
Expand Down
2 changes: 1 addition & 1 deletion cat-cafe-skills/refs/capability-wakeup-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ opus-47 原把 `workspace-navigator` / `rich-messaging` / `browser-preview` 一
- PPT 内容页配图
- 信息图 / 像素画素材

**Backend 路由**:原生 tool call (Codex/Antigravity) / 浏览器自动化 (Gemini/ChatGPT)
**Backend 路由**:原生 tool call (Codex/Antigravity) / API 图像网关(需部署注入网关 key)/ 浏览器自动化 (Gemini/ChatGPT)
**边界**:硬要求可编辑 / native text → 用 PPT/HTML 管线,不用 image-generation

### 4. `workspace-navigator` — 程式打开文件到 Workspace panel
Expand Down
Loading