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

29 KiB
Raw Blame History

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=失败 的行由界面「重试失败行」决定(勾选则处理「空白 + 失败」,否则只处理「空白」)。
  • 行有效性只看「标题 + 衣服图路径」两者非空;货号(B 列)可空——它不进提示词,仅用于单文件行的输出命名(见 §9),为空时命名回退用源图名,因此不应再因缺货号而跳过整行。标题或衣服图为空 → 跳过该行,不写状态、不中断。
  • 字段按原样读取,不清理空格、不回写清理值。
  • 开始前检测 Excel 是否被占用(Office 打开会锁文件,openpyxl 无法写回)→ 提示「请关闭 Excel 后再开始」。
  • 每处理完一行即保存 Excel(降低崩溃丢结果风险)。
  • 成功 → D=新图绝对路径、E=完成;失败 → E=失败、F=原因(同步写日志)。

4.1 C 列为「图片目录」(多图扇出,§9.1)

C 列除了单张图片文件,也可以是一个目录(如 d:/images/a/)。约定:

  • 判定:os.path.isdir(C) 为真,或路径以分隔符结尾。仅取该目录顶层的图片 (扩展名 .png/.jpg/.jpeg/.webp/.gif),不递归子目录,按文件名排序。
  • 该行仍是一个任务、一次回写;对目录内每一张图各生成一张穿搭图(N→N), 共用本行的标题/货号与话术。
  • 输出落到 输出目录/<目录叶子名>/(见 §9.1);Excel 回写仍是整行一个状态: D=子目录绝对路径、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 时阻止开始)。

6.1 ai_models.json 出厂模板

为降低首次部署成本,发布包应在 app\config\ai_models.json 内带一份模型配置模板。 用户数据仍以 ~/.cmbot/config/ai_models.json 为准,程序不会直接把出厂模板当成用户配置使用。

播种/补种规则:

  • 首次安装:启动器可从 app\config\ai_models.json 播种到 ~/.cmbot/config/ai_models.json。
  • 自更新:不能只依赖启动器播种。启动器目前先播种旧 app\config,再应用新版 app;同时 Launcher.exe 本身不自更新,旧启动器也可能不知道 ai_models.json。因此新版 app 在启动或 load_ai_models() 时必须做运行时兜底:如果 ~/.cmbot/config/ai_models.json 不存在,且当前 app\config\ai_models.json 存在,则复制一次。
  • 任何播种/补种都不得覆盖用户已有 ~/.cmbot/config/ai_models.json。

模板格式采用当前程序可直接加载的结构:

{
  "models": [
    {
      "name": "GPT Image 2",
      "url": "https://api.vectorengine.ai/v1/images/edits",
      "model": "gpt-image-2",
      "api_key": "",
      "api_type": "images_edits",
      "timeout_seconds": 0,
      "connect_timeout_seconds": 30,
      "extra_body": {}
    },
    {
      "name": "Nano Banana 2",
      "url": "https://api.vectorengine.ai/v1/chat/completions",
      "model": "gemini-3.1-flash-image-preview",
      "api_key": "",
      "api_type": "auto",
      "timeout_seconds": 0,
      "connect_timeout_seconds": 30,
      "extra_body": {}
    }
  ]
}

规则:

  • name 是界面下拉框显示值,也是 app_config.json 中 outfit_model 的持久化值。
  • timeout_seconds=0 表示按分辨率自动取超时(见 §8),connect_timeout_seconds=30 只控制连接阶段。
  • 出厂模板不得提交真实 api_key;管理员在目标机器的 ~/.cmbot/config/ai_models.json 中填写真实 key。
  • 旧项目的 api_config.json 如为 {last_selected_model, models:{id:{display_name,...}}} 结构,迁移时把 models 对象转成列表,并把 display_name 映射为 name。
  • 当前旧配置里的 last_selected_model=nano_banana_2 对应 outfit_model="Nano Banana 2"。

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 的全部有效数据行(标题/货号/衣服图齐全,不看 E 列完成/失败状态),因此整表生成完成后仍能预览(见 §10.3);默认选第一行。未选 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 原始货号)。货号为空时命名回退用源图名(<源图名>.jpg,与目录行一致)。
  • 成功后把实际新图绝对路径写回 Excel D 列。

9.1 目录行的输出(多图 → 同名子目录)

