diff --git a/CLAUDE.md b/CLAUDE.md index b84e2aa..09f25ff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,36 +31,44 @@ src/ ├── main.js # 入口:创建 Vue 应用,安装 Pinia/Router/VueVirtualScroller ├── router/index.js # 路由定义 + token 验证守卫 ├── stores/ # Pinia 状态管理 -│ ├── user.js # 用户认证、信息(含 sessionId) +│ ├── user.js # 用户认证、信息(含 sessionId),使用 pinia persist 持久化 token │ ├── display.js # 生成历史列表、UI 状态(滚动、画布等) │ └── param.js # 参数 store(当前为空) ├── apis/ # HTTP API 层:纯请求封装,不含业务逻辑 -│ ├── auth/ # 认证相关(登录、token 校验、用户信息) +│ ├── 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) +│ │ ├── quantity/ # 生成数量选择器(支持 1-6,上限由模型配置派生) │ │ ├── Time/ # 视频时长选择器 │ │ └── pattern/ # 视频模式选择器 +│ ├── Popover/ # 自定义弹出层(Teleport to body,position:fixed + fit-content 宽度) +│ ├── Select/ # 自定义下拉选择器(分组选项,与 Popover 协调互斥开关) +│ ├── Img/ # 图片包装组件(点击全屏查看,Teleport 实现) │ ├── virtual-scroller/# 虚拟滚动列表组件(自定义实现,reverse 模式) -│ └── canvas/ # 图片画布编辑(圆/矩形选区,局部重绘) +│ └── canvas/ # 图片画布编辑(圆/矩形选区,局部重绘,undo/redo) ├── views/ # 页面(home、login) ├── utils/ │ ├── request.js # Axios 实例 + 拦截器:统一 Auth(不带 Bearer)+ 按前缀路由 baseURL -│ ├── websocket.js # 任务生成入口:组装参数 → POST 创建任务 → 20s 轮询直至完成/失败 +│ ├── taskPolling.js # 任务生成入口:组装参数 → POST 创建任务 → 20s HTTP 轮询直至完成/失败 │ ├── modelApi.js # 模型业务层:localStorage 30s 缓存 + pendingRequests 并发去重 + 平台编码映射 │ ├── createTask.js # 任务 body 构造:Painting 返回 modelParams,Video 走 Playload 适配器 -│ ├── modelConfig.js # Video 旧架构:从远程 JSON 加载 workflow 配置 +│ ├── 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 模型参数配置:每模型一个 JS 文件,定义 params schema +│ ├── models/ # Painting 模型参数 schema:每模型一个 JS 文件,定义 params 和各字段的 ui 类型 +│ └── modelConfig/ # 静态模型列表:painting.json(按 tag 分组)、video.json(按 pattern 分组) ``` ### 模型参数配置(Painting 新架构) @@ -68,8 +76,8 @@ src/ `src/config/models/` 下每个模型一个 JS 文件,参数通过不同 UI 组件承载: - **`ui: 'textarea'`** → Sender 组件主输入框(prompt) -- **`ui: 'proportion'`** + **`ui: 'resolution'`** → `paintingProportion` 组件(共用 Popover,含自定义 W/H) -- **`ui: 'quantity'`** → `Quantity` 组件(动态 1-6 张) +- **`ui: 'proportion'`** + **`ui: 'resolution'`** → `paintingProportion` 组件(共用 Popover,options 可含 `custom` 开启自定义 W/H) +- **`ui: 'quantity'`** → `Quantity` 组件(动态上限由 model config 的 `options` 数组最大值派生) - **`ui: 'imageUpload'`** → `ImageUploader` 组件 - **`ui: 'hidden'`** → 无 UI,仅写入默认值(如 outputFormat: 'png') @@ -79,6 +87,20 @@ src/ `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,值会穿透到 `` 的 `width` prop,导致异常宽度。**所有通过 `v-model` 传递的值,子组件必须声明对应的 prop。** + ### API 层设计原则 - `src/apis/` 只做纯 HTTP 调用(`service.get/post/delete`),不含缓存、localStorage、业务逻辑 @@ -88,9 +110,9 @@ src/ **Painting(新架构):** -1. 用户设置参数 → 模型选择器按 `tags` 分组,控件根据 model config 的 `ui` 字段渲染 -2. `handleStart()` 收集 `paramValues`(UI refs 通过 watcher 双向同步)→ 组装 `{ modelParams, request }` -3. `websocket.js:generate()` → `createTask(data)` → Painting 直接返回 `data.modelParams` +1. 用户设置参数 → `dialogBox` 从 model config 派生 proportion/resolution 选项、hasCustomSize、quantityMax +2. `handleStart()` 收集参数 → 组装 `{ modelParams, request }` +3. `taskPolling.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 间隔轮询直至完成/失败 @@ -103,9 +125,10 @@ src/ ### 关键注意事项 -- **`sessionId`** 来自登录接口返回的 `userInfo.sessionId`,存储在 `useUserStore().userInfo` 中。`websocket.js` 必须使用该值,禁止随机生成。 +- **`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 + `pendingRequests` Map 并发去重,避免重复请求。 +- **页面加载预请求**:平台模型列表在 `dialogBox onMounted` 时预请求,避免首次点击"发送"时才触发。 ### 接口速查 diff --git a/src/components/Popover/index.vue b/src/components/Popover/index.vue index 648b354..aa3c7c4 100644 --- a/src/components/Popover/index.vue +++ b/src/components/Popover/index.vue @@ -55,11 +55,12 @@ if (!window.__currentOpenPopoverId__) { const contentStyle = computed(() => { const w = typeof props.width === 'number' ? `${props.width}px` : props.width + const isAuto = w === 'auto' return { ...position.value, - width: w, - maxWidth: w === 'auto' ? 'none' : w, - minWidth: w === 'auto' ? '0' : w + width: isAuto ? 'fit-content' : w, + maxWidth: isAuto ? '600px' : w, + minWidth: isAuto ? '0' : w } }) diff --git a/src/components/canvas/index.vue b/src/components/canvas/index.vue index bdae582..7d75b30 100644 --- a/src/components/canvas/index.vue +++ b/src/components/canvas/index.vue @@ -160,7 +160,7 @@