# JuYouAI HTTP 接口文档

## 1. 基础约定

### 1.1 服务地址

| 环境 | 地址 |
| --- | --- |
| 本地联调 | `http://127.0.0.1:8123`，以实际 `SERVER_ADDRESS` 为准 |
| 生产环境 | `https://部署域名`，由 Nginx 转发到 `127.0.0.1:8123` |

接口分为两个前缀：

```text
/api/web      用户端接口
/api/admin    管理端接口
```

### 1.2 请求格式

- 普通写接口使用 `Content-Type: application/json`。
- 文件上传使用 `Content-Type: multipart/form-data`。
- GET 查询参数放在 URL Query 中。
- 所有 `project_id`、`episode_id`、`storyboard_id`、`asset_id`、`task_id`、`output_id`、`media_id`、`script_id` 和资源 `id` 均为 UUID。
- 时间使用 ISO 8601/RFC 3339 字符串，例如 `2026-07-31T10:00:00+08:00`。
- 金额和积分字段通常使用十进制字符串返回，调用方不要用二进制浮点数直接结算。

### 1.3 用户端认证

除登录、刷新、退出和健康检查外，WEB 接口需要：

```http
Authorization: Bearer <web_access_token>
```

### 1.4 管理端认证

除登录、刷新、退出和健康检查外，ADMIN 接口需要：

```http
Authorization: Bearer <admin_access_token>
```

创建兑换码批次还需要管理员二次认证：

```http
X-Admin-Reauth-Token: <reauth_token>
```

### 1.5 成功响应

大多数查询、创建和更新接口使用统一数据包：

```json
{
  "data": {}
}
```

分页响应：

```json
{
  "data": {
    "items": [],
    "total": 0,
    "page": 1,
    "page_size": 20
  }
}
```

删除、退出和部分更新成功时返回 `204 No Content`，没有响应体。

### 1.6 错误响应

```json
{
  "code": "invalid_request",
  "message": "参数说明",
  "trace_id": "请求追踪 UUID"
}
```

常见状态码：

| 状态码 | 说明 |
| --- | --- |
| `200` | 查询或更新成功 |
| `201` | 资源创建成功 |
| `202` | 异步任务已接受 |
| `204` | 操作成功，无响应体 |
| `400` | 参数或业务状态不符合要求 |
| `401` | 未登录、Access Token 失效或登录凭证错误 |
| `403` | 缺少或失效的管理员二次认证 |
| `404` | 资源不存在、无权访问或路由不允许 |
| `413` | 请求体超过限制，通常由上传限制触发 |
| `500` | 服务端执行失败 |
| `503` | PostgreSQL、Redis、COS 等依赖不可用 |

服务会在响应头返回：

```http
X-Request-ID: <请求追踪 UUID>
```

调用方也可以主动发送 `X-Request-ID`，后端将复用该值。

## 2. 公共数据结构

### 2.1 TokenPair

用户登录和管理员登录返回相同的 Token 基础字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `access_token` | string | 用于 Authorization 的短期访问令牌 |
| `refresh_token` | string | 用于刷新登录态；每次刷新后应替换旧值 |
| `expires_at` | datetime | Access Token 到期时间 |
| `user` | object | 仅 WEB 返回的用户摘要 |
| `admin` | object | 仅 ADMIN 返回的管理员摘要 |

### 2.2 分页参数

| 参数 | 位置 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `page` | Query | integer | `1` | 小于 1 时按 1 处理 |
| `page_size` | Query | integer | `20` | 最大 100 |
| `keyword` | Query | string | 空 | 模糊搜索关键词，是否生效取决于接口 |

### 2.3 GenerationTask 摘要

任务列表和创建任务通常返回以下主要字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | UUID | 任务 ID |
| `request_id` | string | 幂等和上游请求追踪标识 |
| `task_type` | string | 任务类型 |
| `status` | string | 当前状态 |
| `project_id` | UUID/null | 所属项目 |
| `episode_id` | UUID/null | 所属剧集 |
| `storyboard_id` | UUID/null | 所属分镜 |
| `estimated_points` | string | 预估积分 |
| `prepaid_points` | string | 已预扣积分 |
| `actual_points` | string/null | 最终积分 |
| `error_code` | string | 失败错误码 |
| `error_message` | string | 失败原因 |
| `created_at` | datetime | 创建时间 |
| `submitted_at` | datetime/null | 提交上游时间 |
| `finished_at` | datetime/null | 终态时间 |

常见任务状态：`pending_submission`、`submitting`、`submit_unknown`、`submitted`、`processing`、`result_ready`、`downloading`、`succeeded`、`failed`、`cancel_requested`、`cancelled`。