当 C 列是目录(§4.1)时:

  • 每张源图各生成一张穿搭图,存到 输出目录/<目录叶子名>/<源图名>.jpg (子目录名 = 目录叶子名,文件名沿用源图名;二者均按 §9 规则替换非法字符)。 例:d:/images/a/img1.png → 输出目录/a/img1.jpg。
  • 不加 _1/_2 去重后缀:目标文件已存在视为「已生成」并跳过,使整行可 幂等重试——重试只补做缺失/失败的那几张,已成功的不重复调 API。
  • 整行回写 Excel 时,D 列写子目录绝对路径(而非单个文件)。
  • 目录内逐张顺序生成(同一 worker 内);为避免压垮中转 API,逐张之间按 「新请求间隔」本地 sleep 节流。并发=1(默认)时即等于全局节流;并发>1 时为近似。 进度条按 Excel 行前进(一个目录=1 格),逐张进度通过实时日志反馈。

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 模型 标签与下拉框同排;本次进度 + 统计(完成 / 失败 / 待处理);开始生成 / 停止生成同排;导出失败清单 / 打开输出目录同排;节省的纵向空间给实时日志(含 §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。不同模型的计费与效果可能不同。仅在下次「开始生成」生效。

  • 频率:每次实际更换都弹一次(不是整会话只弹一次)。
  • 理由:下拉本可逆,不做强制确认(反模式);但分辨率/模型有"慢 / 贵 / 下次才生效"的隐含代价,一次性告知最划算。

10.3 已知问题:全表生成完成后,预览样本行下拉为空

现象:选中的 Excel 整表生成成功后,再次选中该文件,「最终生成要求预览」的样本行下拉只显示「(选 Excel 后显示替换效果)」,无可选行。

原因:预览样本行用 _reload_sample_rows() 填,调的是给生成用的 load_outfit_tasks()——它按设计跳过 E=「完成」的行(失败行也只在勾「重试失败行」时返回)。整表都「完成」→ 返回空 → 下拉空 → 占位文案。预览本只是"拿一行真实数据看话术替换效果",与"这行要不要重新生成"无关,不该受状态过滤。

修复方向:

  • excel_service 新增 read_all_rows(excel):返回每一有效数据行(标题/货号/衣服图齐全)对应的 OutfitTask,忽略 E 列状态。
  • _reload_sample_rows() 改调 read_all_rows(预览专用);生成仍走 load_outfit_tasks(待处理过滤不变)。运行时填下拉的 _on_tasks_loaded 仍用实际任务。
  • 补 read_all_rows 单测(含「完成」行也返回、空字段行跳过)。

10.4 改进:整表无待处理行时的提示 + 生成不清空预览样本

承接 §10.3。整表都「完成」后点「开始生成」时当前体验不清晰,两项改进:

改进 1 — 无待处理行时给明确提示。 现状:整表都「完成」(或都失败但未勾「重试失败行」)→ load_outfit_tasks 返回空 → 仅日志「已加载 0 行待处理任务」+ 结束弹窗「完成 0,失败 0」,用户看不懂为何没出图。 改为:检测到 0 行待处理时,明确提示——例如「该表没有待处理的行:已完成 N 行会跳过;失败 M 行可勾『重试失败行』重做;要重做已完成行,请清空对应行的状态(E)列。」(N/M 可由 read_all_rows 的各行状态统计得到。)在 tasks_loaded 为空或 _on_finished(total==0) 时弹 QMessageBox.information。

改进 2 — 生成不清空预览样本行。 现状:_on_tasks_loaded 里调 _fill_sample_combo(tasks),空任务时把预览下拉清成占位文案,破坏 §10.3 的修复。 改为:_on_tasks_loaded 不再动预览样本下拉;预览样本行只由 read_all_rows(_reload_sample_rows)维护,与运行解耦。

10.5 按钮配色(对齐「添加印花」)

现状:AI 穿搭面板没有 stylesheet,按钮全是系统灰。复用印花页各 widget 已有的按钮规范,让两页一致。

三类按钮(取自印花页 QSS):

  • 主操作(蓝填充) = #openFolderBtn / #queueBatchBtn:color:#fff; font-weight:bold; border:none; border-radius:3px; background:#0078d4; :hover #106ebe; :pressed #005a9e。
  • 次级(灰描边) = #queueActionBtn / #templateBtn:border:1px solid #d6d6d6; border-radius:3px; background:#f0f0f0; :hover #e0e0e0; :pressed #d0d0d0; :disabled color #aaa, bg #f7f7f7。
  • 危险(安静红) = #templateDeleteBtn:次级底 + color:#c42b1c; :hover bg #fde7e9 border #c42b1c; :pressed #f9d4d7。

映射:

按钮 配色
开始生成 主操作蓝(运行中禁用 → 灰)
停止生成 危险安静红(空闲禁用 → 灰)
话术「删除」 危险安静红
浏览…(×2) / 新建 / 另存为 / 重命名 / 插入标题 / 保存话术 / 导出失败清单 / 打开输出目录 次级灰

原则:蓝色只留给唯一主动作(开始生成,对齐「开始批量导出 / 打开文件夹」),红色标停止/删除(对齐「删除」),其余用印花页通用的次级灰。

实现:面板 setObjectName("aiOutfitPanel") + _apply_styles(),QSS 全部以 #aiOutfitPanel 前缀限定作用域(避免波及 QMessageBox/QInputDialog 等弹窗按钮);开始/停止/话术删除三个按钮各设 objectName(aiStartBtn / aiStopBtn / aiPromptDeleteBtn),其余走面板内 QPushButton 默认次级样式。纯样式改动,不动行为。

11. 配置与数据位置

遵循 cmbot「配置集中、放数据目录、凭据不入库」约定:

  • AI 模型与密钥 → ~/.cmbot/config/ai_models.json(管理员预置或界面填写,含明文 key、不提交 git;与更新源凭据同等对待,见 docs/10 §14)。
  • 发布包可附带 app\config\ai_models.json 出厂模板(见 §6.1),用于首次播种;模板中的 api_key 必须为空或占位,真实 key 只写入用户数据目录。
  • 提示词 → ~/.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 可运行。