Files
cmbot/docs/11-ai-outfit.md
T

264 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI 穿搭模块设计
## 1. 文档定位
本文档定义 cmbot「2 AI 穿搭」页签的设计。它是一项独立能力,按 `docs/09` 第 262 行「新能力单独成文」要求成文,不混入印花合成相关文档。
参考来源:同事旧项目「标题生成产品图工具」的分析见 `docs/旧ai穿搭项目.md`。本模块把该项目验证过的「本地图 + 提示词 → 调 AI 图像 API → 写回 Excel」机制,移植进 cmbot 的分层架构(`app` / `core` / `services`)。
一句话目标:**以 Excel 为数据源,按行读取「衣服图 + 标题/货号」,套提示词调 AI 图像 API 生成「人物穿着该衣服」的效果图,保存 JPG 并把结果路径写回 Excel。**
## 2. 目标与非目标
### 2.1 目标
- Excel 批量驱动:逐行读取标题、货号、衣服图路径,生成后把结果写回 Excel(与旧项目列约定一致)。
- AI 生成:衣服图作为图像输入 + 提示词(可插标题/货号占位符)→ 生成人物上身/穿搭效果图,保留款式、版型、颜色、印花。
- 复用 cmbot 现有设施:配置(`config_service` / `~/.cmbot`)、日志、输出目录、后台线程模式、打包/更新,不重造。
- 稳态批量:并发、限速、阶梯重试、温和停止、实时日志、进度与失败清单。
- 分层可测:AI 调用与 Excel 适配放 `services`,无 GUI 逻辑放 `core`,关键逻辑有单测。
### 2.2 非目标
- 不改变数据源形态:**输入固定为 Excel**(不在本期改为文件夹/队列;但核心对内部模型工作,将来加别的输入源不必改核心)。
- 不做模特/姿态/背景的精细可视化编辑(由提示词控制,不做画布微调)。
- 不内置 AI 模型,依赖外部中转 API。
- 不照搬旧项目的 bootstrap / offline_runtime / .bat(cmbot 已有自己的打包与更新体系)。
## 3. 总体架构
把 Excel 当作 **I/O 适配边界**,中间用 cmbot 内部数据模型,核心 AI 逻辑与数据源解耦:
```
Excel(数据源,固定)
│ services/excel_service:读行 → List[OutfitTask](转成内部模型)
▼
core/ai_outfit + services/ai_image_service:
衣服图 + 提示词 → 调 AI 图像 API → 人物上身图(JPG)
│ 复用:并发/日志/配置/进度/~/.cmbot
▼
services/excel_service:把结果写回 Excel(D 新图路径 / E 状态 / F 原因)
```
分层落点:
- `core/models.py`:新增 `OutfitTask` / `OutfitResult`(纯数据,无 PySide6)。
- `services/excel_service.py`:Excel 读取、写回、占用检测、跳过/重试判断。
- `services/ai_image_service.py`:移植旧项目 `ImageApiClient`(多模型、多请求格式、传图、取图、重试、超时)。
- `core/ai_outfit.py`(或 service 内):单行生成的纯逻辑编排(提示词渲染、调用、保存、产出结果),可单测。
- `app/widgets/ai_outfit_panel.py` + 主窗口「2 AI 穿搭」页签:界面与后台线程(`QThread` + `Worker(QObject)` + 信号)。
## 4. Excel 适配(数据源)
沿用旧项目列约定(固定,先不做可配置):
| 列 | 含义 | 读/写 |
|---|---|---|
| A | 标题 | 读 |
| B | 货号(商品 ID) | 读 |
| C | 衣服图路径(本机绝对路径) | 读 |
| D | 生成结果图片路径 | 写回 |
| E | 完成状态:`完成` / `失败`(空=未处理) | 写回 |
| F | 失败原因(仅人工查看) | 写回 |
规则:
- 默认读第一个工作表,第 1 行表头,从第 2 行开始。
- **跳过 E=`完成` 的行**;是否处理 E=`失败` 的行由界面「重试失败行」决定(勾选则处理「空白 + 失败」,否则只处理「空白」)。
- 标题/货号/衣服图任一为空 → 跳过该行,不写状态、不中断。
- 字段按原样读取,不清理空格、不回写清理值。
- **开始前检测 Excel 是否被占用**(Office 打开会锁文件,openpyxl 无法写回)→ 提示「请关闭 Excel 后再开始」。
- **每处理完一行即保存 Excel**(降低崩溃丢结果风险)。
- 成功 → D=新图绝对路径、E=`完成`;失败 → E=`失败`、F=原因(同步写日志)。
## 5. 内部数据模型
`core/models.py` 新增(纯 dataclass,Python 3.7 兼容,不依赖 PySide6):
- `OutfitTask`:`row_index:int`、`title:str`、`product_id:str`、`garment_path:str`、`status:str`(待处理/完成/失败)。
- `OutfitResult`:`task`、`success:bool`、`output_path:str`、`error:str`、`attempts:int`。
Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只认 `OutfitTask`,因此将来换数据源只需另写适配器。
## 6. AI 图像服务(移植旧项目)
`services/ai_image_service.py` 移植旧项目 `ImageApiClient`,要点(详见 `docs/旧ai穿搭项目.md` §5.1/5.2):
- **多模型配置**:`url / model / api_key / api_type / timeout_seconds / connect_timeout_seconds / extra_body`,记住上次所选模型。
- **多请求格式**(按 `api_type`:`auto/chat/gemini/images/images_edits`):
- chat(OpenAI 兼容 `/v1/chat/completions`):`messages[].content` = `{type:text}` + `{type:image_url, image_url:{url:data_url}}`。
- gemini(`generateContent`):`contents[].parts` = `{text}` + `{inlineData:{mimeType,data}}`。
- images:`{model,prompt,image_urls:[data_url],aspect_ratio,resolution,n}`。
- images_edits:multipart `data` + `files={"image":...}`。
- **传图**:本地衣服图 → `data:<mime>;base64,...`(data-url)或 multipart 文件。
- **取图**:递归遍历响应 JSON 找 base64 / data-url / 图片 URL(再下载),对中转 API 结构差异强兼容。
- **URL 归一化**、`extra_body` 合并、字段校验(缺 `url/model/api_key` 时阻止开始)。
> Python 3.7 注意:旧项目用了 `dict[str, Any]` 等 PEP 585 写法,移植时需 `from __future__ import annotations` 或改用 `typing.Dict`,以兼容 cmbot 的 Python 3.7.9。
## 7. 提示词
- 提示词模板存 `~/.cmbot/config/outfit_prompt.txt`,界面可查看/编辑/保存;点「开始」前自动保存一次。
- 占位符:仅 `{title}`(标题);界面只提供「插入标题」按钮,把占位符插入光标处。货号(`product_id`)不进提示词——它只用于 Excel B 列读取与输出文件命名(`货号.jpg`),不影响 AI 生成内容。(`render_prompt` 仍会替换偶然出现的 `{product_id}`,保持兼容,但界面不再引导插入。)
- **「保存话术」按钮**:把当前编辑的模板原文(占位符原样保留,**不**存替换后的结果)经 `config_service` 写入 `outfit_prompt.txt`。用途是"改完先存、暂不开跑"并给用户明确反馈;与"开始前自动保存"并存、互为兜底(保留按钮是有意为之,对非技术用户更安心)。
- **最终提示词预览(内嵌、不弹窗)**:左栏话术编辑下方常驻一块只读预览区,编辑话术时实时把占位符替换成样本行真值,显示最终要发送给 AI 的完整文字。样本行下拉默认取明细表当前选中行、没选则取第一条待处理行;未选 Excel / 无可预览行时显示带高亮占位符的模板原文 + 提示;缺 `{title}` 占位符时在预览区提示。预览只读,不改话术本身。(早期设计为弹窗 `QDialog`,因调话术需"边改边看"改为内嵌。)
- 缺标题占位符时开始前弹窗询问是否继续。
- 默认话术方向(穿搭/上身,区别于旧项目的主图场景,需另调):人物上身实穿、保留衣服款式/版型/颜色/印花、合适身材与场景、电商可用、默认纯净不加促销牛皮癣。
### 7.1 自动附加「批量生成输出要求」
最终发给 AI 的提示词 = **用户话术(替换 `{title}` 后)** + **一段自动附加的输出要求**(移植自旧项目 `app/main.py` 的 `render_prompt`)。用户话术框只写创意部分,结构性要求由程序统一拼接,保证每张图都带:
```
批量生成输出要求:
- 参考解析度:{resolution} ← 动态,取「分辨率」设置(512/1K/2K/4K)
- 固定 1:1 正方形主图
- 必须结合商品标题与参考商品图片
- 服装本身、版型、颜色与图案不可跑版
```
- 仅「参考解析度」动态(= 分辨率设置),其余 3 行固定;**简体文案**(与软件一致)。
- **始终自动附加**(同旧项目,不做开关)。
- 由 `core/ai_outfit.render_prompt(template, task, resolution)` 实现:`resolution` 为空时不附加(保持纯函数单测兼容);`generate_outfit_image` 生成时传入当前分辨率。
- **左栏「最终提示词预览」必须包含这段**(带当前选中分辨率),否则预览与实际发送不一致;分辨率下拉变化时预览同步刷新。
### 7.2 话术模板(多套命名话术)
把当前"单一一份话术"升级为**多套命名话术**:下拉切换、新建、另存为、重命名、删除、保存;记住上次所选。借鉴印花页「模板」区(`template_panel.py` / `template_service.py`)的范式,但**全部自定义**(不分内置)。
**存储**(`config_service` 扩展)
- `~/.cmbot/config/outfit_prompts.json` = `[{ "name", "text" }, …]`,**始终 ≥1 套**。
- 当前选中**名字**存进 `app_config.json` 新键 `outfit_prompt_name`(启动恢复;名字失效则回退第一套)。
- **迁移**:首次无 `outfit_prompts.json` 时——有旧 `outfit_prompt.txt` 就转成一套「默认」,否则用 `DEFAULT_OUTFIT_PROMPT` 建「默认」。
- 新函数 `load_outfit_prompts()` / `save_outfit_prompts(list)`(沿用 utf-8-sig 读、无 BOM 写、损坏回退「默认」那套的约定)。
**界面**(「穿搭生成话术」组,套印花页范式)
- 编辑框**上方一行**:「穿搭生成话术」标签 + 模板下拉;下一行放「新建」「另存为」「重命名」「删除」。
- 编辑框**下方**:保留「插入标题」「保存」。
- 左栏约 360px,5 个模板按钮偏多 → 实现时可排两行或用紧凑小按钮。
**行为**
- **切换 / 重命名 / 删除 / 新建 / 另存为之前**:若编辑框与当前套已存文本不同(脏)→ 弹「是否保存当前修改?」(保存 / 不保存 / 取消;取消则下拉还原到原选项)。
- 保存=覆盖当前套;另存为=存为新名并选中;重命名=改当前套名;删除=二次确认,删后选邻近,**不可删到 0**。
- 名字**唯一**(重名拒绝或自动加序号)。
- 记住上次所选;生成/预览用编辑框当前文本(`render_prompt` + §7.1 尾巴不变);「开始生成」前把当前编辑存回所选套(沿用现有自动存逻辑)。
**解耦**:核心 `render_prompt(text, task, resolution)` 不动;"用哪段文本"由面板的模板选择决定。
## 8. 并发、限速、重试、停止
复用旧项目策略(`docs/旧ai穿搭项目.md` §5.3):
- **并发由 Python 线程负责,PySide6 不参与**:`requests` 在等网络响应时释放 GIL,所以 `ThreadPoolExecutor` 起多线程跑行任务时多个 HTTP 请求是真并发。Qt 只负责把进度/结果通过 **signal 跨线程排队回主线程**刷新 UI——**子线程绝不直接操作控件**(违反会崩溃/随机出错)。默认并发 1 是迁就中转 API 限流(429),非框架限制,可在界面调高。
- `ThreadPoolExecutor(max_workers=并发数)` 调度行任务;`RateLimiter(新请求间隔)` 限制请求开始间隔;任务间 `单任务冷却`。
- 每行最多「首次 + 重试次数」尝试;限流/429 用短阶梯等待,普通错误短等待。
- 单次请求放子线程 + 主线程秒级检查,**等待 >30 秒持续打心跳日志**;超时按分辨率动态决定(512/1K/2K/4K → 180/240/360/600 秒,可被 `timeout_seconds` 覆盖)。
- **温和停止**:置位停止后不再提交新任务,已发请求收尾后正常写回。
## 9. 输出
- 格式 JPG,1:1,压缩到 ≤2MB;质量三档(小文件 75 / 均衡 85 / 高清 92)。
- 默认输出目录沿用 cmbot `get_output_dir()`(程序旁的「合并后的图片」,见 `docs/10` §5),界面可改。
- 命名:`货号.jpg`,重名自动 `_1`/`_2`,非法字符替换为 `_`(不改 Excel 原始货号)。
- 成功后把**实际新图绝对路径**写回 Excel D 列。
## 10. 界面(「2 AI 穿搭」页签)
界面效果图见 **`docs/ui-ai-outfit.html`**(浏览器打开)/ **`docs/ui-ai-outfit.png`**,完全沿用 cmbot 现有视觉、与「1 添加印花」严格统一(同 `docs/ui-v1`:浅灰底、`#0067c0` 蓝单一主色、12px 雅黑、3px 圆角、pill 状态徽章;不引入第二识别色)。「生成中」状态用蓝,与印花页「导出中」同色。把主窗口当前禁用的「2 AI 穿搭」页签启用,做成独立工作页(与「1 添加印花」并列、互不干扰)。
三栏布局:
- **左栏(数据源 + 话术创作,~360px)**:`Excel` / `输出` 各为**行内一行**(标签 + 路径 + 浏览,省纵向空间);数据源概览(共 N 行 / 完成 / 待处理 / 失败);**加大的**穿搭生成话术编辑 +「保存话术」+「插入标题」;下方常驻**加大的最终生成要求预览**(内嵌、实时,标题行右侧放样本行下拉,含 §7.1 自动附加的输出要求)。AI 模型下拉不在此(见右栏)。
- **中栏(结果 + 明细)**:顶部「最近结果」缩略图条——**只展示已完成的人物效果图**,新图自动加到最左、首图标「最新」,单击看大图、右键开所在文件夹;下方「处理明细」表(行 / 标题 / 货号 / 衣服图 / 状态 / 结果或原因),按 Excel 行顺序,状态用 完成 / 失败 / 生成中 / 待处理 / 跳过 徽章。
- **右栏(设置 + 运行,~400px)**:生成设置(重试失败行 + 并发数 / 新请求间隔 / 单任务冷却 / 失败重试 / 分辨率 / JPG 质量,**3 列 × 2 行**);其下 **AI 模型下拉**(来自 AI 模型配置);本次进度 + 统计(完成 / 失败 / 待处理);开始生成 / 停止生成;导出失败清单 / 打开输出目录;实时日志(含 §8 心跳行)。
> **设计取舍:不做"实时单图大预览"。** 这是"开了走人、回头抽查"的批量工具;单图实时预览在并发时会产生"该显示哪一行"的歧义。改为「最近结果缩略图条」——只展示已落地成品,既保留"早发现话术/模型不对、及时停掉改话术"的价值,又因只显示成品而消除并发歧义。
- 后台用 `QThread` + `Worker(QObject)` + 信号回主线程(与 cmbot 更新检查/导出一致;并发细节见 §8)。
> 页签内容切换:当前页签栏不切换内容面板(见 `docs/07` §4.2)。启用 AI 穿搭需要为页签接一个 `QStackedWidget`(「1 添加印花」=现有工作区,「2 AI 穿搭」=本面板);这部分在实现阶段一并补。
### 10.1 已知问题:左栏内容被中间面板裁掉(下拉框撑宽)
**现象**:「2 AI 穿搭」左栏组件右侧显示不完整,像被中间面板遮挡了一部分。
**定位(离屏实测 1280×720)**:
- 左栏是 `QScrollArea`(`ai_outfit_panel.py` `_build_left`),`widgetResizable(True)` 且**横向滚动条 `AlwaysOff`**。其内容的**最小宽度高达 829px**,而视口仅 ~428px;既不能再缩、又没有横向滚动条,于是内容按 829px 摆放、超出 ~428px 的右侧被裁掉,看起来就是"被中间盖住"。
- 829px 由**单个控件**撑出:**「样本行」`QComboBox`** 的 `minimumSizeHint ≈ 743px`,因为它当前项是长字符串「第 N 行 · 货号 · 标题」。这把「最终提示词预览」分组撑到 ~805px 最小宽,主导了整列。**「AI 模型」下拉**有同样隐患(选到长模型名时一样会撑宽)。
- 纵向正常(内容 666 vs 视口 664)——纯横向裁切,与"占位太多"无关。
**根因**:`QComboBox` 默认会让 `minimumSizeHint` 随当前/最长项文本增长;长文本下拉框把列撑宽,而"无横向滚动"的滚动区只能裁切。
**修复方向**:
- 让两个下拉框不再决定列宽:`setSizeAdjustPolicy(AdjustToMinimumContentsLengthWithIcon)` + `setMinimumContentsLength(6)` + `setSizePolicy(Ignored, Preferred)`,长项改为省略号(elide)而非撑宽。
- 兜底:左栏滚动区横向策略由 `AlwaysOff` 改 `AsNeeded`,将来再有宽控件就滚动而非裁切。
- 可选:`_build_ui` 的 `setSizes([430,760,300])`(和=1490)大于默认窗口 1280,首屏即被压缩;改成和≤窗口的一组(如 `[400,600,300]`),非根因。
### 10.2 切换分辨率 / AI 模型时的信息提示
用户**手动切换**「分辨率」或「AI 模型」下拉时,弹一个**信息框**告知影响——**只告知、不拦截、不还原**(保持新选项)。
- **触发**:用 `QComboBox.activated` 信号(只在用户点选时发),**不用** `currentIndexChanged`——否则启动 / `apply_config` / `_set_combo` / `_fill_sample_combo` 等**程序化赋值**会误弹。仅在**值实际改变**时弹(同项重选不弹)。
- **形式**:`QMessageBox.information`(只有「确定」),不阻断、不回退。预览刷新逻辑不变(分辨率仍照常实时刷预览)。
- **文案**:
- 分辨率(超时取 `ai_image_service.resolution_timeout`:512→180 / 1K→240 / 2K→360 / 4K→600 秒):
> 已切换分辨率到 **4K**。单任务超时约 600 秒,分辨率越高越慢。仅在下次「开始生成」生效,不影响正在进行的批次。
- AI 模型(带该模型 `api_type`):
> 已切换模型到 **GPT Image 2**。调用方式:images_edits。不同模型的计费与效果可能不同。仅在下次「开始生成」生效。
- **频率**:每次实际更换都弹一次(不是整会话只弹一次)。
- **理由**:下拉本可逆,不做强制确认(反模式);但分辨率/模型有"慢 / 贵 / 下次才生效"的隐含代价,一次性告知最划算。
## 11. 配置与数据位置
遵循 cmbot「配置集中、放数据目录、凭据不入库」约定:
- **AI 模型与密钥** → `~/.cmbot/config/ai_models.json`(管理员预置或界面填写,**含明文 key、不提交 git**;与更新源凭据同等对待,见 `docs/10` §14)。
- **提示词** → `~/.cmbot/config/outfit_prompt.txt`。
- **批量设置 + 上次 Excel/输出路径** → 并入 `app_config.json`(`config_service` 集中读写,UI 不直接读写配置文件,遵守 `docs/04` 第 6 节 / `docs/05` 4.12)。
- 失败记录 / 日志沿用 `~/.cmbot/logs` 与现有日志服务。
## 12. 依赖与兼容
- 新增依赖:`requests`(HTTP)、`openpyxl`(Excel);`Pillow`、`PySide6` 已有。写入 `requirements.txt` 并锁版本(兼容 Python 3.7:`requests>=2.31,<3`、`openpyxl>=3.1,<4`、`urllib3<2`)。
- 所有新代码保持 **Python 3.7.9** 兼容(注意类型注解写法)。
- `docs/03-technical-stack.md` 增列上述依赖;`docs/02-prd.md`、`docs/07-ui-design.md` §4.2 加指针指向本文档。
## 13. 安全考量
- AI 中转 API key 为明文,仅放 `~/.cmbot`,**不入库、不随包分发到公网**;内部使用可接受(与更新凭据一致)。
- 中转 API 会 429 限流:并发默认 1,阶梯重试。
- 衣服图为本机路径;不存在/打不开/非图片 → 该行失败并记录,继续下一行。
## 14. 实现阶段建议
分步落地,每步可独立验证:
1. **核心与服务(无 GUI,可测)**:`core.models.OutfitTask/OutfitResult`、`services/excel_service.py`(读/写回/占用检测)、`services/ai_image_service.py`(移植 ImageApiClient)、`core/ai_outfit.py`(单行编排)+ 单测(Excel 读写、提示词渲染、取图、命名去重;API 用 mock)。
2. **批量编排**:并发/限速/重试/停止的 `Worker`,纯逻辑部分尽量可测。
3. **UI 页签**:页签 `QStackedWidget` + `ai_outfit_panel`,接线后台线程、日志、进度、失败清单。
4. **配置与提示词**:`ai_models.json` / `outfit_prompt.txt` / `app_config.json` 接入,集中读写。
5. **真机联调**:用真实中转 API + 小批量 Excel 跑通端到端,调穿搭提示词。
## 15. 暂不做
- 文件夹/队列作为输入源(保留扩展位,本期不做)。
- 模特/姿态/背景的可视化精细编辑。
- 多 Excel / 多工作表批处理、列映射可配置化。
- 生成结果的应用内预览画廊(先以写回 Excel + 输出目录为准)。
## 16. 验收要点
- 选 Excel + 输出目录并记住上次;能读/存提示词。
- 能校验 AI 配置缺失;缺 `url/model/api_key` 时阻止开始并提示。
- 按行处理:跳过「完成」、按设置重试「失败」、空字段行安全跳过。
- 衣服图 + 提示词 → 生成人物上身 JPG(1:1、≤2MB、命名去重)。
- 每行实时写回 D/E/F 并保存 Excel;Excel 被占用时明确提示。
- 并发/限速/重试/温和停止生效;实时日志 + 进度 + 结束摘要 + 失败清单导出。
- 日志写入 `~/.cmbot/logs`;AI 密钥不入库。
- 核心逻辑单测通过、不依赖 GUI、Python 3.7 可运行。