- websocket.js → taskPolling.js:文件名不再暗示 WebSocket,准确反映 HTTP 轮询机制 - 删除过期备份文件 websocket copy.js - painting.vue 声明 width/height props,拦截 $attrs 穿透,修复 Popover 宽度 = 尺寸值的 bug - Popover contentStyle:width:auto → fit-content + max-width:600px,彻底解决 fixed 定位宽度异常 - 比例子项 flex:1 + gap:5px 替代 space-between,间距恒定不受选项数量影响 - CLAUDE.md 补充 Select/Img 组件、dialogBox 编排中心、$attrs 穿透陷阱等文档
10 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
常用命令
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-Idheader + 扁平 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),使用 pinia persist 持久化 token
│ ├── display.js # 生成历史列表、UI 状态(滚动、画布等)
│ └── param.js # 参数 store(当前为空)
├── apis/ # HTTP API 层:纯请求封装,不含业务逻辑
│ ├── auth/ # 认证相关(登录、token 校验、用户信息、验证码)
│ └── display/ # 任务创建/轮询/历史、平台模型、收藏/删除
├── components/
│ ├── dialogBox/ # 生成参数输入面板(核心交互入口)
│ │ ├── index.vue # 编排中心:组装所有控件,处理 handleStart()、模型配置加载、参数回填
│ │ ├── model/ # 模型选择器(按 API 返回的 tags 分组,value 编码为 tag::display_name)
│ │ ├── proportion/ # 比例/分辨率选择器(painting.vue 用于 Painting,video.vue 用于 Video)
│ │ ├── imageUploader/ # 图片上传(Painting)
│ │ ├── videoImageUploader/ # 视频图片上传(Video)
│ │ ├── quantity/ # 生成数量选择器(支持 1-6,上限由模型配置派生)
│ │ ├── Time/ # 视频时长选择器
│ │ └── pattern/ # 视频模式选择器
│ ├── Popover/ # 自定义弹出层(Teleport to body,position:fixed + fit-content 宽度)
│ ├── Select/ # 自定义下拉选择器(分组选项,与 Popover 协调互斥开关)
│ ├── Img/ # 图片包装组件(点击全屏查看,Teleport 实现)
│ ├── virtual-scroller/# 虚拟滚动列表组件(自定义实现,reverse 模式)
│ └── canvas/ # 图片画布编辑(圆/矩形选区,局部重绘,undo/redo)
├── views/ # 页面(home、login)
├── utils/
│ ├── request.js # Axios 实例 + 拦截器:统一 Auth(不带 Bearer)+ 按前缀路由 baseURL
│ ├── taskPolling.js # 任务生成入口:组装参数 → POST 创建任务 → 20s HTTP 轮询直至完成/失败
│ ├── modelApi.js # 模型业务层:localStorage 30s 缓存 + pendingRequests 并发去重 + 平台编码映射
│ ├── createTask.js # 任务 body 构造:Painting 返回 modelParams,Video 走 Playload 适配器
│ ├── modelConfig.js # Video 旧架构:从远程 JSON 加载 workflow 配置(含 localStorage 每日缓存)
│ ├── downloadImage.js # 图片/视频下载:fetch → Blob → 自动文件名下载
│ ├── tokenError.js # 认证失败处理:提示后 5 秒刷新页面
│ ├── encrypt.ts # 加密工具(Base64/MD5/RSA/AES,依赖 crypto-js、jsencrypt)
│ └── 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 模型参数 schema:每模型一个 JS 文件,定义 params 和各字段的 ui 类型
│ └── modelConfig/ # 静态模型列表:painting.json(按 tag 分组)、video.json(按 pattern 分组)
模型参数配置(Painting 新架构)
src/config/models/ 下每个模型一个 JS 文件,参数通过不同 UI 组件承载:
ui: 'textarea'→ Sender 组件主输入框(prompt)ui: 'proportion'+ui: 'resolution'→paintingProportion组件(共用 Popover,options 可含custom开启自定义 W/H)ui: 'quantity'→Quantity组件(动态上限由 model config 的options数组最大值派生)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 采用内部中文名区分。
dialogBox 编排中心
src/components/dialogBox/index.vue 是核心编排组件,负责:
- 模型选择切换:监听
model+modelType变化,调用loadModelConfig()加载模型参数 schema - 派生 UI 配置:从 model config 计算
paintingProportionOpts(滤除custom选项)、paintingResolutionOpts、hasCustomSize(是否显示自定义尺寸)、quantityMax - 状态管理:
customWidth/customHight通过v-model:width/v-model:height与paintingProportion双向绑定 - 参数回填:
fillParamsFromResult()供历史记录重编辑使用 - 任务发起:
handleStart()收集所有参数 → 构造data→ 调用taskPolling.js:generate()
$attrs 穿透注意
向子组件传递的 prop 如果子组件未声明,会通过 $attrs 穿透到根元素。例如 dialogBox 向 paintingProportion 传递 v-model:width="customWidth",若 paintingProportion 未声明 width prop,值会穿透到 <Popover> 的 width prop,导致异常宽度。所有通过 v-model 传递的值,子组件必须声明对应的 prop。
API 层设计原则
src/apis/只做纯 HTTP 调用(service.get/post/delete),不含缓存、localStorage、业务逻辑- 缓存、数据转换等业务逻辑放在
src/utils/中
核心数据流
Painting(新架构):
- 用户设置参数 →
dialogBox从 model config 派生 proportion/resolution 选项、hasCustomSize、quantityMax handleStart()收集参数 → 组装{ modelParams, request }taskPolling.js:generate()→createTask(data)→ Painting 直接返回data.modelParamsgetModelId(type, modelName)查找 UUID(内部调用fetchPlatformModels走缓存)requestCreateTask(body, sessionId)→ POST/suanli/v1/tasks,携带X-Session-Idheader- 返回 task_id → 20s 间隔轮询直至完成/失败
Video(旧架构,保留):
- 用户设置 Pattern、videoModel、比例、时长
createTask(data)→runninghub.Playload(data)→fetchModelConfig()获取 workflow JSON → 返回{ workflowId, nodeInfoList }- 后续同 Painting 步骤 4-6
关键注意事项
sessionId来自登录接口返回的userInfo.sessionId,存储在useUserStore().userInfo中。taskPolling.js必须使用该值,禁止随机生成。X-Session-Id自定义 header 需要 nginx 在/suanli/location 的Access-Control-Allow-Headers中加入,否则 POST 请求会触发 CORS 预检失败。- 模型列表缓存:
modelApi.js中fetchPlatformModels使用 localStorage 30 秒 TTL +pendingRequestsMap 并发去重,避免重复请求。 - 页面加载预请求:平台模型列表在
dialogBox onMounted时预请求,避免首次点击"发送"时才触发。
接口速查
| 函数 | 端点 | 用途 |
|---|---|---|
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 |
删除历史记录 |
任务响应格式
// 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 APIunplugin-vue-components:自动注册src/components/下的组件和 Element Plus 组件- Element Plus 图标通过
unplugin-icons按需加载