# lehuoAI Hono API 接口文档（可复制）

- OpenAPI JSON：`GET /openapi.json`
- Markdown 原文：`GET /docs.md`

## 数据结构

### DemoTask

```ts
type DemoTask = {
  name: string
  slug: string
  description?: string
  completed: boolean
  due_date: string // ISO 8601 datetime
}
```

## Demo Tasks（示例任务）

### 1) 列出任务

- `GET /api/tasks`
- Query
  - `page`：number，默认 `0`
  - `isCompleted`：`true` | `false`（可选）

参考响应（200）：

```json
{
  "success": true,
  "tasks": [
    {
      "name": "lorem",
      "slug": "lorem-1",
      "description": "任务描述（可选）",
      "completed": false,
      "due_date": "2026-01-01T00:00:00.000Z"
    }
  ]
}
```

### 2) 创建任务

- `POST /api/tasks`
- Body（`application/json`）：DemoTask

参考响应（200）：

```json
{
  "success": true,
  "task": {
    "name": "lorem",
    "slug": "lorem-1",
    "description": "任务描述（可选）",
    "completed": false,
    "due_date": "2026-01-01T00:00:00.000Z"
  }
}
```

参考响应（409）：

```json
{
  "success": false,
  "error": "Task slug already exists"
}
```

### 3) 获取单个任务

- `GET /api/tasks/{taskSlug}`

参考响应（200）：

```json
{
  "success": true,
  "task": {
    "name": "lorem",
    "slug": "lorem-1",
    "description": "任务描述（可选）",
    "completed": false,
    "due_date": "2026-01-01T00:00:00.000Z"
  }
}
```

参考响应（404）：

```json
{
  "success": false,
  "error": "Task not found"
}
```

### 4) 删除任务

- `DELETE /api/tasks/{taskSlug}`

参考响应（200）：

```json
{
  "success": true,
  "task": {
    "name": "lorem",
    "slug": "lorem-1",
    "description": "任务描述（可选）",
    "completed": false,
    "due_date": "2026-01-01T00:00:00.000Z"
  }
}
```

参考响应（404）：

```json
{
  "success": false,
  "error": "Task not found"
}
```

## 通用校验错误（OpenAPI 路由）

- HTTP 400

```json
{
  "success": false,
  "error": "请求参数不合法",
  "issues": []
}
```

---

## 外站 Public API（JWT / X-API-Key）

用于“其他网站”或“画布内登录用户”调用绘图/视频/任务查询接口：`JWT` 或 `API Key` 二选一即可。

### 1) 认证与安全

- 登录用户（画布内推荐）：
  - `Authorization: Bearer <tap_token>`（登录 JWT；用于鉴权/计费/资源归属）
- 外部系统（推荐）：
  - `X-API-Key: tc_sk_...`
  - 或（兼容）`Authorization: Bearer tc_sk_...`
- 同时携带（可选）：
  - `X-API-Key: tc_sk_...`（通道 Key）
  - `Authorization: Bearer <tap_token>`（登录 JWT）
  - 计费与归属：优先使用 JWT 对应的用户（“谁用扣谁的费”）
- CORS：
  - 已允许任意 `Origin` 跨站调用（仍需具备 JWT 或 API Key）。

### 2) 通用返回结构

大部分接口返回：

```json
{
  "vendor": "auto 或具体厂商",
  "result": {
    "id": "task id",
    "kind": "text_to_image | image_edit | text_to_video | ...",
    "status": "queued | running | succeeded | failed",
    "assets": [{ "type": "image|video", "url": "...", "thumbnailUrl": null }],
    "raw": {}
  }
}
```

当 `status` 为 `queued/running` 时，用 `/public/tasks/result` 轮询结果。

### 3) 绘图

- `POST /public/draw`

请求体（简化版）：

```json
{
  "vendor": "auto",
  "prompt": "一张电影感海报…",
  "kind": "text_to_image",
  "extras": { "modelAlias": "nano-banana-pro", "aspectRatio": "1:1" }
}
```

说明：
- `vendor=auto` 会在系统级已启用且已配置的厂商列表中依次尝试，直到成功或候选耗尽（可用厂商来自 `/model-catalog/vendors`；顺序可能基于近期成功率动态排序）。
- `extras.modelAlias` 用于选择模型（Public 统一别名；推荐；默认=同 modelKey）。不同厂商可以配置同一个别名，从而让外部调用不用关心具体厂商的 modelKey。
- 兼容：仍支持 `extras.modelKey`（厂商内 modelKey），但不建议对外暴露。
- 当 `vendor=auto` 且未传 `extras.modelAlias` 时，服务端会优先按 `extras.modelKey` 做真实 `model_key` 匹配；只有在找不到可用 `model_key` 时，才会把它当作别名做兼容解析。
- 当调用方把真实 `model_key` 误传进 `extras.modelAlias` 时，服务端也会先按 alias 查找；若 alias 不存在，再按精确 `model_key` 做兼容解析。