### 2.4 MediaAsset

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | UUID | 媒体 ID |
| `public_url` | string | COS 公开访问地址 |
| `original_name` | string | 原始文件名 |
| `display_name` | string | 业务显示名称 |
| `mime_type` | string | MIME 类型 |
| `size_bytes` | integer | 文件字节数 |
| `sha256` | string | 文件摘要 |
| `width`、`height` | integer/null | 图片或视频尺寸 |
| `duration_ms` | integer/null | 音视频时长 |
| `created_at` | datetime | 创建时间 |

## 3. 健康检查

### 3.1 接口列表

| 方法 | 路径 | 认证 | 作用 |
| --- | --- | --- | --- |
| GET | `/health` | 否 | 系统级健康检查 |
| GET | `/api/web/health` | 否 | WEB 前缀健康检查 |
| GET | `/api/admin/health` | 否 | ADMIN 前缀健康检查 |

三个接口参数和响应一致，同时 Ping PostgreSQL 和 Redis。

成功示例：

```json
{
  "status": "ok",
  "services": {
    "postgresql": "ok",
    "redis": "ok"
  }
}
```

任一依赖失败时返回 `503`，`status` 为 `degraded`，对应服务值为 `unavailable`。

## 4. WEB 用户认证与账户

### 4.1 接口总表

| 方法 | 路径 | 认证 | 作用 | 参数 |
| --- | --- | --- | --- | --- |
| POST | `/api/web/auth/login` | 否 | 用户登录 | JSON：`account`、`password`，均必填 |
| POST | `/api/web/auth/refresh` | 否 | 刷新并轮换 Token | JSON：`refresh_token`，必填 |
| POST | `/api/web/auth/logout` | 否 | 注销 Refresh Token | JSON：`refresh_token`，可空 |
| GET | `/api/web/account` | 是 | 获取账户、积分等完整信息 | 无 |
| PATCH | `/api/web/account/profile` | 是 | 修改用户显示名 | JSON：`username`，必填 |
| POST | `/api/web/account/avatar` | 是 | 上传或替换头像 | multipart：`file` |
| PUT | `/api/web/account/password` | 是 | 修改用户密码 | JSON：`original_password`、`new_password` |
| GET | `/api/web/usage/30-days` | 是 | 获取最近 30 天消耗数据 | 无 |
| GET | `/api/web/usage/consumption-records` | 是 | 获取最近消费记录 | 无；后端固定最多 100 条 |
| POST | `/api/web/redemption/redeem` | 是 | 使用兑换码增加积分 | JSON：`code`，必填 |

### 4.2 登录

请求：

```json
{
  "account": "user001",
  "password": "用户密码"
}
```

成功返回 `TokenPair`，其中 `user` 包含 `id`、`uid`、`account`、`username`、`avatar_url` 等用户摘要。账号不存在、密码错误或账号被禁用返回 `401`。

### 4.3 更新资料

```json
{
  "username": "新的显示名称"
}
```

用户名格式不正确或已存在时返回 `400 profile_invalid`。

### 4.4 修改密码

```json
{
  "original_password": "原密码",
  "new_password": "新密码"
}
```

原密码错误或新密码不满足 8 至 128 字符限制时返回 `400 password_invalid`。成功返回 `204`。

### 4.5 上传头像

| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `file` | file | 是 | JPEG、PNG 或 WebP；大小不超过 `COS_MAX_IMAGE_SIZE_MB` |

后端同时校验声明的 MIME 和文件头。成功返回：

```json
{
  "data": {
    "avatar_url": "https://媒体域名/users/..."
  }
}
```

替换成功后会删除旧头像对象。

## 5. WEB 提示词与模型配置

