From 1d07cdc907793db13d1ed5dc64f40df0f3b32915 Mon Sep 17 00:00:00 2001 From: WangLeo <690854599@qq.com> Date: Wed, 17 Jun 2026 18:22:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BE=93=E5=85=A5=E6=A1=86=E5=AE=BD?= =?UTF-8?q?=E5=BA=A662%=E3=80=81=E5=8F=91=E9=80=81=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=9C=86=E5=BD=A2=E7=81=B0=E5=BA=A6=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E3=80=81=E7=BB=98=E7=94=BB=E5=B9=B3=E5=8F=B0=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E9=80=89=E6=96=87=E7=94=9F=E5=9B=BE=E9=A6=96=E4=B8=AA?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 输入框从75%缩至62% - 发送按钮去掉文字改为44px正圆形,无提示词时浅灰底+深色箭头,有提示词时稍深灰底+白色箭头 - 绘画平台 getDefaultModel() 返回空字符串,modelSelector 加载后优先选 text tag 模型 - CLAUDE.md 新增 Display Store/Canvas/视图层架构章节,移除已删除的 model-configs 目录,修正环境变量表 --- CLAUDE.md | 62 +++++++++++++++++++++--- src/components/dialogBox/index.vue | 28 ++++------- src/platforms/painting/index.js | 2 +- src/platforms/painting/modelSelector.vue | 11 +++-- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9471e5d..c63fb11 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,6 @@ AI 绘画/视频/音乐生成前端操作平台,通过 HTTP 接口对接算力 ### 关键目录 ``` -├── model-configs/ # 运维参考:模型参数配置 JSON 文件(hailuo, ltx, vidu 等,位于项目根目录) ├── config/ │ └── plugins.js # Vite 插件配置(unplugin-auto-import + unplugin-vue-components resolver) ├── vite.config.js # 构建配置:alias(@→src, ~→根目录)、envPrefix: ['VITE','FILE']、optimizeDeps @@ -74,7 +73,7 @@ src/ │ └── timeControl.vue # 时长滑块(常用模式) ├── stores/ # Pinia 状态管理 │ ├── user.js # 用户认证、信息(含 sessionId),pinia persist 持久化 token -│ ├── display.js # 生成历史列表、UI 状态(滚动、画布等) +│ ├── display.js # 中央调度器:历史列表、画布状态、重新编辑/再次生成、输入框收缩 │ └── param.js # 参数 store(当前为空) ├── apis/ # HTTP API 层:纯请求封装,不含业务逻辑 │ ├── auth/ # 认证相关(登录、token 校验、用户信息、验证码) @@ -171,6 +170,39 @@ props: (config) => ({ 4. `taskPolling.js` 直接读取 `data.body` → `getModelId(type, modelName)` 查找 UUID → POST `/suanli/v1/tasks`(请求体含 `sessionId`) 5. 20s 间隔轮询直至完成/失败 +### Display Store — 中央调度器 + +`src/stores/display.js`(`useDisplayStore`)被 6 个文件引用,是展示层跨组件通信的中央枢纽: + +- **历史列表生命周期**:`initHistoryList`(首次加载)→ `appendHistoryList`(滚动加载更多)→ `deleteHistoryItem` +- **生成状态追踪**:`addGeneratingItem`(生成中占位)→ `updateItemToSuccess`(完成时替换为结果 URL),`isSubGerenate` 控制发送按钮 loading 态 +- **重新编辑/再次生成**:`setResultData()` 存储当前操作的历史数据 → `fillParamsForEdit()` 仅回填参数到 dialogBox → `triggerGenerateWithResult()` 回填后立即发起生成。二者通过 `dialogBoxRef` 跨组件调用 `dialogBox` 的 `fillParamsFromResult()` + `handleStart()` +- **Canvas 状态**:`openCanvas(data)` 统一入口,接收来自 Set 卡片或 ImageUploader 的数据,设置 `canvasVisible/canvasImage/canvasReferenceImages/canvasSource` 四个响应式状态 +- **滚动 → 输入框收缩**:`scrollToBottom()` 调用虚拟滚动 API;`Sender_variant` ref 在 `display/index.vue` 的 `handleScroll` 中被修改,在 `dialogBox` 的 `autoSizeConfig` 中被消费 + +### Canvas 画布编辑架构 + +`src/components/canvas/index.vue` 是一个完整的图片编辑器(~600 行),用于局部重绘场景: + +- **选区绘制**:圆形/矩形选区,5 色(红/橙/绿/蓝/紫)自动轮换,按住拖拽绘制到 canvas 上 +- **undo/redo**:`history` 数组 + `historyIndex` 指针,每次操作(添加选区/删除/修改描述)保存快照并推进指针 +- **参考图选择**:从 props 的 `referenceImages` 中多选参考图,选区描述自动拼接为「将图1{颜色}{框/圈}内的【XXX】替换为【图{X}中的{描述}】」 +- **提示词组合**:选区描述 + 笔刷 textarea 内容组合为完整 prompt,通过 `generate()` 提交任务(`modelType: 'edit'`) +- **编辑模式限制**:画笔生成的任务标记 `modelType === 'edit'`,在 Set 卡片中禁止"重新编辑"和"再次生成" +- **触发入口**:Set 卡片的画笔按钮、ImageUploader 的已上传图片点击 + +### 视图层 + +`src/views/home/index.vue` 是 thin shell,同时挂载 `dialogBox`(输入编排)和 `display`(结果展示),通过 `useDisplay.setDialogBoxRef()` 建立两者之间的通信桥梁。 + +`src/views/home/display/index.vue` 负责结果展示区: + +- **首次加载**:`onMounted` → `fetchHistory()` 拉取第 1 页 → 转换后 `initHistoryList` → 多次重试 `scrollToBottom` +- **无限滚动**:`handleScroll` 监听 `isAtPageTop` → `fetchHistory(true)`(加载更早的历史),3 秒防抖锁 `isLoadingMoreLocked` 防重复请求 +- **数据转换**:`conversion()` 将 API 响应适配为 UI 格式 — status 映射(`queued/processing`→`generate`,`failed/cancelled`→`error`)、`outputs` 扁平化为 `files` URL 数组 +- **筛选控件**:时间段选择(全部/一周/一月/三月)+ 收藏筛选,通过 `requestTaskHistory` 的 `user_id`/`platform_code`/`status` 参数过滤 +- **退出按钮**:如果在 iframe 内通过 `postMessage` 通知父页面导航,否则 `router.go(-1)` + ### 模型标识与查找 API 返回的模型对象包含三个标识字段: @@ -181,6 +213,14 @@ API 返回的模型对象包含三个标识字段: | `name` | `vidu-text-to-video-q3-turbo` | 内部标识名,通常也唯一 | | `display_name` | `Vidu q3-turbo` | 用户可见的显示名,**可能重复**(不同 pattern 下的同名模型) | +各平台 `model.value` 使用的标识类型不同: + +| 平台 | `model.value` 类型 | 原因 | +|------|-------------------|------| +| Painting | `display_name` | 历史兼容,`getDefaultModel()` 返回 `''` 后由 modelSelector watcher 自动选第一个 text tag 模型 | +| Video | `id`(UUID) | 避免 `display_name` 在不同 pattern 下重复导致查找歧义 | +| Music | `id`(UUID) | 与 Video 一致,避免冲突 | + **Video 平台使用 `id`(UUID)作为 `model.value`**,避免 `display_name` 冲突导致的模型查找错误。`modelSelector.vue` 中 `modelGroups` 的 `value` 设为 `m.id`,`label` 仍用 `display_name` 显示。 `getModelId(type, modelName)` 查找优先级:`m.id === modelName` → `m.name === modelName` → `m.display_name === modelName`,向下兼容旧的 name/display_name 调用。 @@ -384,20 +424,30 @@ Music 平台与 Painting/Video 的关键差异: ### 环境变量速查 +`.env.development`(测试环境)和 `.env.production`(生产环境)中实际配置的变量: + ```bash -# .env.development +# 地址前缀 VITE_BASE = '/' # 应用基础路径 + +# 主服务 VITE_API_PREFIX = '/api' # 主服务前缀 VITE_API_BASE_URL = 'http://...' # 主服务(默认目标) -VITE_API_PAY_PREFIX = '/pay' # 支付服务前缀 -VITE_API_PAY_TARGET = 'http://...' # 支付服务目标 + +# 任务服务 VITE_API_TASK_PREFIX = '/suanli' # 任务服务前缀 VITE_API_TASK_TARGET = 'http://...' # 任务服务目标 + +# 图片上传 VITE_API_WORKFLOW_UPLOAD = 'http://...' # 图片上传地址(imageUploader 组件 action) -VITE_OPEN_DEVTOOLS = false # 是否开启开发者工具 + +# 其他 +VITE_OPEN_DEVTOOLS = false # 是否开启开发者工具(仅 .env.development) FILE_OPEN_PREVIEW = true # 是否开启 KKFileView 预览 ``` +`request.js` 还引用了 `VITE_API_PAY_PREFIX`/`VITE_API_PAY_TARGET` 和 `VITE_API_AIGC_PREFIX`/`VITE_API_AIGC_TARGET`,作为**可选**前缀路由扩展点——未配置时走默认 target,当前两个 .env 文件中均未设置。 + `vite.config.js` 中 `envPrefix: ['VITE', 'FILE']`,因此只有以 `VITE_` 和 `FILE_` 开头的变量会被暴露给客户端代码。 ### 平台编码映射 diff --git a/src/components/dialogBox/index.vue b/src/components/dialogBox/index.vue index 29f1fa8..14f12c2 100644 --- a/src/components/dialogBox/index.vue +++ b/src/components/dialogBox/index.vue @@ -60,10 +60,9 @@ -
- +
+ -
发送
@@ -213,7 +212,7 @@ watch(() => props.type, (newType) => { diff --git a/src/platforms/painting/index.js b/src/platforms/painting/index.js index 4c908a3..b32c628 100644 --- a/src/platforms/painting/index.js +++ b/src/platforms/painting/index.js @@ -171,7 +171,7 @@ export function definePaintingPlatform() { }, getDefaultModel() { - return 'Flux 2' + return '' }, validateBeforeSubmit() { diff --git a/src/platforms/painting/modelSelector.vue b/src/platforms/painting/modelSelector.vue index 806a83d..80f9104 100644 --- a/src/platforms/painting/modelSelector.vue +++ b/src/platforms/painting/modelSelector.vue @@ -102,15 +102,16 @@ const modelGroups = computed(() => { })) }) -// 模型列表加载后自动纠正不可用模型 +// 模型列表加载后自动纠正不可用模型,优先选文生图(text tag)第一个 watch(platformModels, (models) => { if (models.length === 0) return const currentModel = models.find((m) => (m.display_name || m.name) === props.modelValue || m.id === props.modelValue) if (!currentModel || currentModel.disabled) { - const firstEnabled = models.find((m) => !m.disabled) - if (firstEnabled) { - emit('update:modelValue', firstEnabled.display_name || firstEnabled.name) - emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name))) + const firstText = models.find((m) => !m.disabled && m.tags?.includes('text')) + const fallback = firstText || models.find((m) => !m.disabled) + if (fallback) { + emit('update:modelValue', fallback.display_name || fallback.name) + emit('update:typeValue', tagToInputType(findTagForModel(fallback.display_name || fallback.name))) } } }, { immediate: true })