AI_Painting_V2.0/CLAUDE.md
WangLeo f0008aedde 修复响应解析:outputs 为扁平数组而非嵌套 images 对象,补充 CLAUDE.md 文档
轮询成功返回时 outputs 是 [{url, type}] 数组,代码误解析为 outputs.images 导致提取 URL 为空,触发"生成失败"通知且列表项无法更新为成功状态。同时在 CLAUDE.md 中补充了 API 响应格式说明、eslint 命令及 config/plugins.js 引用。
2026-06-04 18:48:54 +08:00

163 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 常用命令
```bash
pnpm dev # 启动 Vite 开发服务器
pnpm build # 生产构建
pnpm preview # 预览生产构建
npx eslint . # 代码检查(@antfu/eslint-configVue 支持,无 TypeScript
```
## 技术栈
Vue 3 (Composition API) + Vite 7 + Pinia + Vue Router + Element Plus + `vue-element-plus-x`(多媒体编辑) + Less + pnpm
## 架构概览
AI 绘画/视频生成前端操作平台,通过 HTTP 接口对接算力调度后端suanli和第三方 AI 平台RunningHub提交生成任务并轮询结果。
**Painting 和 Video 走两套不同的任务构造路径:**
- **Painting新架构**:本地模型参数 schema → 专用控件 + 动态表单 → `X-Session-Id` header + 扁平 API body
- **Video旧架构**:远程 workflow JSON → RunningHub Playload 适配器 → `{ workflowId, nodeInfoList }` body
### 关键目录
```
src/
├── main.js # 入口:创建 Vue 应用,安装 Pinia/Router/VueVirtualScroller
├── router/index.js # 路由定义 + token 验证守卫
├── stores/ # Pinia 状态管理
│ ├── user.js # 用户认证、信息(含 sessionId
│ ├── display.js # 生成历史列表、UI 状态(滚动、画布等)
│ └── param.js # 参数 store当前为空
├── apis/ # HTTP API 层:纯请求封装,不含业务逻辑
│ ├── auth/ # 认证相关登录、token 校验、用户信息)
│ └── display/ # 任务创建/轮询/历史、平台模型、收藏/删除
├── components/
│ ├── dialogBox/ # 生成参数输入面板(核心交互入口)
│ │ ├── model/ # 模型选择器(按 API 返回的 tags 分组value 编码为 tag::display_name
│ │ ├── proportion/ # 比例/分辨率选择器painting.vue 用于 Paintingvideo.vue 用于 Video
│ │ ├── imageUploader/ # 图片上传Painting
│ │ ├── videoImageUploader/ # 视频图片上传Video
│ │ ├── quantity/ # 生成数量选择器(支持 1-6
│ │ ├── Time/ # 视频时长选择器
│ │ └── pattern/ # 视频模式选择器
│ ├── virtual-scroller/# 虚拟滚动列表组件自定义实现reverse 模式)
│ └── canvas/ # 图片画布编辑(圆/矩形选区,局部重绘)
├── views/ # 页面home、login
├── utils/
│ ├── request.js # Axios 实例 + 拦截器:统一 Auth不带 Bearer+ 按前缀路由 baseURL
│ ├── websocket.js # 任务生成入口:组装参数 → POST 创建任务 → 20s 轮询直至完成/失败
│ ├── modelApi.js # 模型业务层localStorage 30s 缓存 + pendingRequests 并发去重 + 平台编码映射
│ ├── createTask.js # 任务 body 构造Painting 返回 modelParamsVideo 走 Playload 适配器
│ ├── modelConfig.js # Video 旧架构:从远程 JSON 加载 workflow 配置
│ └── auth.ts # token 存取工具localStorage
├── config/
│ ├── plugins.js # Vite 插件配置unplugin-auto-import + unplugin-vue-components
│ ├── index.js # 平台配置入口(目前仅导出 runninghub 供 Video 使用)
│ ├── runninghub/ # RunningHub 平台适配器Playload() 构造和 result() 解析Video 专用)
│ └── models/ # Painting 模型参数配置:每模型一个 JS 文件,定义 params schema
```
### 模型参数配置Painting 新架构)
`src/config/models/` 下每个模型一个 JS 文件,参数通过不同 UI 组件承载:
- **`ui: 'textarea'`** → Sender 组件主输入框prompt
- **`ui: 'proportion'`** + **`ui: 'resolution'`** → `paintingProportion` 组件(共用 Popover含自定义 W/H
- **`ui: 'quantity'`** → `Quantity` 组件(动态 1-6 张)
- **`ui: 'imageUpload'`** → `ImageUploader` 组件
- **`ui: 'hidden'`** → 无 UI仅写入默认值如 outputFormat: 'png'
模型选择器从 API`fetchPlatformModels`)获取模型列表,按 API 返回的 `tags` 数组字段分组(`text`→生成模型,`edit`→编辑模型,`vision`→视觉理解模型)。
### `displayNameMap` 机制
`src/config/models/index.js``displayNameMap` 负责将 API 返回的 `display_name` 映射到 config key。因为同一模型在不同 tag 下可能共用一个 `display_name`(如 `GPT-Image-2``GPT-image-2` 分别对应编辑/生成config key 采用内部中文名区分。
### API 层设计原则
- `src/apis/` 只做纯 HTTP 调用(`service.get/post/delete`不含缓存、localStorage、业务逻辑
- 缓存、数据转换等业务逻辑放在 `src/utils/`
### 核心数据流
**Painting新架构**
1. 用户设置参数 → 模型选择器按 `tags` 分组,控件根据 model config 的 `ui` 字段渲染
2. `handleStart()` 收集 `paramValues`UI refs 通过 watcher 双向同步)→ 组装 `{ modelParams, request }`
3. `websocket.js:generate()``createTask(data)` → Painting 直接返回 `data.modelParams`
4. `getModelId(type, modelName)` 查找 UUID内部调用 `fetchPlatformModels` 走缓存)
5. `requestCreateTask(body, sessionId)` → POST `/suanli/v1/tasks`,携带 `X-Session-Id` header
6. 返回 task_id → 20s 间隔轮询直至完成/失败
**Video旧架构保留**
1. 用户设置 Pattern、videoModel、比例、时长
2. `createTask(data)``runninghub.Playload(data)``fetchModelConfig()` 获取 workflow JSON → 返回 `{ workflowId, nodeInfoList }`
3. 后续同 Painting 步骤 4-6
### 关键注意事项
- **`sessionId`** 来自登录接口返回的 `userInfo.sessionId`,存储在 `useUserStore().userInfo` 中。`websocket.js` 必须使用该值,禁止随机生成。
- **`X-Session-Id`** 自定义 header 需要 nginx 在 `/suanli/` location 的 `Access-Control-Allow-Headers` 中加入,否则 POST 请求会触发 CORS 预检失败。
- **模型列表缓存**`modelApi.js` 中 `fetchPlatformModels` 使用 localStorage 30 秒 TTL + `pendingRequests` Map 并发去重,避免重复请求。
### 接口速查
| 函数 | 端点 | 用途 |
|------|------|------|
| `requestCreateTask` | POST `/suanli/v1/tasks` | 创建任务(带 `X-Session-Id` header |
| `requestTaskStatus` | GET `/suanli/v1/tasks/:id` | 查询单个任务状态 |
| `requestTaskHistory` | GET `/suanli/v1/tasks/history` | 历史任务列表(支持 `user_id`/`platform_code`/`page`/`pageSize` |
| `fetchPlatformModels` | GET `/suanli/v1/platforms/:code/models` | 获取平台模型列表(返回 `{id, display_name, tags, disabled?}` |
| `cancelOrCollect` | POST `/collect/toggle` | 收藏/取消收藏 |
| `deleteGenerateHistory` | DELETE `/taskRecordHistory/delete` | 删除历史记录 |
### 任务响应格式
```json
// GET /suanli/v1/tasks/:id 返回结构
{
"code": 0,
"data": {
"task_id": "uuid",
"status": "completed", // queued | processing | completed | failed
"outputs": [ // ⚠️ 扁平数组,不是 { images: [...] }
{ "url": "https://...", "type": "png" }
],
"vendor_error": "..." // 仅 failed 时有值
}
}
```
### 请求拦截器路由
拦截器统一设置 `Authorization: <token>`(不带 Bearer 前缀),根据 URL 前缀切换后端:
| URL 前缀 | 环境变量 |
|----------|----------|
| `/suanli` | `VITE_API_TASK_TARGET` |
| `/pay` | `VITE_API_PAY_TARGET` |
| `/aigc` | `VITE_API_AIGC_TARGET` |
| 其他 | `VITE_API_BASE_URL`(默认) |
### 平台编码映射
| 类型 | 平台编码 |
|------|----------|
| Painting | `ai_painting_talk` |
| Video | `ai_video_talk` |
映射函数 `getPlatformCode()` 位于 `utils/modelApi.js`
### 自动导入
- `unplugin-auto-import`:自动导入 Vue/Router/Pinia API
- `unplugin-vue-components`:自动注册 `src/components/` 下的组件和 Element Plus 组件
- Element Plus 图标通过 `unplugin-icons` 按需加载