### 5.1 接口列表

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/web/user/prompts/{prompt_type}` | 列出指定类型的系统提示词、用户自定义提示词和当前选择 | Path：`prompt_type` |
| PUT | `/api/web/user/prompts/{prompt_type}/select` | 选择或关闭某类提示词 | JSON：`prompt_id`，UUID 或 `null` |
| POST | `/api/web/user/custom-prompts` | 新建用户提示词 | JSON：`name`、`type`、`content` |
| PUT | `/api/web/user/custom-prompts/{id}` | 修改本人提示词 | Path：`id`；JSON：`name`、`content`，`type` 可选 |
| DELETE | `/api/web/user/custom-prompts/{id}` | 删除本人提示词并清理选择关系 | Path：`id` |
| GET | `/api/web/creative/options` | 获取可用风格、模型、价格和能力信息 | 无 |
| GET | `/api/web/user/model-configs/{scope}` | 获取用户级模型偏好 | 当前 `scope` 只接受 `script_analysis` |
| PUT | `/api/web/user/model-configs/{scope}/{model_type}` | 保存用户级模型偏好 | 当前只接受 `scope=script_analysis`、`model_type=text`；JSON：`model_id` |

### 5.2 提示词列表响应

```json
{
  "data": {
    "prompts": [
      {
        "id": "UUID",
        "name": "提示词名称",
        "type": "提示词类型",
        "content": "内容",
        "scope": "system",
        "editable": false
      }
    ],
    "selected_id": "UUID"
  }
}
```

`scope` 为 `system` 或 `user`。平台代码内固定的短剧解析提示词不允许用户选择或自定义。

### 5.3 保存模型偏好

```json
{
  "model_id": "模型 UUID"
}
```

模型及所属渠道必须启用。成功返回 `204`。

## 6. WEB 剧本分析

所有接口均需要用户认证。

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/web/script-analyses` | 获取本人剧本分析列表 | 无 |
| POST | `/api/web/script-analyses` | 创建空白剧本分析 | 无；默认名称为“未命名剧本” |
| GET | `/api/web/script-analyses/import-projects` | 获取可导入的剧本反推项目 | 无 |
| GET | `/api/web/script-analyses/{script_id}` | 获取剧本、分析结果和角色列表 | Path：`script_id` |
| PUT | `/api/web/script-analyses/{script_id}` | 保存剧本名称、源内容或人工结果内容 | JSON：`name`、`source_content`、`result_content` |
| POST | `/api/web/script-analyses/{script_id}/import-project` | 从本人反推项目导入已完成剧本 | JSON：`project_id` |
| POST | `/api/web/script-analyses/{script_id}/import-file` | 从文件替换剧本源内容 | multipart：`file` |
| POST | `/api/web/script-analyses/{script_id}/analyze` | 创建异步剧本分析任务 | 无；返回 `202` |
| POST | `/api/web/script-analyses/{script_id}/cancel` | 请求取消当前分析任务 | 无 |
| DELETE | `/api/web/script-analyses/{script_id}` | 软删除剧本分析 | 无 |

### 6.1 更新剧本

```json
{
  "name": "剧本名称",
  "source_content": "待分析的完整剧本",
  "result_content": "可选的人工整理结果"
}
```

剧本正在分析时，部分源内容替换操作会被拒绝。

### 6.2 文件导入

| 参数 | 类型 | 限制 |
| --- | --- | --- |
| `file` | file | 仅 `.txt`、`.doc`，有效内容不超过 3 MiB；整个 multipart 请求不超过 4 MiB |

导入会使用文件名作为剧本名称，并清空旧分析结果和角色数据。

### 6.3 剧本分析主要返回字段

| 字段 | 说明 |
| --- | --- |
| `id`、`name` | 剧本标识和名称 |
| `source_content` | 原始剧本内容 |
| `result_content` | 人工或展示用结果文本 |
| `analysis_result` | JSON 结构化分析结果 |
| `analysis_status` | `idle`、`queued`、`running`、`succeeded`、`failed` |
| `analysis_message` | 进度或错误说明 |
| `characters` | 角色名称、阵营、传记、动机和关系 |

## 7. WEB 项目管理

### 7.1 接口列表

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/web/projects` | 获取本人项目列表 | Query：`project_type`、`keyword` |
| POST | `/api/web/projects` | 创建项目 | JSON：ProjectInput |
| GET | `/api/web/projects/{project_id}` | 获取项目、模型配置和剧集详情 | Path：`project_id` |
| PUT | `/api/web/projects/{project_id}` | 修改项目基础信息 | JSON：ProjectInput |
| DELETE | `/api/web/projects/{project_id}` | 删除项目及关联数据库和 COS 对象 | Path：`project_id` |
| PUT | `/api/web/projects/{project_id}/model-configs` | 保存项目的文本、图片和视频配置 | JSON：模型配置映射 |

### 7.2 项目列表查询参数

| 参数 | 类型 | 说明 |
| --- | --- | --- |
| `project_type` | string | 可空；常用值 `video_redraw`、`premium_drama` |
| `keyword` | string | 按项目名称模糊搜索 |

### 7.3 ProjectInput

```json
{
  "project_type": "premium_drama",
  "name": "项目名称",
  "style_id": "风格 UUID",
  "era_type": "modern_city",
  "custom_era": null,
  "aspect_ratio": "16:9",
  "localization": "china",
  "short_drama_type": "短剧类型",
  "play_count": "播放量文本",
  "audience_profile": "受众说明",
  "producer": "制作方",
  "cast_members": [
    {
      "name": "演员名",
      "follower_count": "粉丝数文本"
    }
  ]
}
```

| 字段 | 必填 | 说明 |
| --- | --- | --- |
| `project_type` | 创建时建议填写 | `video_redraw` 或 `premium_drama` |
| `name` | 是 | 项目名称 |
| `style_id` | 短剧项目是 | 风格 UUID；反推项目未填时后端尝试使用第一个可用风格 |
| `era_type` | 是 | 时代类型；自定义时代使用 `other` 配合 `custom_era` |
| `custom_era` | 否 | 自定义时代文本 |
| `aspect_ratio` | 是 | 项目画面比例 |
| `localization` | 视项目而定 | 本地化区域代码 |
| 其他字段 | 否 | 短剧运营和演员信息 |

### 7.4 项目模型配置

接口接受按模型类型或用途命名的对象。推荐格式：

```json
{
  "text": {
    "model_id": "文本模型 UUID",
    "settings": {
      "prompt": "可选附加提示"
    }
  },
  "image": {
    "model_id": "图片模型 UUID",
    "settings": {}
  },
  "video": {
    "model_id": "视频模型 UUID",
    "settings": {
      "resolution": "720p"
    }
  }
}
```

也兼容 `prompt_reverse`、`image_generation`、`video_generation` 作为键。`resolution` 允许 `480p`、`720p`、`1080p`，无效值回退为 `480p`。图片比例由业务固定，不从模型配置保存。

## 8. WEB 剧集、导入与短剧解析

### 8.1 剧集接口

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| POST | `/api/web/projects/{project_id}/episodes` | 新建剧集 | JSON：`episode_no`、`name`，均可省略使用默认值 |
| PUT | `/api/web/projects/{project_id}/episodes/{episode_id}` | 修改剧集名称 | JSON：`name` |
| DELETE | `/api/web/projects/{project_id}/episodes/{episode_id}` | 删除剧集及其媒体、分镜和任务数据 | 无请求体 |
| GET | `/api/web/projects/{project_id}/episodes/{episode_id}/workbench` | 获取项目、剧集、资产和分镜工作台数据 | 无 |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/storyboards` | 手动新增 5 秒空白分镜 | 无 |

