# 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-config,Vue 支持,无 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 用于 Painting,video.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 返回 modelParams,Video 走 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: `(不带 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` 按需加载