diff --git a/CLAUDE.md b/CLAUDE.md index 2f54f6d..cf68295 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,8 +33,6 @@ src/ │ │ ├── index.js # definePaintingPlatform():controls、state、loadConfig、buildTaskBody 等 │ │ ├── modelSelector.vue # 模型选择器(按 API tags 分组) │ │ ├── imageUploader.vue # 图片上传组件 -│ │ ├── models/ # 模型参数 schema(本地 JS,待后端化) -│ │ │ └── index.js # getModelConfig(modelName) → 查找 config │ │ └── controls/ # 平台专用控件 │ │ ├── proportion.vue │ │ ├── dimension.vue @@ -67,9 +65,8 @@ src/ └── utils/ ├── request.js # Axios 实例 + 拦截器:统一 Auth(不带 Bearer)+ 按前缀路由 baseURL ├── taskPolling.js # 任务生成入口:组装参数 → POST 创建任务 → 20s HTTP 轮询直至完成/失败 - ├── modelApi.js # 模型业务层:localStorage 30s 缓存 + pendingRequests 并发去重 + 平台编码映射 - ├── createTask.js # 透传层:return data.body(各平台 buildTaskBody() 已返回扁平 modelParams) - ├── modelConfig.js # Video 专用:从远程 JSON 加载 workflow 配置(含 localStorage 每日缓存) + ├── modelApi.js # 模型业务层:localStorage 缓存 + 并发去重。平台模型列表(30s TTL) + 模型参数配置(60s TTL) + ├── modelConfigHelper.js # 模型配置共享工具:syncDefaults / syncParamValues / getDimConfig / checkShowWhen ├── downloadImage.js # 图片/视频下载:fetch → Blob → 自动文件名下载 ├── uploadImage.js # 图片上传工具 ├── tokenError.js # 认证失败处理:提示后 5 秒刷新页面 @@ -128,47 +125,54 @@ props: (config) => ({ - **平台切换**:`const platform = computed(() => createPlatform(props.type))`,切换时重置默认模型并加载模型列表 - **控件渲染**:`visibleControls = platform.controls.filter(c => c.show(getCurrentConfig()))`,用 `` + `v-bind="ctrl.props(...)"` 渲染 -- **配置获取**:`getCurrentConfig()` 返回 `platform.modelConfig?.value ?? platform.modelDisplayConfig?.value`,兼容两种配置来源 +- **配置获取**:`getCurrentConfig()` 返回 `platform.modelConfig?.value` - **模型切换**:`watch([model, modelType])` → `platform.loadConfig()` → `syncDefaults()` 将 schema 写入响应式 state → controls 的 `show()`/`props()` 读取 state → `visibleControls` 自动更新 → UI 重渲染 - **任务发起**:`handleStart()` 调用 `platform.validateBeforeSubmit()` → `platform.buildTaskBody()` → `generate()` - **参数回填**:`fillParamsFromResult()` 委托给 `platform.fillFromResult()` ### 统一数据流(Painting + Video) -两个平台现已统一,`createTask.js` 是纯透传: +两个平台现已统一,通过后端 API 获取模型参数配置: -1. 用户选择模型 → `platform.loadConfig(modelName, modelType)` 加载参数 schema +1. 用户选择模型 → `platform.loadConfig(modelName, modelType)` → 调用 `GET /suanli/v1/models/:id/config`(优先 60s 缓存)加载参数 schema 2. 参数 schema 驱动 `controls` 渲染 UI,用户填写参数 3. 用户点击发送 → `handleStart()` → `platform.buildTaskBody({ prompt, referenceImages })` 返回扁平 `modelParams` -4. `createTask(data)` 透传 `data.body`(不再做任何转换) -5. `getModelId(type, modelName)` 查找 UUID → POST `/suanli/v1/tasks`(`X-Session-Id` header) -6. 20s 间隔轮询直至完成/失败 +4. `taskPolling.js` 直接读取 `data.body` → `getModelId(type, modelName)` 查找 UUID → POST `/suanli/v1/tasks`(`X-Session-Id` header) +5. 20s 间隔轮询直至完成/失败 ### 模型参数配置 -Painting 模型参数 schema 在 `src/platforms/painting/models/*.js` 中,参数通过 `ui` 字段映射到 UI 控件: +模型参数配置通过后端 API `GET /suanli/v1/models/:id/config` 获取(60s localStorage 缓存),替代了旧的本地硬编码。参数通过 `ui` 字段映射到前端控件: | `ui` 值 | 控件 | 说明 | |---------|------|------| | `textarea` | Sender 内置 textarea | prompt 输入框 | | `proportion` | `PaintingProportion` / `VideoProportion` | 比例选择 Popover(`options` 含 `custom` 时可自定义宽高) | | `resolution` | proportion 控件内部 | 分辨率子选项,与 proportion 共用 Popover | -| `dimension` | `DimensionInput` | **组合模式**:单字段 `"W*H"` 格式,通过 `dimension.parse/format` 序列化 | -| `dimensionWidth` + `dimensionHeight` | `DimensionInput` | **拆分模式**:两个独立字段,共享同一 Popover 和比例锁 | +| `dimension` | `DimensionInput` | **组合模式**:单字段 `"W*H"` 格式,后端传 `dimension.separator`,前端生成 parse/format | +| `dimensionWidth` + `dimensionHeight` | `DimensionInput` | **拆分模式**:两个独立字段(含 `min`/`max`),共享比例锁 | +| `number` | proportion 控件内部 | 自定义宽高(如 Flux 的 customWidth/customHight),配合 `showWhen` 条件显示 | | `select` | `Select` | 通用下拉(如 quality) | -| `quantity` | `Quantity` | 生成数量,上限由 `options` 最大值派生 | +| `quantity` | `Quantity` | 生成数量,`options` 必须为数字数组,上限由 `Math.max()` 派生 | | `imageUpload` | `ImageUploader` | 参考图上传,`maxCount` 控制上限 | | `hidden` | 无 | 静默写入默认值 | -**dimension 模式区分**:通过 `getDimConfig()` 自动检测 `ui: 'dimension'`(组合)或 `ui: 'dimensionWidth'`(拆分),两种模式共用 `DimensionInput` 组件。 +**dimension 模式区分**:`getDimConfig()`(来自 `modelConfigHelper.js`)自动检测 `ui: 'dimension'`(组合)或 `ui: 'dimensionWidth'`(拆分),两种模式共用 `DimensionInput` 组件。 -**条件显示**:`showWhen: { aspectRatio: 'custom' }` 使参数仅在 proportion 选 `custom` 时显示。 +**条件显示**:`showWhen: { aspectRatio: 'custom' }` 使参数仅在 proportion 选 `custom` 时显示。`checkShowWhen()` 在 controls 的 `show()` 回调中调用,因直接读取 reactive 的 `paramValues[key]`,computed 自动追踪依赖。 -### `displayNameMap` 机制 +### modelConfigHelper 共享工具 -`src/platforms/painting/models/index.js` 中 `displayNameMap` 负责将 API 返回的 `display_name` 映射到 config key。同一模型在不同 tag 下可能共用一个 `display_name`(如 `GPT-Image-2` 和 `GPT-image-2`),config key 采用内部中文名区分。 +`src/utils/modelConfigHelper.js` 提供 Painting/Video 双平台共用的 4 个纯函数: -**已知 bug**:`displayNameMap` 存在重复 key `'GPT-Image-2'`,第二条会覆盖第一条,导致文字生图版 GPT-Image-2 查找走 `displayNameMap` 时映射到 I2I 版。当前因 `model.value` 已是中文 config key 直达 `configs[]`,暂不触发。若后续改为按 `display_name` 查找,需修复此重复 key。 +| 函数 | 说明 | +|------|------| +| `syncDefaults(config, state)` | 将 API 返回的 config 同步到响应式 state。增强项:`dimension.separator` → parse/format 生成、`promptPlaceholder` 同步 | +| `syncParamValues(config, state)` | 在 `buildTaskBody` 前将专用 ref 回写到 `paramValues` | +| `getDimConfig(config)` | 检测 combined/split 模式 | +| `checkShowWhen(param, paramValues)` | 检查 `showWhen` 条件 | + +`state` 参数对象需包含 `modelConfig, paramValues, proportion, resolution, quantity, quality, customWidth, customHight, dimWidth, dimHeight, promptPlaceholder`。各平台通过包装函数(如 `syncDefaults(config) { _syncDefaults(config, paintingState) }`)适配。 ### `$attrs` 穿透注意 @@ -184,6 +188,7 @@ Painting 模型参数 schema 在 `src/platforms/painting/models/*.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 并发去重。 +- **模型配置缓存**:`modelApi.js` 中 `getModelConfig` 使用 localStorage 60 秒 TTL + `pendingConfigRequests` Map 并发去重。`loadModels()` 会在获取模型列表后调用 `preloadModelConfigs` 批量预加载。 - **平台包预加载**:dialogBox 顶层 `import` Painting 和 Video 平台包,触发自注册,确保首次使用时 registry 已就绪。 - **VirtualScroller 反旋转**:组件用外层 `transform: rotate(180deg)` 实现 reverse 底部锚定。所有作为 slot 插入的内容必须在根元素加 `transform: rotate(180deg)` 反旋转,否则文字/图片会颠倒显示。参考 `src/views/home/display/components/set.vue:2`。 - **VirtualScroller 存在独立测试页**:`src/components/virtual-scroller/test.html` + `test-data.js`,可用于验证虚拟滚动行为。 @@ -225,6 +230,8 @@ Painting 模型参数 schema 在 `src/platforms/painting/models/*.js` 中,参 | `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?}`) | +| `requestModelConfigsBatch` | POST `/suanli/v1/models/configs` | 批量获取模型配置(body: `{ modelIds: [...] }`) | +| `requestModelConfig` | GET `/suanli/v1/models/:id/config` | 单条模型配置(60s 缓存优先) | | `cancelOrCollect` | POST `/collect/toggle` | 收藏/取消收藏 | | `deleteGenerateHistory` | DELETE `/taskRecordHistory/delete` | 删除历史记录 |