### 8.2 小说或剧本导入

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| POST | `/api/web/projects/{project_id}/episodes/import/preview` | 解析上传文件或粘贴文本，生成章节预览和一次性导入令牌 | multipart：`file` 或 `raw_text` 至少一个 |
| POST | `/api/web/projects/{project_id}/episodes/import/confirm` | 使用预览令牌正式创建剧集 | JSON：见下方 |

预览限制：整个请求不超过 4 MiB，文件读取上限为 3 MiB。文件和 `raw_text` 同时提供时优先使用文件。

确认请求：

```json
{
  "import_token": "预览返回的 UUID",
  "chapters_per_episode": 2,
  "start_episode_no": 1,
  "treat_as_single_episode": false
}
```

| 字段 | 说明 |
| --- | --- |
| `import_token` | 必填，一次性导入会话 UUID |
| `chapters_per_episode` | 每集包含的章节数 |
| `start_episode_no` | 起始集号 |
| `treat_as_single_episode` | 无章节或希望合并时按单集导入 |

### 8.3 剧集文本源和解析

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/web/projects/{project_id}/episodes/{episode_id}/text-source` | 获取待解析的剧集文本 | 无 |
| PUT | `/api/web/projects/{project_id}/episodes/{episode_id}/text-source` | 保存剧集标题和原始正文 | JSON：`title`、`raw_content` |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/parse` | 创建短剧文本解析任务 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/reparse` | 清理旧分析产物并重新解析 | 无；需要 COS 可用 |
| GET | `/api/web/projects/{project_id}/parse-tasks` | 获取项目解析任务 | Query：可选 `episode_id` |
| POST | `/api/web/projects/{project_id}/parse-tasks/{task_id}/cancel` | 取消解析任务 | 无 |

保存文本请求：

```json
{
  "title": "本集标题",
  "raw_content": "本集完整正文"
}
```

## 9. WEB 反推与媒体分析

### 9.1 工作台和任务接口

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/web/projects/{project_id}/redraw-workbench` | 获取项目级反推工作台 | 无 |
| GET | `/api/web/projects/{project_id}/episodes/{episode_id}/redraw-workbench` | 获取剧集级反推工作台 | 无 |
| POST | `/api/web/projects/{project_id}/redraw-analyze` | 分析项目级原视频 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/redraw-reanalyze` | 删除旧分析并重新分析项目原视频 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/redraw-script` | 生成项目级完整反推剧本 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/redraw-script` | 生成剧集级反推剧本 | 无；返回 `202` |
| PUT | `/api/web/projects/{project_id}/episodes/{episode_id}/redraw-script` | 保存修改后的剧集级反推剧本 | JSON：`content` |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/analyze` | 分析剧集原视频 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/reanalyze` | 清理并重新分析剧集原视频 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/storyboards/{storyboard_id}/reinfer` | 重新反推指定短剧分镜 | 无；返回 `202` |
| POST | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/redraw-reinfer` | 重新反推指定项目级分镜 | 无；返回 `202` |

### 9.2 分析设置

| 方法 | 路径 | 作用 |
| --- | --- | --- |
| PUT | `/api/web/projects/{project_id}/redraw-analysis-settings` | 保存项目级台词来源和源语言 |
| PUT | `/api/web/projects/{project_id}/episodes/{episode_id}/analysis-settings` | 保存剧集级台词来源和源语言 |

请求：

```json
{
  "audio_source": "video_audio",
  "source_language": "zh"
}
```

`audio_source` 只允许：

- `video_audio`：从视频中提取音频识别；
- `subtitle_file`：使用上传字幕。

剧集级 `source_language` 最长 16 字符；项目级允许空值表示自动或未指定。

### 9.3 原视频和字幕上传

| 方法 | 路径 | multipart 参数 |
| --- | --- | --- |
| POST | `/api/web/projects/{project_id}/redraw-source` | `file`：项目原视频 |
| POST | `/api/web/projects/{project_id}/redraw-subtitle` | `file`：项目字幕 |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/source` | `file`：剧集原视频 |
| POST | `/api/web/projects/{project_id}/episodes/{episode_id}/subtitle` | `file`：剧集字幕 |

