# 流式生成与恢复

本站提供两种流式接法。新应用建议使用异步任务加 SSE，方便持久化任务 ID 和断线恢复；已有支持官方流式格式的客户端可以使用 `/v1/novelai/ai/generate-image-stream`，客户端 Base URL 填写 `https://api.hamubox.com/v1/novelai`。

两者都使用本站 API Key。仅 V4、V4.5、V5 的生成操作可用，每次请求只生成一张，`parameters.n_samples` 省略或填写 `1`；模型和参数限制见[模型与绘图参数](https://api.hamubox.com/docs/novelai/models/)。V3、Director、放大和氛围编码不提供中间生成过程。

## 异步任务加 SSE

按[异步任务示例](https://api.hamubox.com/docs/novelai/tasks/#提交任务)先 POST `/v1/novelai/tasks`，请求顶层添加 `"stream": true`；立即保存 HTTP 202 返回的 `id`。随后订阅：

```bash
curl --no-buffer --fail-with-body \
  'https://api.hamubox.com/v1/novelai/tasks/task_your_existing_id/events' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY" \
  -H 'Accept: text/event-stream'
```

响应为 `text/event-stream`，事件的 `data` 是 JSON：

| 事件 | `data` 内容 | 客户端处理 |
| --- | --- | --- |
| `state` | 与 GET 任务相同的完整响应 | 更新状态；`completed` / `failed` 停止订阅 |
| `preview` | `step`、`sample`、`image`（图片 Data URL） | 临时预览，不能当成最终结果 |
| `image` | `sample`、`filename`、`image`（完整成图 Data URL） | 可立即清晰显示和下载；继续等待归档/结算状态 |

`step`、`sample` 从 0 开始。以 `:` 开头的注释是心跳。中间帧可能被合并或跳过，不保证每一步都有事件；没有预览也不表示生成失败。只做状态订阅而未在提交时开启 `stream`，不会把普通任务变成流式生成。

浏览器原生 `EventSource` 不能直接设置 Authorization 请求头，建议使用 `fetch` 读取响应流并按 SSE 规则解析。不要把 API Key 放进 URL。支持跨网络 chunk 累积文本，遇到空行才结束一个事件，最终图的 Data URL 可能很长。

## 官方格式入口

POST `/v1/novelai/ai/generate-image-stream` 使用官方生成 JSON，**不包含**异步接口的 `payload`、`operation` 外层包装：

```json title="novelai-native-stream.json"
{
  "input": "a blue ceramic cup on a wooden table, soft daylight, illustration",
  "model": "nai-diffusion-5-curated",
  "action": "generate",
  "parameters": {
    "params_version": 4,
    "width": 832,
    "height": 1216,
    "steps": 23,
    "scale": 5,
    "sampler": "k_euler_ancestral",
    "noise_schedule": "karras",
    "seed": 42,
    "n_samples": 1,
    "stream": "msgpack",
    "image_format": "png",
    "uc": "lowres, blurry",
    "v4_prompt": {
      "caption": {
        "base_caption": "a blue ceramic cup on a wooden table, soft daylight, illustration",
        "char_captions": []
      },
      "use_coords": false,
      "use_order": true
    },
    "v4_negative_prompt": {
      "caption": {
        "base_caption": "lowres, blurry",
        "char_captions": []
      },
      "use_coords": false,
      "use_order": false
    }
  }
}
```

```bash
curl --fail-with-body --no-buffer \
  'https://api.hamubox.com/v1/novelai/ai/generate-image-stream' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @novelai-native-stream.json \
  --dump-header stream-headers.txt --output result.msgpack
```

`result.msgpack` 是带长度帧的事件流，**不是 PNG，也不是 ZIP**。不要直接改名成图片。

服务端在响应头 `X-NovelAI-Task-Id` 中返回本站任务 ID。客户端应在读图片前就保存它，以便查询 `/v1/novelai/tasks/{task_id}`。本站原生入口也会创建任务记录，并使用相同的计费、留档和资产入库流程。

### Multipart 素材

支持 `multipart/form-data`：`request` 字段为 JSON 文本或 JSON Blob，其余字段是图片文件。JSON 中的 `parameters.image` / `mask` 指向对应字段名，例如：

```json title="request.json"
{
  "input": "a blue ceramic cup, watercolor illustration",
  "model": "nai-diffusion-5-curated",
  "action": "img2img",
  "parameters": {
    "params_version": 4,
    "width": 832,
    "height": 1216,
    "steps": 23,
    "scale": 5,
    "sampler": "k_euler_ancestral",
    "noise_schedule": "karras",
    "seed": 42,
    "n_samples": 1,
    "stream": "msgpack",
    "image_format": "png",
    "image": "source_image",
    "strength": 0.6,
    "noise": 0
  }
}
```

```bash
curl --fail-with-body --no-buffer \
  'https://api.hamubox.com/v1/novelai/ai/generate-image-stream' \
  -H "Authorization: Bearer $HAMUBOX_API_KEY" \
  -F 'request=@request.json;type=application/json' \
  -F 'source_image=@source.png;type=image/png' \
  --dump-header stream-headers.txt --output result.msgpack
```

V4/V4.5 的 `reference_image_multiple_cached[]`、`director_reference_images_cached[]` 支持 `data` 和 `cache_secret_key`。首次带数据，后续可以只带缓存键；缓存按本站用户隔离，失效时按官方格式返回 `INVALID_CACHE_KEYS`，客户端应补传素材。普通数组也支持 multipart 字段引用。不能同时发送同一数组的普通字段和 `_cached` 字段，详见[原生接口](https://api.hamubox.com/docs/novelai/native/#参考素材缓存)。

### MessagePack 帧格式

响应 `Content-Type: application/msgpack`，每一帧为：

```text
4 字节无符号大端长度 N | N 字节 MessagePack map
```

| `event_type` | 字段 | 含义 |
| --- | --- | --- |
| `intermediate` | `image` 二进制、`samp_ix`、`step_ix` | 中间图，通常为 JPEG |
| `final` | `image` 二进制、`samp_ix` | 完整最终图；按实际文件格式解析 |
| `error` | `message` | 流内错误，不能只检查 HTTP 200 |
| `heartbeat` | 可忽略其他字段 | 保持连接 |

`image` 在这个入口中是 MessagePack 的二进制值，**不是 Base64**。只有上述 SSE 的图片字段使用 Data URL。网络 chunk 可能只包含半帧，也可能包含多帧，必须精确累计长度；主站当前单帧上限为 32 MiB。

收到完整 `final` 后即可展示或保存该图片，不需要等连接自然 EOF。尚未收到完整成品帧就遇到 EOF、截断、错误事件，不能把预览当作生成成功。

### Python 示例

[下载 novelai-stream.py](https://api.hamubox.com/docs/examples/novelai-stream.py)，需要 Python 3.10+ 和 `msgpack`：

```bash
python -m pip install msgpack
export HAMUBOX_API_KEY='sk-your-api-key'
python novelai-stream.py novelai-native-stream.json --output result.png
```

此 Python 示例面向 `n_samples: 1`，不自动重试 POST。它先保存不含 Key 的任务 ID 文件，再解析长度帧，将最终图原样写入磁盘。请求 WebP 时使用 `.webp` 文件名。需要稳定结果 URL 与最终费用时，用[异步客户端](https://api.hamubox.com/docs/examples/client.py)继续查询：

```bash
python client.py novelai --task-id task_your_existing_id
```

## 成图、元数据与恢复

未指定 `parameters.image_format` 时，主站流式请求默认向上游请求 PNG；显式 `webp` 不强制转码。已验证 V5 可直接流式返回带生成元数据的 PNG。中间 JPEG 的格式不受最终格式选项约束。

最终图片收齐并校验后，优先通过 `image` / `final` 交付；后台仍会完成存储和结算。快速交付缓存上限为每批结果 32 MiB；超限或缓存不可用时等待持久化后交付。成图中的既有元数据保留，PNG 缺失时补充；实时交付与后续转存使用相同的处理后字节。不要把提前收到图片等同于持久化成功，最终状态和实际费用以任务查询为准，流式响应 Trailer 可能没有尚未完成的结算值。

| 情况 | 恢复方式 |
| --- | --- |
| 浏览器、客户端到本站的连接断开 | 后台任务继续；用保存的任务 ID 查询，也可重新 GET SSE |
| SSE 重连时没有旧预览或成图事件 | 正常查询任务；实时缓存只临时保留，不保证事件历史重放，不支持 `Last-Event-ID` 精确续播 |
| 收到成图但任务仍在进行 | 可以展示和下载，同时等待同一任务的稳定链接与结算 |
| 收到成图后转存失败 | 显示任务错误；保留本地已收到的图片，不要自动重新生成 |
| 本站与上游之间的流在完整成图到达前中断 | 不保证找回成图；当前官方流式协议没有可凭本站任务 ID 再取原图的补查接口 |
| 连接已结束但没有成图 | 先查原任务，不能直接判定需要重发付费请求 |

SSE/原生流单连接最长等待 10 分钟，到时结束连接并不取消后台任务。客户端超时也不取消任务。中间图不做持久化；最终图的临时缓存不可替代文件归档。不要把 Base64 图片、API Key 或完整二进制流写进应用日志。