请求体（完整字段，按需填写）：
- `vendor?: string`（默认 `auto`）
- `vendorCandidates?: string[]`（可选；仅当 `vendor=auto` 时生效：限制候选厂商范围，例如 `["apimart"]`）
- `kind?: "text_to_image" | "image_edit"`（默认 `text_to_image`）
- `prompt: string`（必填）
- `negativePrompt?: string`（可选；不同厂商可能忽略）
- `seed?: number`（可选；不同厂商可能忽略）
- `width?: number` / `height?: number`（可选；像素。`qwen` 会严格使用（默认 `1328×1328`）；其他厂商可能忽略或仅用于推断横竖构图）
- `steps?: number` / `cfgScale?: number`（可选；不同厂商可能忽略）
- `extras?: object`（可选；透传给模型/网关，常用字段：`modelKey` / `aspectRatio` / `referenceImages` / `resolution` / `imageResolution`）
  - `extras.modelAlias?: string`（模型别名选择；推荐）
  - `extras.modelKey?: string`（模型 Key（厂商内）；兼容）

尺寸/分辨率示例：

- 严格像素宽高（推荐：显式指定 `vendor=qwen`）：

```json
{
  "vendor": "qwen",
  "kind": "text_to_image",
  "prompt": "一张电影感海报，中文“lehuoAI”，高细节，干净背景",
  "width": 1328,
  "height": 1328,
  "extras": { "modelAlias": "qwen-image-plus" }
}
```

- 仅控制构图比例（`vendor=auto` 常用；不同通道支持不一）：

```json
{
  "vendor": "auto",
  "kind": "text_to_image",
  "prompt": "一张电影感海报，中文“lehuoAI”，高细节，干净背景",
  "extras": { "modelAlias": "nano-banana-pro", "aspectRatio": "16:9" }
}
```

### 4) 图像理解

- `POST /public/vision`

请求体：

```json
{
  "vendor": "auto",
  "imageUrl": "https://github.com/dianping/cat/raw/master/cat-home/src/main/webapp/images/logo/cat_logo03.png",
  "prompt": "请详细分析我提供的图片，推测可用于复现它的英文提示词，包含主体、环境、镜头、光线和风格。输出必须是纯英文提示词，不要添加中文备注或翻译。",
  "modelAlias": "gemini-3.1-flash-image-preview",
  "temperature": 0.2
}
```

说明：
- 图片输入二选一：`imageUrl`（http(s)）或 `imageData`（`data:image/*;base64,...`）。
- `vendor=auto` 会在系统级已启用且已配置的厂商列表中依次尝试，直到成功或候选耗尽（顺序可能基于近期成功率动态排序）。
- `modelAlias`（推荐）/ `modelKey`（兼容）用于选模；只有两者都未传时，才会默认使用 `gemini-3.1-flash-image-preview`。
- 若 `modelAlias` 本身就是一个真实 `model_key`，服务端会在 alias 查找失败后自动切到精确 `model_key` 匹配。

参考响应（200）：

```json
{
  "id": "task_01HXYZ...",
  "vendor": "yunwu",
  "text": "A clean minimal logo of a cat..."
}
```

### 5) 生成视频

- `POST /public/video`

请求体（简化版）：

```json
{
  "vendor": "auto",
  "prompt": "雨夜霓虹街头，一只白猫缓慢走过…",
  "durationSeconds": 10,
  "extras": { "modelAlias": "<YOUR_VIDEO_MODEL_ALIAS>" }
}
```

说明：
- `vendor=auto` 会在系统级已启用且已配置的厂商列表中依次尝试，直到成功或候选耗尽（顺序可能基于近期成功率动态排序）。
- 视频任务如果需要“参考图/首帧图”，推荐传 `extras.firstFrameUrl`；也兼容简写 `extras.url`（单图）或 `extras.urls` / `extras.referenceImages`（多图）。

请求体（完整字段，按需填写）：
- `vendor?: string`（默认 `auto`）
- `vendorCandidates?: string[]`（可选；仅当 `vendor=auto` 时生效：限制候选厂商范围，例如 `["apimart"]`）
- `prompt: string`（必填）
- `durationSeconds?: number`（可选；会写入 `extras.durationSeconds`；不同厂商会做归一化/截断）
- `extras?: object`（可选；透传给模型/网关，常用字段：`modelKey` / `durationSeconds` / `firstFrameUrl` / `firstFrameImage` / `first_frame_image` / `url` / `lastFrameUrl` / `urls` / `referenceImages` / `orientation` / `size` / `resolution` / `promptOptimizer`）
  - `extras.modelAlias?: string`（模型别名选择；推荐）
  - `extras.modelKey?: string`（模型 Key（厂商内）；兼容）

参考响应（200）：

```json
{
  "vendor": "veo",
  "result": {
    "id": "task_01HXYZ...",
    "kind": "text_to_video",
    "status": "queued",
    "assets": [],
    "raw": {}
  }
}
```