视频限制：

- MIME：`video/mp4`、`video/webm`、`video/quicktime`、`video/x-m4v`；
- 扩展名：`.mp4`、`.webm`、`.mov`、`.m4v`；
- 最大 50 MiB。

字幕限制：扩展名 `.srt`、`.vtt`、`.txt`，最大 2 MiB。

替换原视频会重置已有分析产物；替换字幕不会自动触发分析。

## 10. WEB 资产管理

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| POST | `/api/web/projects/{project_id}/assets` | 创建资产 | JSON：`asset_type`、`name` |
| PUT | `/api/web/projects/{project_id}/assets/{asset_id}` | 修改资产 | JSON：允许字段见下方 |
| DELETE | `/api/web/projects/{project_id}/assets/{asset_id}` | 删除资产及媒体和输出 | 无 |
| POST | `/api/web/projects/{project_id}/assets/{asset_id}/image` | 上传资产参考图 | multipart：`file` |
| DELETE | `/api/web/projects/{project_id}/assets/{asset_id}/image` | 移除当前参考图 | 无 |
| POST | `/api/web/projects/{project_id}/assets/{asset_id}/audio` | 上传参考音频 | multipart：`file`、可选 `duration_ms` |
| GET | `/api/web/projects/{project_id}/assets/{asset_id}/outputs` | 获取资产图片生成历史 | 无 |
| PUT | `/api/web/projects/{project_id}/assets/{asset_id}/outputs/{output_id}/activate` | 将历史输出设为当前资产图 | 无 |
| DELETE | `/api/web/projects/{project_id}/assets/{asset_id}/outputs/{output_id}` | 删除资产历史输出和 COS 对象 | 无 |

创建资产：

```json
{
  "asset_type": "character",
  "name": "资产名称"
}
```

`asset_type` 允许 `character`、`scene`、`prop`、`custom`。同一项目内同类型资产名称不能重复。

修改资产允许字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `name` | string | 非空；重命名时同步更新分镜引用 |
| `description` | string | 资产描述 |
| `image_prompt` | string | 资产图片提示词 |
| `appearances` | array | 出场信息 |

剧本反推项目中的解析资产只允许修改 `name`。

参考图只接受 JPEG、PNG、WebP，大小不超过 `COS_MAX_IMAGE_SIZE_MB`，后端会解析真实图片尺寸。音频必须为 `audio/*`，大小不超过 `COS_MAX_AUDIO_SIZE_MB`；`duration_ms` 必须是大于等于 0 的整数。

## 11. WEB 分镜、生成与输出

### 11.1 分镜编辑

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| PUT | `/api/web/projects/{project_id}/storyboards/{storyboard_id}` | 修改分镜 | JSON：允许字段见下方 |
| POST | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/insert` | 在目标分镜前或后插入空白分镜 | JSON：`position` |
| DELETE | `/api/web/projects/{project_id}/storyboards/{storyboard_id}` | 删除分镜及关联输出 | 无 |

允许修改的分镜字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `title` | string | 分镜标题 |
| `script_content` | string | 分镜剧本内容 |
| `source_excerpt` | string | 来源文本片段 |
| `prompt_content` | string | 生成提示词 |
| `dialogue` | array/object | 以 JSONB 保存的台词结构 |
| `asset_refs` | array | 资产引用结构 |
| `locked` | boolean | 是否锁定 |
| `active_output_id` | UUID/null | 当前活动输出 |
| `duration_seconds` | integer | 5 至 15 秒 |

插入请求：

```json
{
  "position": "after"
}
```

`position` 只允许 `before` 或 `after`。

### 11.2 创建生成任务

```text
POST /api/web/projects/{project_id}/episodes/{episode_id}/storyboards/{storyboard_id}/generate
```

请求：

```json
{
  "task_type": "image_generation",
  "input": {
    "prompt": "生成提示词",
    "size": "16:9",
    "duration": 5,
    "resolution": "720p",
    "image_urls": [],
    "audio_urls": [],
    "video_urls": []
  }
}
```

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `task_type` | string | 只允许 `image_generation`、`video_generation` |
| `input` | object | 任务输入；根据任务类型使用提示词、画面比例、时长、清晰度和参考媒体 URL 等字段 |

后端会结合项目模型配置、分镜、资产引用和渠道能力重新校验，不应把 `input` 当作可任意透传第三方的对象。成功返回 `202` 和 GenerationTask。

### 11.3 任务接口

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/web/projects/{project_id}/tasks` | 获取项目任务列表 | Query：可选 `episode_id` |
| POST | `/api/web/projects/{project_id}/tasks/{task_id}/cancel` | 取消排队任务或请求取消上游任务 | 无 |

