# VORTEX API 图片接口示例

安装完整技能包后，优先调用 `scripts/node/image-gen.js` 或 `scripts/python/image_gen.py`；本文件用于移植到其他语言。

## 统一约定

- 默认模型为 `gpt-image-2`，可通过 `--model` 显式选择其他图片模型；配置快照及验证边界见 [SKILL.md](../SKILL.md#模型与响应方式)。
- 默认使用 `stream: true`、`response_format: "b64_json"`，`Accept` 为 `text/event-stream`。
- 非流式请求使用 `stream: false`、`Accept: application/json`，保持 `response_format: "b64_json"`。multipart 的 stream 字段使用字符串 `true` 或 `false`。
- 文生图：`POST https://vortexapi.xyz/v1/images/generations`。
- 图生图、文+图生图：`POST https://vortexapi.xyz/v1/images/edits`。
- 带图请求使用 multipart；可上传本地文件、data URL 解码后的文件或远程 URL 下载后的文件，并重复 `image` 字段传入多张。

网关可能把上游 JSON 包装成 SSE 完成事件；SSE 不保证逐帧预览。下方流式代码是接口移植示例；完整的超时、多图收集、JSON/SSE 响应兼容和错误处理以随包脚本为准。失败后不自动重试或切换模型，避免重复计费。

## 非流式文生图

下面使用显式模型和非流式 JSON。请先确认当前密钥可见该模型；可见性不代表所有参数组合均已验收。

```bash
SKILL_DIR="/absolute/path/to/image-gen"
node -- "$SKILL_DIR/scripts/node/image-gen.js" --doctor --model gpt-image-2.5-sunburst
node -- "$SKILL_DIR/scripts/node/image-gen.js" --model gpt-image-2.5-sunburst --stream false --prompt "生成一张太空猫" --out ./sunburst.png
```

等价请求体：

```json
{"model":"gpt-image-2.5-sunburst","prompt":"生成一张太空猫","n":1,"size":"1024x1024","quality":"auto","stream":false,"response_format":"b64_json"}
```

成功响应中的 `data[].b64_json` 解码后即为图片字节。CLI 会保存为绝对路径，并在 stdout JSON 的 `paths` 中返回；不需要自行解析响应。

## 文生图

```python
import base64
import json
import os
import urllib.request
from pathlib import Path

API_URL = "https://vortexapi.xyz/v1/images/generations"
API_KEY = os.environ["IMAGE_GEN_API_KEY"]

body = {
    "model": "gpt-image-2",
    "prompt": "生成一张太空猫",
    "n": 1,
    "size": "1024x1024",
    "quality": "auto",
    "stream": True,
    "response_format": "b64_json",
}

request = urllib.request.Request(
    API_URL,
    data=json.dumps(body).encode(),
    method="POST",
    headers={
        "Authorization": "Bearer " + API_KEY,
        "Content-Type": "application/json",
        "Accept": "text/event-stream",
    },
)

with urllib.request.urlopen(request, timeout=900) as response:
    buffer = ""
    while chunk := response.read(4096):
        buffer += chunk.decode("utf-8", errors="replace")
        frames = buffer.split("\n\n")
        buffer = frames.pop()
        for frame in frames:
            data = "\n".join(line[5:].strip() for line in frame.splitlines() if line.startswith("data:"))
            if not data or data == "[DONE]":
                continue
            event = json.loads(data)
            image = event.get("b64_json") or (event.get("data") or [{}])[0].get("b64_json")
            if image:
                Path("generated-image.png").write_bytes(base64.b64decode(image))
                raise SystemExit("图片已保存：generated-image.png")
```

## 图生图 / 文+图生图

两种模式使用同一个 multipart 接口。区别是：图生图可以省略 `prompt`（脚本会补充默认提示词），文+图生图必须提供自己的 `prompt`。接口响应可能是普通 JSON 或 SSE。

```python
import base64
import json
import os
import urllib.request
import uuid
from pathlib import Path

API_URL = "https://vortexapi.xyz/v1/images/edits"
API_KEY = os.environ["IMAGE_GEN_API_KEY"]

boundary = "----vortex-" + uuid.uuid4().hex
fields = {"model": "gpt-image-2", "prompt": "融合两张参考图，保留主体并生成统一的科技风画面。", "n": "1", "size": "1024x1024", "quality": "auto", "stream": "true", "response_format": "b64_json"}
images = [("a.png", Path("a.png").read_bytes(), "image/png"), ("b.png", Path("b.png").read_bytes(), "image/png")]
payload = bytearray()
for key, value in fields.items():
    payload.extend(f"--{boundary}\r\nContent-Disposition: form-data; name=\"{key}\"\r\n\r\n{value}\r\n".encode())
for filename, data, content_type in images:
    payload.extend(f"--{boundary}\r\nContent-Disposition: form-data; name=\"image\"; filename=\"{filename}\"\r\nContent-Type: {content_type}\r\n\r\n".encode())
    payload.extend(data)
    payload.extend(b"\r\n")
payload.extend(f"--{boundary}--\r\n".encode())

request = urllib.request.Request(
    API_URL,
    data=bytes(payload),
    method="POST",
    headers={
        "Authorization": "Bearer " + API_KEY,
        "Content-Type": "multipart/form-data; boundary=" + boundary,
        "Accept": "text/event-stream",
    },
)

with urllib.request.urlopen(request, timeout=900) as response:
    buffer = ""
    while chunk := response.read(4096):
        buffer += chunk.decode("utf-8", errors="replace")
        frames = buffer.split("\n\n")
        buffer = frames.pop()
        for frame in frames:
            data = "\n".join(line[5:].strip() for line in frame.splitlines() if line.startswith("data:"))
            if not data or data == "[DONE]":
                continue
            event = json.loads(data)
            if event.get("type") == "image_generation.partial_image":
                continue
            image = event.get("b64_json") or (event.get("data") or [{}])[0].get("b64_json") or (event.get("item") or {}).get("result")
            if image:
                Path("generated-image.png").write_bytes(base64.b64decode(image))
                raise SystemExit("图片已保存：generated-image.png")
```

## CLI 示例

先将已安装的 `image-gen` 技能目录解析为绝对路径：

```bash
SKILL_DIR="/absolute/path/to/image-gen"
node -- "$SKILL_DIR/scripts/node/image-gen.js" --doctor
node -- "$SKILL_DIR/scripts/node/image-gen.js" --mode text --prompt "生成一张太空猫" --out ./cat.png
node -- "$SKILL_DIR/scripts/node/image-gen.js" --mode image --image ./reference.png --out ./variation.png
node -- "$SKILL_DIR/scripts/node/image-gen.js" --mode image-prompt --prompt "融合参考图" --image ./a.png --image ./b.png --out ./result.png
python3 "$SKILL_DIR/scripts/python/image_gen.py" --mode image-prompt --prompt "融合参考图" --images ./a.png,./b.png --out ./result.png
```

成功时脚本在 stdout 输出 JSON，失败时在 stderr 输出 JSON；不会打印 API Key。`--mode edit` 仍是 `image-prompt` 的旧兼容别名。