取消已进入终态的任务不会重新改变结果。不同项目类型的取消和退款规则不同，以后端返回为准。

### 11.4 分镜输出历史

| 方法 | 路径 | 作用 |
| --- | --- | --- |
| GET | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/outputs` | 获取分镜生成历史 |
| PUT | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/outputs/{output_id}/activate` | 设置当前输出 |
| PUT | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/outputs/{output_id}/candidate` | 标记候选输出 |
| DELETE | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/outputs/{output_id}/candidate` | 取消候选标记 |
| DELETE | `/api/web/projects/{project_id}/storyboards/{storyboard_id}/outputs/{output_id}` | 删除输出和 COS 对象 |

输出列表主要返回 `id`、`task_id`、`media_asset_id`、`output_type`、`public_url`、`mime_type`、`size_bytes`、`active`、`candidate`、积分和创建时间。

### 11.5 受保护媒体读取

```text
GET /api/web/media/{media_id}
```

后端先校验媒体属于当前用户，再从 COS 流式返回原始二进制。响应 `Content-Type` 使用媒体 MIME，`Cache-Control` 为 `private, max-age=3600`。该接口不是 JSON 响应。

## 12. ADMIN 管理员认证

| 方法 | 路径 | 认证 | 作用 | 参数 |
| --- | --- | --- | --- | --- |
| POST | `/api/admin/auth/login` | 否 | 管理员登录 | JSON：`username`、`password` |
| POST | `/api/admin/auth/refresh` | 否 | 刷新管理员 Token | JSON：`refresh_token` |
| POST | `/api/admin/auth/logout` | 否 | 注销 Refresh Token | JSON：可选 `refresh_token` |
| POST | `/api/admin/auth/reauth` | Admin JWT | 使用当前密码取得 5 分钟二次认证令牌 | JSON：`password` |
| PUT | `/api/admin/auth/password` | Admin JWT | 修改管理员密码 | JSON：`current_password`、`new_password`、`confirm_password` |

二次认证成功：

```json
{
  "data": {
    "reauth_token": "短期 JWT",
    "expires_at": "2026-07-31T10:05:00+08:00"
  }
}
```

新管理员密码必须与确认密码一致，并满足 8 至 128 字符限制。修改成功返回 `204`。

## 13. ADMIN 用户管理

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/admin/users` | 分页查询用户 | Query：`keyword`、`enabled`、`page`、`page_size` |
| POST | `/api/admin/users` | 创建单个用户 | JSON：`account`、`password`、`daily_limit` |
| POST | `/api/admin/users/batch` | 按前缀和序号批量创建用户 | JSON：见下方 |
| PATCH | `/api/admin/users/batch` | 批量调整日限额或启停 | JSON：`ids`、可选 `daily_limit`、可选 `enabled`、`reason` |
| DELETE | `/api/admin/users/batch` | 批量软删除用户 | JSON：`ids`、`reason` |

单用户创建：

```json
{
  "account": "user001",
  "password": "初始密码",
  "daily_limit": "100.00"
}
```

批量创建：

```json
{
  "prefix": "user",
  "start_sequence": "001",
  "end_sequence": "010",
  "password": "统一初始密码",
  "daily_limit": "100.00"
}
```

`start_sequence`、`end_sequence` 使用字符串以保留前导零。批量更新至少应提供 `daily_limit` 或 `enabled` 之一。

## 14. ADMIN 模型管理

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/admin/models` | 分页查询模型和价格 | Query：`keyword`、`model_type`、`channel_id`、`page`、`page_size` |
| POST | `/api/admin/models` | 创建模型配置 | JSON：ModelBody |
| PUT | `/api/admin/models/{id}` | 更新模型并整体替换价格 | JSON：ModelBody |

ModelBody：

```json
{
  "channel_id": "渠道 UUID",
  "name": "渠道侧标识",
  "model_type": "text",
  "multimodal": false,
  "text_billing_mode": "per_request",
  "enabled": true,
  "prices": [
    {
      "price_key": "default",
      "unit": "次",
      "price": "0.50"
    }
  ]
}
```

| 字段 | 说明 |
| --- | --- |
| `channel_id` | 所属渠道 UUID |
| `name` | 渠道请求使用的标识 |
| `model_type` | `text`、`image` 或 `video` |
| `multimodal` | 文本类型是否支持多模态输入 |
| `text_billing_mode` | 文本类型只允许 `per_request` 或 `per_token` |
| `enabled` | 是否允许新任务选择 |
| `prices` | 更新时整体替换原价格列表 |

文本按次计费必须且只能提供 `price_key=default`；按 Token 计费必须同时提供 `input` 和 `output`，单位由后端规范化。

模型启停和删除目前没有独立 `/models/{id}` 路由，前端代码虽然尝试使用通用资源路由，但服务器通用资源白名单当前只允许 `styles`、`channels`。直接调用 `/api/admin/resources/models/...` 会返回 `404 route_not_found`。

## 15. ADMIN 提示词管理

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/admin/prompts` | 查询系统提示词 | Query：`keyword`、`type`、`page`、`page_size` |
| POST | `/api/admin/prompts` | 创建并立即生效系统提示词 | JSON：`name`、`type`、`content` |
| PUT | `/api/admin/prompts/{id}` | 修改并立即生效系统提示词 | JSON：`name`、`type`、`content` |
| DELETE | `/api/admin/prompts/{id}` | 删除系统提示词 | Path：`id` |
| POST | `/api/admin/prompts/{id}/actions` | 兼容占位接口 | 当前忽略请求体并返回 `204` |
| GET | `/api/admin/prompts/{id}/history` | 兼容占位接口 | 当前固定返回空数组 |

请求示例：

```json
{
  "name": "提示词名称",
  "type": "提示词类型",
  "content": "完整提示词内容"
}
```

`name`、`type`、`content` 不能为空。后端代码内固定的短剧解析提示词不会出现在列表，也不允许通过该接口维护。

注意：当前 ADMIN 页面删除操作使用 `/resources/prompts/{id}`，但通用资源白名单不允许 `prompts`；对接方应以本节已注册的 `DELETE /api/admin/prompts/{id}` 为准。

## 16. ADMIN 兑换码

| 方法 | 路径 | 作用 | 参数 |
| --- | --- | --- | --- |
| GET | `/api/admin/redemption-codes` | 分页查询兑换码 | Query：`keyword`、`status`、`page`、`page_size` |
| POST | `/api/admin/redemption-batches` | 创建一批兑换码 | Admin JWT + `X-Admin-Reauth-Token`；JSON：见下方 |

查询：

- `keyword` 匹配兑换码、兑换码掩码、批次名或用户 UID；
- `status` 按 `unused`、`redeemed`、`expired` 等状态过滤；
- 过期但仍标记 unused 的记录会动态显示为 expired。

创建请求：

```json
{
  "name": "活动批次名称",
  "points": "50",
  "quantity": 10,
  "expires_at": "2026-08-07T23:59:59+08:00"
}
```

| 字段 | 限制 |
| --- | --- |
| `name` | 批次名称 |
| `points` | 单码 1 至 100 积分 |
| `quantity` | 1 至 10 个 |
| `expires_at` | 可空；为空时默认当前时间后 7 天 |

成功响应中的 `codes` 是完整明文兑换码；管理后台列表也会返回 `code` 字段供查看和复制。

## 17. ADMIN 风格图片和排序

### 17.1 上传风格图片

```text
POST /api/admin/uploads/style-images
```

multipart 参数：`file`。

限制：

- JPEG、PNG 或 GIF；
- 大小不超过 `COS_MAX_IMAGE_SIZE_MB`；
- 宽高均为 64 至 8192 像素。

返回 `image_key`、`image_url`、`image_mime`、`image_size`、`image_width`、`image_height`，随后把这些字段传给风格创建或更新接口。

### 17.2 风格排序

```text
PUT /api/admin/resources/styles/reorder
```

请求：

```json
{
  "ids": ["风格 UUID 1", "风格 UUID 2"]
}
```

`ids` 必须包含当前所有未删除风格，不能缺少、重复或包含无效 UUID。数组顺序就是最终 `sort_order`。

## 18. ADMIN 通用资源接口

通用资源路由形式如下：

| 方法 | 路径 | 作用 |
| --- | --- | --- |
| GET | `/api/admin/resources/{resource}` | 分页查询资源 |
| POST | `/api/admin/resources/{resource}` | 创建资源 |
| PUT | `/api/admin/resources/{resource}/{id}` | 更新资源 |
| PATCH | `/api/admin/resources/{resource}/{id}/enabled` | 启用或停用资源 |
| DELETE | `/api/admin/resources/{resource}/{id}` | 软删除资源 |

服务器路由白名单当前只允许：

```text
styles
channels
```

其他 `{resource}` 值直接返回 `404 route_not_found`。

### 18.1 通用查询参数

| 参数 | 说明 |
| --- | --- |
| `page`、`page_size` | 分页；默认 1/20，最大 100 |
| `keyword` | 渠道按名称或基础网址搜索；风格当前不使用关键词 |
| `enabled` | 渠道启停过滤 |
| `channel_type` | 渠道类型过滤 |

### 18.2 风格资源

创建或更新 JSON：

```json
{
  "name": "风格名称",
  "image_key": "styles/2026/07/UUID.png",
  "image_url": "https://媒体域名/...",
  "image_mime": "image/png",
  "image_size": 102400,
  "image_width": 1024,
  "image_height": 1024
}
```

允许字段仅为以上字段。创建时后端自动把新风格放在当前排序末尾。

### 18.3 渠道资源

创建或更新 JSON：

```json
{
  "name": "渠道名称",
  "base_url": "https://api.example.com/v1",
  "api_key": "渠道 API Key",
  "warning_threshold": "10.00",
  "channel_points_per_cny": "100.00",
  "max_concurrency": 500,
  "max_user_concurrency": 10,
  "enabled": true
}
```

| 字段 | 创建 | 更新 | 说明 |
| --- | --- | --- | --- |
| `name` | 必填 | 可改 | 渠道显示名 |
| `base_url` | 必填 | 可改 | 请求基础网址，不要带具体任务路径 |
| `api_key` | 必填 | 可省略 | 更新时省略表示保留原密钥；提供时加密替换 |
| `warning_threshold` | 建议填写 | 可改 | 余额预警阈值 |
| `channel_points_per_cny` | 必填 | 必填 | 大于 0，表示每人民币对应渠道积分 |
| `max_concurrency` | 可省略 | 可改 | 默认 500，范围 1 至 5000 |
| `max_user_concurrency` | 可省略 | 可改 | 默认 10，范围 1 至 500，且不超过总并发 |
| `enabled` | 可选 | 可改 | 是否启用 |

创建时后端强制 `channel_type=relay`。列表响应不会返回密文或完整 API Key，而是返回 `api_key_masked`。读取渠道列表会异步触发一次余额同步。

### 18.4 启停和删除

启停请求：

```json
{
  "enabled": false,
  "reason": "调整原因"
}
```

停用渠道时会同步停用该渠道下的模型。删除使用软删除，可选请求体：

```json
{
  "reason": "删除原因"
}
```

## 19. 接口调用示例

### 19.1 用户登录并查询项目

```bash
curl -X POST "https://部署域名/api/web/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"account":"user001","password":"用户密码"}'
```

```bash
curl "https://部署域名/api/web/projects?project_type=video_redraw&page=1" \
  -H "Authorization: Bearer WEB_ACCESS_TOKEN"
```

`/api/web/projects` 当前不使用分页参数，示例中的 `page` 会被忽略；项目列表直接返回数组。

### 19.2 上传原视频

```bash
curl -X POST "https://部署域名/api/web/projects/PROJECT_UUID/redraw-source" \
  -H "Authorization: Bearer WEB_ACCESS_TOKEN" \
  -F "file=@source.mp4"
```

### 19.3 创建生成任务

```bash
curl -X POST "https://部署域名/api/web/projects/PROJECT_UUID/episodes/EPISODE_UUID/storyboards/STORYBOARD_UUID/generate" \
  -H "Authorization: Bearer WEB_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"task_type":"image_generation","input":{"prompt":"画面描述","size":"16:9"}}'
```

### 19.4 管理员创建渠道

```bash
curl -X POST "https://部署域名/api/admin/resources/channels" \
  -H "Authorization: Bearer ADMIN_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name":"渠道名称",
    "base_url":"https://api.example.com/v1",
    "api_key":"渠道密钥",
    "warning_threshold":"10",
    "channel_points_per_cny":"100",
    "max_concurrency":500,
    "max_user_concurrency":10,
    "enabled":true
  }'
```

## 20. 对接注意事项

- 前端和第三方调用方应以 HTTP 状态码和 `code` 判断结果，不要只比较中文 `message`。
- 收到 401 后最多刷新并重放一次原请求，避免 Refresh Token 失效时形成循环。
- 任务创建返回 202 只表示已经进入任务系统，不表示第三方任务完成。
- 任务真实状态以 PostgreSQL 返回的接口数据为准，不以 Redis 队列长度为准。
- 删除项目、剧集、资产、分镜或输出会同时清理 COS；COS 不可用时接口可能返回 503。
- 文件上传不要手工伪造 MIME；后端会同时检查扩展名、MIME、文件头或可解析性。
- 渠道 API Key、COS SecretKey、JWT Secret 和数据库密码不能传给 WEB 或 ADMIN 前端。
- 当前没有 OpenAPI/Swagger 自动生成路由；修改 `server.go`、Handler 参数或返回结构后，应同步更新本文档。
