Files
cmbot/tasks.md
T
adminandClaude Opus 4.8 d962b60816 feat(ai-outfit): clear 'no pending rows' message + keep preview on generate (§19.10)
- _on_finished(total==0) now shows _show_no_pending_message: counts 完成/失败
  via read_all_rows and tells the user how to redo (clear E column / retry
  failed), instead of the silent '完成 0,失败 0'.
- _on_tasks_loaded no longer calls _fill_sample_combo(tasks), so generating on
  an all-完成 sheet no longer empties the preview dropdown (read_all_rows owns it).
- Offscreen-verified both paths; full suite (12 files) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:17:43 +08:00

1137 lines
49 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 或开发者领取任务前,必须先阅读对应任务的前置文档,并严格遵守 `docs/04-development-rules.md`。
任务状态说明:
- `[x]` 已完成。
- `[ ]` 未开始。
- `[~]` 进行中或部分完成。
## 任务执行顺序
任务编号即建议开发顺序,后续任务依赖前序任务的产出:
```
1(基础服务)→ 2(数据模型)→ 3/4(文件/模板服务)
→ 5(图片合成核心)→ 6(批量任务核心)→ 6.5(低对比组合筛选核心)→ 7(主界面布局)→ 8-13(各 UI 面板)→ 14(微调状态)→ 15(测试)→ 16(打包)
```
任务 1-6 完成前不应开始任务 7 及以后的 UI 任务,否则 UI 层将缺少可依赖的数据模型和服务层。
执行每个「完善 xxx.py」类任务前,必须先读取该文件的当前内容,了解骨架代码现状,再决定新增或修改哪些内容,避免覆盖已有实现。
## 0. 已完成基础工作
- [x] 编写项目愿景文档:`docs/01-product-vision.md`
- [x] 编写 PRD:`docs/02-prd.md`
- [x] 编写技术栈文档:`docs/03-technical-stack.md`
- [x] 编写开发规则文档:`docs/04-development-rules.md`
- [x] 编写项目架构文档:`docs/05-project-architecture.md`
- [x] 编写 UI 效果图提示词:`docs/06-ui-mockup-prompt.md`
- [x] 整理 Claude UI 提示词版本:`docs/06-ui-mockup-prompt-claude.md`
- [x] 保存 UI 效果图设计稿:`docs/ui-v1.html`
- [x] 保存 UI 效果图截图:`docs/ui-v1.png`
- [x] 编写 UI 设计文档:`docs/07-ui-design.md`
- [x] 编写图像编辑器设计文档:`docs/08-image-editor-design.md`
- [x] 编写本地打包发布文档:`docs/09-packaging-release.md`
- [x] 创建 `requirements.txt`
- [x] 创建 `src/version.py`,统一维护 `APP_NAME` 和 `APP_VERSION`
- [x] 创建 `src/main.py` 最小 PySide6 入口
- [x] 创建 `src/app/main_window.py` 最小主窗口
- [x] 创建 `src/app/widgets/` 占位模块
- [x] 创建 `src/core/` 占位模块
- [x] 创建 `src/services/` 占位模块
- [x] 创建 `src/resources/` 资源目录
- [x] 创建 `tests/` 测试目录
- [x] 验证本机 Python 版本为 `3.7.9`
- [x] 验证 PySide6 版本为 `6.5.3`
- [x] 通过 `python -m py_compile` 语法检查
- [x] 验证最小 PySide6 窗口可以创建并关闭
- [x] 已提交文档规划代码
- [x] 已提交 PySide6 工程骨架代码
## 1. 工程基础完善
### 1.1 日志服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/services/log_service.py` 现有内容
- [x] 完善 `src/services/log_service.py`
- [x] 启动时创建 `logs/` 目录
- [x] 日志文件按启动时间或日期命名
- [x] 日志格式包含时间、模块、级别、消息
- [x] 在 `src/main.py` 中初始化日志
验收:
- [x] 启动程序后生成日志文件
- [x] 程序启动信息写入日志
- [x] 不使用 `print` 作为正式日志
### 1.2 路径与资源服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/09-packaging-release.md`
任务:
- [x] 在 `src/services/file_service.py` 中新增路径辅助函数(不单独建模块)
- [x] 实现 `get_app_dir()`:返回程序根目录,兼容开发环境和 PyInstaller 打包环境
- [x] 实现 `get_resource_path(relative_path)`:返回资源文件绝对路径
- [x] 实现 `get_config_path(relative_path)`:返回配置文件绝对路径
- [x] 实现 `get_log_dir()`:返回日志目录路径
- [x] 实现 `get_output_dir()`:返回默认输出目录路径
- [x] 保证 Windows 中文路径可用
验收:
- [x] 路径函数不使用开发机绝对路径
- [x] 程序目录下缺少 `logs/` 或 `output/` 时可自动创建
### 1.3 配置服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/services/config_service.py` 现有内容
- [x] 完善 `src/services/config_service.py`
- [x] 定义默认配置
- [x] 支持读取 JSON 配置
- [x] 支持保存 JSON 配置
- [x] 配置损坏时使用安全默认值并记录日志
验收:
- [x] 缺少配置文件时程序可启动
- [x] 配置文件损坏时程序可启动并记录日志
- [x] 不会无提示清空用户配置
## 2. 核心数据模型
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/core/models.py` 现有内容
- [x] 完善 `src/core/models.py`
- [x] 定义 `ImageAsset`
- [x] 定义 `TransformState`
- [x] 定义 `Template`
- [x] 定义 `ExportOptions`
- [x] 定义 `BatchOptions`
- [x] 定义 `ComposeResult`
- [x] 定义 `BatchResult`
验收:
- [x] `models.py` 不依赖 PySide6 UI 控件
- [x] 变换状态包含 `x/y/width/height/rotation/keep_aspect_ratio`
- [x] 模型可以被 core、services 和 UI 层复用
## 3. 文件扫描与素材导入
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/services/file_service.py` 现有内容
- [x] 完善 `src/services/file_service.py`
- [x] 支持递归扫描衣服图片文件夹
- [x] 支持递归扫描印花图片文件夹
- [x] 支持格式:PNG、JPG、JPEG、WEBP
- [x] 忽略不支持格式并记录日志
- [x] 保留来源子文件夹信息
- [x] 生成安全输出文件名
验收:
- [x] 可以扫描包含子文件夹的中文路径
- [x] 不修改原始素材文件
- [x] 不支持文件格式不会导致扫描失败
## 4. 模板服务
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/services/template_service.py` 现有内容
- [x] 完善 `src/services/template_service.py`
- [x] 定义内置模板
- [x] 支持读取自定义模板 JSON
- [x] 支持保存自定义模板 JSON
- [x] 支持新增、重命名、删除自定义模板
- [x] 校验模板字段
验收:
- [x] 内置模板和自定义模板可区分
- [x] 单个模板损坏不影响全部模板加载
- [x] UI 不直接操作模板 JSON 文件
## 5. 图片合成核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/core/composer.py` 现有内容
- [x] 完善 `src/core/composer.py`
- [x] 使用 Pillow 读取衣服底图和印花图
- [x] 支持透明 PNG alpha 合成
- [x] 支持按 `TransformState` 缩放印花
- [x] 支持按 `TransformState` 旋转印花
- [x] 支持旋转后按中心点对齐粘贴
- [x] 支持 PNG 导出
- [x] 支持 JPG 导出
- [x] 输出文件名避免默认覆盖
验收:
- [x] 合成逻辑不依赖 PySide6 UI 控件
- [x] 输出尺寸默认与衣服底图一致
- [x] 透明 PNG 不出现黑底或白底
- [x] 印花部分超出画布时不会报错
## 6. 批量任务核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
- [x] 先读取 `src/core/batch.py` 现有内容
- [x] 完善 `src/core/batch.py`
- [x] 支持多衣服 × 单印花
- [x] 支持单衣服 × 多印花
- [x] 支持一一匹配
- [x] 支持全组合
- [x] 复用 `core/composer.py`
- [x] 单个任务失败时继续处理剩余任务
- [x] 汇总成功数量、失败数量和失败原因
验收:
- [x] 不复制第二套合成算法
- [x] 单张失败不会中断整个批量任务
- [x] 失败原因可供 UI 和日志使用
## 6.5 低对比组合筛选核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 定义可见度状态:`正常`、`偏低`、`不明显`、`无法判断`
- [x] 新增低对比分析核心函数,位置应符合架构分层,不能写进 UI 事件
- [x] 基于当前模板或 `TransformState` 获取衣服目标区域
- [x] 只分析衣服目标区域颜色,不使用整张衣服图判断
- [x] 只统计印花 alpha 有效像素,例如 `alpha > 20`
- [x] 计算 RGB 颜色距离
- [x] 计算亮度差
- [x] 根据阈值输出可见度状态和评分
- [x] 单个组合分析失败时返回 `无法判断`,不得中断整个队列
- [x] 为后续队列项保存可见度结果预留字段或结果结构
验收:
- [x] 白色衣服配浅色印花时可标记为 `偏低` 或 `不明显`
- [x] 深色衣服配深色印花时可标记为 `偏低` 或 `不明显`
- [x] 透明 PNG 的透明区域不参与印花颜色判断
- [x] 低对比分析不修改原始衣服图片和原始印花图片
- [x] 分析逻辑不依赖 PySide6 UI 控件
- [x] 失败项不会中断批量队列生成或导出流程
## 7. 主界面布局
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/app/main_window.py` 现有内容
- [x] 将 `src/app/main_window.py` 从最小窗口扩展为主布局
- [x] ~~实现顶部标题和版本号~~(后移除内容区自绘标题栏:与系统窗口标题重复,名称/版本改由系统标题栏显示,见 17.7)
- [x] 实现流程页签区域
- [x] 实现左侧素材栏容器
- [x] 实现中间预览区容器
- [x] 实现右侧参数栏容器
- [x] 实现底部合成队列容器
- [x] 实现底部状态栏
验收:
- [x] 窗口标题显示 `APP_NAME` 和 `APP_VERSION`
- [x] UI 布局接近 `docs/ui-v1.png`
- [x] 不把所有业务逻辑塞进 `main_window.py`
## 8. 素材列表 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/app/widgets/image_list_panel.py` 现有内容
- [x] 实现衣服图片列表
- [x] 实现印花图片列表
- [x] 实现打开文件夹按钮
- [x] 实现全选和取消全选
- [x] 实现单张勾选和取消勾选
- [x] 显示已选数量和总数量(例如:`含子文件夹 · 共 12`)
- [x] 显示来源子文件夹标签和文件名
- [x] 实现排序选择器(默认按文件夹+名称排序)
- [x] 点击素材后更新当前预览选择
验收:
- [x] 支持递归导入素材
- [x] 当前预览选中态和批量勾选态可区分
- [x] 不在 UI 层重复实现文件扫描规则
## 9. 图像预览编辑器
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/app/widgets/image_canvas.py` 现有内容
- [x] 使用 `QGraphicsView / QGraphicsScene`
- [x] 显示衣服底图(scene 坐标与原图像素坐标保持一致)
- [x] 显示印花图层
- [x] 实现印花拖动
- [x] 实现缩放控制点(至少四角)
- [x] 实现旋转控制点(顶部中心外侧)
- [x] 实现预览缩放(通过 `QGraphicsView` view transform,不修改 scene 内容)
- [x] 实现预览顶部栏:显示当前底图文件名、印花文件名、操作模式按钮(移动/缩放/旋转)、缩放比例控制
- [x] 实现画布操作提示 Overlay(拖动移动 / 四角缩放 / 顶部旋转 / Shift 锁比例)
- [x] 将交互结果同步为 `TransformState`
验收:
- [x] 预览缩放不改变真实合成参数
- [x] 拖动后坐标为衣服原图像素坐标
- [x] 缩放后宽高同步
- [x] 旋转后角度同步
- [x] 不使用截图作为导出结果
## 10. 参数面板 UI
前置阅读:
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/app/widgets/transform_panel.py` 现有内容
- [x] 完善 `src/app/widgets/transform_panel.py`
- [x] 实现 X/Y 坐标输入
- [x] 实现宽度/高度输入
- [x] 实现锁定宽高比例
- [x] 实现角度输入
- [x] 实现左旋 90 度和右旋 90 度按钮
- [x] 与 `ImageCanvas` 双向同步 `TransformState`
验收:
- [x] 参数输入后预览同步更新
- [x] 预览交互后参数输入框同步更新
- [x] 程序化更新输入框不会造成信号循环
## 11. 模板面板 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 先读取 `src/app/widgets/template_panel.py` 现有内容
- [x] 完善 `src/app/widgets/template_panel.py`
- [x] 显示模板下拉框
- [x] 支持选择模板
- [x] 支持保存模板
- [x] 支持另存为模板
- [x] 支持重置为模板
- [x] 通过 `template_service` 操作模板
验收:
- [x] 选择模板后更新预览和参数面板
- [x] UI 不直接修改模板 JSON
- [x] 自定义模板重启后仍可用
## 12. 导出面板 UI
前置阅读:
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/09-packaging-release.md`
任务:
- [x] 先读取 `src/app/widgets/export_panel.py` 现有内容
- [x] 完善 `src/app/widgets/export_panel.py`
- [x] 实现输出目录选择
- [x] 实现输出格式选择
- [x] 实现质量选择
- [x] 实现导出当前单张
- [x] ~~实现应用为模板入口~~(后移除:与模板区域「另存为」重复,见 17.3)
- [x] ~~实现重置为模板入口~~(后移除:与模板区域「重置为模板」重复,见 17.3)
- [x] 显示当前操作影响范围提示
验收:
- [x] 缺少衣服或印花时导出按钮禁用
- [x] 输出目录不可写时提示用户并记录日志
- [x] 单张导出调用 `core/composer.py`
## 13. 合成队列 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
- [x] 实现底部合成队列表格(建议使用 `QTableView` + 模型视图结构)
- [x] 实现队列头部批量模式切换:多衣服×单印花 / 单衣服×多印花 / 一一匹配 / 全组合(矩阵)
- [x] 实现队列统计显示(共 N 项 · 完成 · 进行 · 失败 · 待导出)
- [x] 实现「重置全部」按钮
- [x] 实现「导出选中」按钮
- [x] 实现「开始批量导出」按钮(导出中切换为停止/暂停)
- [x] 实现队列折叠/展开
- [x] 显示任务状态(已完成 / 待导出 / 导出中 / 失败)
- [x] 导出中任务显示进度条
- [x] 显示失败原因
- [x] 支持选中队列项并更新预览区和参数面板
验收:
- [x] 队列统计显示总数、完成、进行、失败、待导出
- [x] 导出中任务显示进度条
- [x] 失败行显示可理解原因
- [x] 选中队列项可以加载对应状态
## 14. 微调状态
前置阅读:
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 定义队列项微调状态
- [x] 手动调整队列项后标记为 `已微调`
- [x] 微调项保存独立 `TransformState`
- [x] 批量导出时微调项使用自己的参数
- [x] 未微调项使用模板参数
验收:
- [x] 已微调项不会被无提示覆盖
- [x] 队列表格能显示 `模板` 和 `已微调`
## 15. 测试
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
- [x] 编写 `tests/test_composer.py`
- [x] 编写 `tests/test_templates.py`
- [x] 测试透明 PNG 合成
- [x] 测试缩放参数
- [x] 测试旋转中心
- [x] 测试文件扫描
- [x] 测试模板读写
- [x] 测试批量任务生成
验收:
- [x] 核心逻辑测试不依赖 GUI
- [x] 测试可在 Python 3.7 环境运行
## 16. 打包
前置阅读:
- `docs/03-technical-stack.md`
- `docs/04-development-rules.md`
- `docs/09-packaging-release.md`
任务:
- [x] 创建打包脚本
- [x] 验证 PyInstaller `onedir` 打包
- [x] 打包资源文件
- [x] 打包默认配置和模板
- [x] 验证打包后程序启动
- [x] 验证标题栏版本号
- [x] 验证单张导出
验收:
- [ ] 打包产物可在无 Python 环境的 Windows 电脑启动
- [x] 发布包不包含源代码、缓存、测试输出和私人配置
## 17. 体验优化(持久化与独立重置)
第二阶段根据实际使用反馈新增。
### 17.1 用户偏好持久化
前置阅读:
- `docs/02-prd.md`(6.6 模板选择、6.9 批量合成导出、9 数据与文件结构)
- `docs/05-project-architecture.md`(4.12 config_service)
任务:
- [x] 先读取 `src/services/config_service.py` 与 `src/app/main_window.py` 现有内容
- [x] 在 `main_window` 启动时调用 `load_config()` 并把初值分发给各面板(集中管理,不让控件各自读写)
- [x] 在 `config_service` 默认配置中新增 `last_template`、`last_batch_mode`
- [x] 记住并在启动时恢复上次选择的模板;自定义模板已删除时回退到第一个内置模板
- [x] 记住并在启动时恢复上次选择的批量模式;持久化值非法时回退默认(全组合)
- [x] 面板选择变化时「改一次存一次」回写配置
- [x] `template_panel` 增加模板选择变化信号(携带模板名)
- [x] `queue_panel` 增加 `set_batch_mode()` 与批量模式变化信号
验收:
- [x] 重启后自动恢复上次选择的模板和批量模式
- [x] 自定义模板被删除或批量模式值非法时安全回退,不报错
- [x] UI 控件不直接读写配置文件
### 17.2 位置 / 尺寸 / 旋转独立重置
前置阅读:
- `docs/07-ui-design.md`(7 右侧参数栏、10 视觉规范)
- `docs/08-image-editor-design.md`
任务:
- [x] 先读取 `src/app/widgets/transform_panel.py` 现有内容
- [x] 在 `位置` / `尺寸` / `旋转` 三个分区标题右侧各加一个独立 `重置` 按钮
- [x] 各重置只把对应参数还原为当前选中模板的值,互不影响(位置→仅 X/Y;尺寸→仅宽/高,位置不变;旋转→仅角度)
- [x] 移除合并的 `重置位置 / 尺寸 / 角度` 按钮与角度旁的 `归零` 按钮
- [x] 重置按钮采用幽灵样式(静止灰、悬停蓝、按下深蓝、禁用更灰),不使用红色
- [x] 未选模板或未加载衣服/印花时三个按钮置灰
- [x] 由 `main_window` 取模板换算值并按项合并进当前 `TransformState`
验收:
- [x] 位置、尺寸、旋转可分别独立重置,互不影响
- [x] 尺寸重置只改变宽 / 高,不改变位置 X / Y
- [x] 按钮视觉层级符合 `docs/07-ui-design.md` 第 10 节
### 17.3 按钮去重(导出面板模板入口)
前置阅读:
- `docs/07-ui-design.md`(7.1 模板区域、7.7 操作按钮)
任务:
- [x] 移除导出面板的 `应用为模板` 与 `重置为模板` 按钮(与模板区域的 `另存为` / `重置为模板` 是同一动作的重复入口)
- [x] 模板相关操作统一留在模板区域,导出面板只负责输出与导出
验收:
- [x] 同一个动作在界面上只出现一次,命名一致
- [x] 导出面板仅保留输出设置与 `导出当前单张`
### 17.4 「重置为模板」归位并更名为「位置尺寸旋转重置为当前模板」
前置阅读:
- `docs/07-ui-design.md`(7 右侧参数栏、10 视觉规范)
任务:
- [x] 把模板区域的 `重置为模板` 按钮移出,移到参数栏底部(旋转分区下方)
- [x] 更名为 `位置尺寸旋转重置为当前模板`,与三个分项 `↺ 重置` 构成「全部 + 分项」两级
- [x] 还原逻辑改为把位置/尺寸/旋转整体设为模板值,并与分项重置同走 `_apply_preview_transform`
- [x] 与三个分项重置一起受「未加载图片时置灰」控制
- [x] 采用安静、全宽、悬停出强调色的样式(不使用红色)
验收:
- [x] 模板区域只剩 `保存` / `另存为`,不含整体重置
- [x] `位置尺寸旋转重置为当前模板` 在旋转分区下方,未加载图片时置灰
- [x] 点击后位置、尺寸、旋转一并还原为当前选中模板
### 17.5 模板删除按钮
前置阅读:
- `docs/07-ui-design.md`(7.1 模板区域)
任务:
- [x] 在模板区域增加 `删除` 按钮(复用 `template_service.delete_template`)
- [x] `删除` 仅对自定义模板可用,选中内置模板时隐藏(与 `保存` 一致)
- [x] 删除前弹二次确认对话框;确认后才删除
- [x] 删除后刷新下拉框并回选第一个内置模板,同步持久化选择
- [x] `删除` 用红色文字提示危险但保持安静样式
验收:
- [x] 可在界面上删除自定义模板,无需手改 `templates.json`
- [x] 内置模板不显示 `删除`
- [x] 删除有二次确认,取消则不删
### 17.6 参数/导出面板紧凑化与命名
前置阅读:
- `docs/07-ui-design.md`(7.2 位置区域、7.3 尺寸区域、7.5 输出设置区域)
任务:
- [x] 「位置」的 X、Y 输入框放在同一行(标签简化为 `X` / `Y`)
- [x] 「尺寸」的宽、高输入框放在同一行(标签简化为 `宽` / `高`),`锁定宽高比例` 在其下方
- [x] 导出面板「格式」「质量」放在同一行
- [x] 底部整体重置按钮更名为 `位置尺寸旋转重置为当前模板`
- [x] 仅改布局/文案,不动同步与导出逻辑
验收:
- [x] X/Y 一行、宽/高一行、格式/质量一行,界面更紧凑
- [x] 拖动/缩放预览后输入框仍正常同步
- [x] 质量仍仅在 JPG 时可用
### 17.7 移除重复的自绘标题栏与无效「设置」按钮
前置阅读:
- `docs/07-ui-design.md`(3 整体布局、4.1 标题栏)
任务:
- [x] 移除标题栏里点了无反应的占位「设置」按钮
- [x] 移除内容区自绘标题栏(与系统窗口标题重复),名称/版本由系统标题栏(`setWindowTitle`)显示
- [x] 同步清理相关样式与未用常量
验收:
- [x] 界面不再出现重复的软件名称/版本号
- [x] 系统窗口标题仍显示 `APP_NAME` 与 `APP_VERSION`
- [x] 工作区因移除标题栏而获得更多纵向空间
### 17.8 加载文件夹后默认预览第一张
前置阅读:
- `docs/07-ui-design.md`(5.1 衣服图片面板)
任务:
- [x] 衣服、印花面板加载文件夹后,自动选中并预览第一张(按当前排序)
- [x] 空文件夹不选;把选中逻辑抽成按行选择以复用
- [x] 与现有「模板粘性」衔接:自动选印花后按当前模板落位
验收:
- [x] 加载文件夹后画布不再为空、无需手动点选
- [x] 两个文件夹加载顺序无关,最终都能得到合成预览
### 17.9 输出按「时间戳 → 印花」分组到子文件夹
前置阅读:
- `docs/02-prd.md`(6.8 单张合成导出、9 数据与文件结构)
任务:
- [x] `make_safe_output_path` 输出到 `<输出目录>/<印花文件名>/<衣服文件名>.<扩展名>`
- [x] 新增 `timestamped_run_dir(base)`,每次导出运行在输出目录下新建时间戳文件夹
- [x] 一次导出运行(批量/单张)共用同一个时间戳;批量在开始时计算一次
- [x] 单张导出、队列导出、`core.batch.run_batch` 均接入时间戳层
- [x] 子文件夹由 `composer.compose` 保存时自动创建;重名仍追加 `_1`/`_2`
验收:
- [x] 路径形如 `输出目录/<时间戳>/<印花文件名>/<衣服文件名>.<扩展名>`
- [x] 重复运行落在不同时间戳文件夹,互不覆盖
- [x] 一次批量运行内所有图片共用同一个时间戳文件夹
### 17.10 记住上次的衣服/印花文件夹
前置阅读:
- `docs/02-prd.md`(6.1 图片加载、9 数据与文件结构)
- `docs/05-project-architecture.md`(4.12 config_service)
任务:
- [x] `_AssetPanel` 打开对话框时定位到上次目录(`set_start_dir`),并在选中后发 `folder_opened` 信号
- [x] `ImageListPanel` 暴露 `set_garment_start_dir` / `set_print_start_dir` 与 `garment_folder_opened` / `print_folder_opened`
- [x] `main_window` 启动恢复 `last_garment_dir` / `last_print_dir`,并在打开文件夹时「改一次存一次」回写
- [x] UI 不直接读写配置,统一经主窗口
验收:
- [x] 重启后打开「打开文件夹」对话框定位到上次的衣服/印花目录
- [x] 目录已不存在时安全回退,不报错
### 17.11 修复:批量导出忽略所选输出格式/质量/目录
前置阅读:
- `docs/07-ui-design.md`(7.5 输出设置区域、8 底部合成队列)
问题:
- `queue_panel.set_export_options()` 从未被调用,导出面板也无「选项变化」信号,导致队列始终使用默认 `ExportOptions`(PNG、默认目录),批量导出无视 UI 所选格式/质量/目录。
任务:
- [x] 导出面板新增 `export_options_changed` 信号与公开 `current_options()`
- [x] 格式/质量/输出目录任一变化即发出当前 `ExportOptions`
- [x] `main_window` 接到 `queue_panel.set_export_options`,并在启动时同步一次
- [x] 单张导出 `_do_export` 改用同一 `current_options()` 来源
验收:
- [x] 选 JPG 后批量导出的文件为 `.jpg` 且按 JPEG 保存
- [x] 批量导出跟随所选输出目录与质量
### 17.12 合并图文件名加上印花名
前置阅读:
- `docs/02-prd.md`(6.8 单张合成导出、9 数据与文件结构)
任务:
- [x] `make_safe_output_path` 的文件名由 `<衣服文件名>` 改为 `<衣服文件名>_<印花文件名>`
- [x] 目录分组结构不变(仍 `输出目录/<时间戳>/<印花文件名>/`)
- [x] 重名仍追加 `_1`/`_2` 避免覆盖
验收:
- [x] 输出文件形如 `输出目录/<时间戳>/TY030/1_TY030.png`
- [x] 单张导出与批量导出一致
### 17.13 隐藏「导出当前单张」按钮
前置阅读:
- `docs/07-ui-design.md`(7.7 操作按钮、10 视觉规范)
任务:
- [x] 隐藏导出面板的 `导出当前单张` 按钮(`setVisible(False)`)
- [x] 保留按钮与 `_do_export` 逻辑,便于后续需要时再显示
- [x] 导出统一经底部合成队列完成
验收:
- [x] 导出面板不再显示 `导出当前单张`,只剩输出设置
- [x] 队列的批量/选中导出不受影响
### 17.14 修复:选中未微调队列项时预览未按模板落位
前置阅读:
- `docs/02-prd.md`(6.6 模板选择)
- `docs/07-ui-design.md`(12.4 选中队列项)
问题:
- `_on_queue_item_activated` 仅在 `item.transform` 存在(已微调)时覆盖画布变换;未微调项保留 `load_print` 的写死默认(印花≈衣服宽 40%、居中),预览尺寸与选中模板不一致——「模板粘性」在队列项激活路径的遗漏分支。
任务:
- [x] 选中队列项且 `item.transform` 为空时,调用 `template_panel.apply_current()` 按当前模板落位
- [x] 仍在 `_loading_queue_item` 保护下,避免被误标为「已微调」
- [x] 与预览流程(`_on_print_preview`)行为一致
验收:
- [x] 点击未微调队列项,预览印花为选中模板要求的大小/位置
- [x] 已微调项仍显示其自身参数,不被覆盖
### 17.15 修复:批量导出对未微调项复用单个像素变换
前置阅读:
- `docs/02-prd.md`(6.6 模板选择)
- `docs/08-image-editor-design.md`(变换状态)
问题:
- `QueuePanel` 用单个像素级 `TransformState`(`_template_transform`,来自 `template_applied`,按「当时画布那张衣服/印花尺寸」算出)作为所有未微调项的回退变换。
- `TransformState` 是「衣服原图像素坐标」,对尺寸不同的衣服/印花原样复用会错位、错尺寸(如 1000×1000 算出的 x=400/w=250 套到 2000×3000 上,印花跑到左上、尺寸只剩一半)。
- 影响批量导出与「导出选中」中所有未微调项。
任务:
- [x] 队列改存模板对象(比例坐标 `Template`)而非单个像素变换:`set_template()` 取代 `set_transform()`
- [x] 导出时按每项自身衣服/印花尺寸现算:抽纯函数 `core.composer.resolve_transform()`(已微调项用自身变换,否则 `template.to_transform_state(gw,gh,pw,ph)`)
- [x] widget 仅负责读图尺寸(`core.composer.image_size()`,只读 header)+ 单次运行内缓存
- [x] `main_window` 在 `template_changed`/`template_applied` 时把模板推给队列,并在启动时初始同步
- [x] 新增 4 个纯函数单测覆盖:已微调优先、不同尺寸各自换算、空模板/空尺寸返回 None
验收:
- [x] 一个批次内含不同尺寸的衣服/印花时,未微调项各自按模板正确落位
- [ ] GUI 实测:混合尺寸批量导出,结果图印花位置/大小均正确
### 17.16 局域网更新 · 阶段②:启动时检测并通知
前置阅读:
- `docs/10-lan-update.md`(§7 更新源、§8 流程、§16 阶段②)
- `docs/02-prd.md`(app_config 的 `update_source`)
说明:
- 阶段①(数据目录分离)已在 commit `76f2c6d` 完成。本任务实现阶段②「只读通知」,不自动安装。
任务:
- [x] 配置项 `update_source`(空 = 不检查)写入 `DEFAULT_CONFIG`
- [x] `services/update_service.py`:`parse_version` / `is_newer` / `check_for_update`,读取 `<source>/manifest.json`,任何不可达/损坏/非更新一律返回 None(不抛错、不阻塞)
- [x] 主窗口顶部通知横幅(默认隐藏):「发现新版本 vX.Y.Z」+「打开更新目录」+ 关闭
- [x] 检查在后台守护线程进行,经 Qt 队列信号回主线程显示横幅(更新源不可达不卡启动)
- [x] 「打开更新目录」用 `QDesktopServices` 打开 `manifest.source`
- [x] 15 个纯函数单测覆盖版本比较与 `check_for_update` 各分支
验收:
- [x] 单测通过;更新源为空/不可达时静默跳过
- [x] GUI 实测:配置可达更新源 + 高版本 manifest → 启动后显示横幅,点击打开目录
- [x] GUI 实测:更新源不可达 → 正常启动、无横幅、无卡顿
### 17.17 局域网更新 · 阶段③:自动安装启动器(脚本)
前置阅读:
- `docs/10-lan-update.md`(§3 架构、§8 流程、§16 阶段③)
说明:
- 实现 PowerShell 启动器 `scripts/update.ps1`,跑通「检查 → staging → 校验 → 原子切换 → 启动 → 降级」整条链路。先以脚本验证流程,后续再决定是否编译为 `Launcher.exe`。
任务:
- [x] `scripts/update.ps1`:读 `current.txt` 与 `data\config\app_config.json` 的 `update_source`
- [x] 读远端 `manifest.json`、语义化版本比较,仅当远端更高才更新
- [x] robocopy 拷到 `staging\<ver>.tmp` → 校验 `marker` → 重命名进 `versions\<ver>` → 原子写 `current.txt`(ascii 无 BOM)
- [x] 启动 `versions\<current>\CMBot.exe` 并设 `CMBOT_DATA_DIR`
- [x] 降级:源不可达 / robocopy 失败 / marker 缺失 / 坏 manifest 一律启动本地现版本
- [x] 假版本目录验证 6 个用例(更新、幂等、源不可达、未配置源、下载损坏、坏 manifest)全过
- [x] 接入真实安装结构:新增 `scripts/install_local.ps1` 产出 `%LOCALAPPDATA%\CMBot\app` / `data` / `staging` 启动器布局
- [ ] 真实双机实测
### 17.18 更新传输统一改为 HTTP
前置阅读:
- `docs/10-lan-update.md`(已整体改为 HTTP:§7 源、§8 流程、§14 安全、§16 阶段)
- `docs/02-prd.md`(`update_source` / `update_user` / `update_pass`)
背景:
- 更新源改为 HTTP(S) 文件服务(已部署 gohttpserver + nginx,HTTP Basic Auth,示例 `http://cm.xiapi.com/`)。§17.16/§17.17 的本地文件版与 UNC/robocopy 版作为历史保留,本任务把传输统一到 HTTP。
任务:
- [x] 文档:`docs/10` 由 UNC/robocopy 全面改为 HTTP(源/清单/流程/安全/阶段);`docs/02`、`docs/05` 同步配置项
- [x] 配置项 `update_user` / `update_pass` 写入 `DEFAULT_CONFIG`
- [x] `update_service.check_for_update` 支持 `http(s)://` 源 + Basic Auth(当前仅 `open()` 本地文件,填 URL 静默返回 None),补单测
- [x] 启动器 `update.ps1` 改 HTTP:带凭据下载 zip → 校验 SHA-256 → 解压到 `staging\app.new` → `app/app.old` 切换回滚
- [x] manifest 字段由 `source`/`files`/`marker` 改为 `url`/`sha256`/`size`
- [x] 发布流程脚本:build → 打 zip → 算 SHA-256 → 写 manifest → 上传
- [x] BOM 兼容:`check_for_update` 用 `utf-8-sig` 解码,`build.ps1` 写 manifest 不带 BOM(否则 PS5.1 的 UTF8 BOM 会让 app 内 `json.loads` 静默失效);补 2 个 BOM 单测
- [x] `update.ps1` HTTP 版以本地 HTTP server 假发布包端到端验证(拉清单→下载→SHA-256→`app/app.old` 切换)
- [ ] 生产前将更新源切到 HTTPS、客户端改用只读账号
- [ ] 真实环境(cm.xiapi.com)端到端实测
### 17.19 启动器改 Launcher.exe + 数据移到 ~/.cmbot(便携模型)
前置阅读:
- `docs/10-lan-update.md`(§3 架构、§4 布局、§5 数据根三级回退、§9 权限、§11、§16 阶段④)
背景:
- 改用编译的 `Launcher.exe`(Python + PyInstaller onefile)取代 `update.ps1`,对终端用户更友好。
- 程序采用便携布局:解压任意可写目录即用,安装根 = `Launcher.exe` 所在目录,不限定 `%LOCALAPPDATA%`。
- 用户数据移到 `~/.cmbot`(`%USERPROFILE%\.cmbot`):始终可写、按用户隔离、不随程序更新丢失。
任务:
- [x] 文档:`docs/10` 改为便携 + `Launcher.exe` + `~/.cmbot` 模型(§3/§4/§5/§8/§9/§11/§16)
- [x] `get_data_dir()` 三级回退:`CMBOT_DATA_DIR` → 打包态 `~/.cmbot` → 开发态项目根;`tests/test_file_service.py` 5 个单测
- [x] `src/launcher.py`:复用 `update_service`,下载 zip→SHA-256→解压→`app/app.old` 切换→启动;安装根可写性检测;首次把 `app\config\` 默认模板播种到 `~/.cmbot`;`update_service` 扩展(`UpdateInfo.sha256/size/min_supported`、绝对 url 解析、`make_auth_header`/`download`);`tests/test_launcher.py` 11 个单测 + 本地 HTTP server 真实端到端验证
- [x] `build.ps1` 增产 `Launcher.exe`(PyInstaller onefile,console);发布目录改为便携布局 `Launcher.exe` + `app\`;产出两个 zip——自更新载荷 `CMBot-<ver>.zip`(app\ 内容,manifest.url 指向它)与便携安装包 `CMBot-<ver>-portable.zip`;`launcher.py` 日志初始化健壮化(--windowed/只读根不崩)。语法校验通过;实际 PyInstaller 构建需在 Windows 跑
- [x] 退休 `scripts/update.ps1` 与 `scripts/install_local.ps1`(已删除,git 历史可查)
- [ ] 端到端实测(解压到 D 盘运行、自更新、回滚)
### 17.20 设置对话框(更新配置入口)
前置阅读:
- `docs/07-ui-design.md`(4.3 设置入口、4.4 设置对话框)
- `docs/02-prd.md`(`update_source` / `update_user` / `update_pass`)
- `docs/05-project-architecture.md`(4.12 config 集中管理)
背景:
- 更新地址/账号/密码目前只能手改 `~/.cmbot/config/app_config.json`,加一个应用内设置入口,管理员配一次,launcher 与 app 内通知都跟着用。
任务:
- [x] 文档:`docs/07` §3 布局、§4.1 去掉「暂不提供设置入口」、新增 §4.3 入口 + §4.4 对话框
- [x] 页签栏右端加低调 `⚙ 配置` 按钮(`addStretch` 与编号页签隔开,非编号页签)
- [x] `src/app/widgets/settings_dialog.py`(`QDialog`,纯 UI):更新地址 / 账号 / 密码(掩码+显示) / 测试连接 / 当前版本 + 安全提示
- [x] `测试连接`:后台线程调用 `update_service.load_manifest`(区分「连不上」与「已是最新」),反馈最新/发现新版/连接失败
- [x] `main_window`:打开时注入当前 config,保存时集中 `save_config`,UI 不直接写配置
- [x] 保存后重新触发一次在线更新检查(横幅刷新;`_update_found` 改为只连一次避免重复)
- [x] 取消不改动配置
验收:
- [x] 单测:`load_manifest` 返回 dict / 缺失抛错(2 个);对话框 Qt 符号导入校验通过
- [ ] GUI 实测:改更新地址/账号/密码并持久化到 `~/.cmbot/config/app_config.json`
- [ ] GUI 实测:测试连接正确反馈三种结果
- [ ] GUI 实测:改完无需重启 launcher,app 内横幅按新配置刷新
### 17.21 更新改为非阻塞:app 内下载 + 启动器只应用
前置阅读:
- `docs/10-lan-update.md`(§3 两阶段架构、§6 时机、§8 流程、§12 体验)
- `docs/07-ui-design.md`(4.3 有新版本提示、4.4 设置对话框「检查并更新」)
背景:
- 原启动器在启动时下载更新、阻塞进主界面(57MB 包看着像卡死)。改为「下载在 app 内、切换在启动器」,启动永不阻塞。
任务:
- [x] `services/installer.py`:`download_and_stage()`(app 运行时下载→SHA-256→解压→`staging\app.new`)、`apply_staged()`(启动器秒切 `app/app.old`,含「不比当前新则丢弃」「移动失败回滚」)、`staged_version()`、`is_writable`
- [x] `launcher.py` 瘦身:seed + `apply_staged` + 启动,不联网;`tests/test_launcher.py` 重写
- [x] `main_window`:发现新版在 `⚙ 配置` 左侧显示蓝色提示文字(删除旧阻塞横幅与「打开更新目录」)
- [x] `settings_dialog`:加「检查并更新」→ 下载暂存 → 「下次启动生效」
- [x] `tests/test_installer.py` 11 个单测 + 本地 HTTP server 真实端到端(下载暂存→app 不动→应用切换)
- [x] 文档:`docs/10` §3/§6/§8/§12/§16/§18、`docs/07` §4.3/§4.4 对齐
- [ ] GUI 实测:提示文字出现 → 设置里更新 → 重启生效(真机)
### 17.22 默认导出目录改为程序旁的「合并后的图片」
前置阅读:
- `docs/10-lan-update.md`(§4 布局、§5 路径规则)
- `docs/07-ui-design.md`(7.5 输出设置)
背景:
- 默认导出在 `~/.cmbot/output`,藏在用户目录深处不好找。改为放在**安装根**(`Launcher.exe` 旁)的 `合并后的图片\`:好找、不随 `app\` 更新替换。**不放 `app\`**(每次更新会被替换、导出会丢)。
任务:
- [x] 文档:`docs/10` §4/§5、`docs/02` 配置说明、`docs/07` §7.5 对齐
- [x] `file_service.get_output_dir()`:打包态优先 `<安装根>\合并后的图片`(安装根 = `get_app_dir().parent`),不可写回退 `get_data_dir()/output`;开发态用项目目录;`tests/test_file_service.py` 3 个单测
- [x] 导出面板默认值随之变化(沿用 `get_output_dir()`,无需单独改)
- [ ] GUI 实测:打包运行后默认导出到 `<安装根>\合并后的图片`,且更新后仍在
## 18. 后续暂缓任务
以下任务第一阶段暂不做,后续需要时再新增设计文档:
- [ ] 按电脑名控制版本
- [ ] 强制更新与版本保留策略(`mandatory` / `min_supported` 生效、`app.old` 保留策略;docs/10 §16 阶段④)
- [~] AI 穿搭(已转正、进入设计/实现,见 §19)
- [ ] 导出上架流程
- [ ] 自动抠图
- [ ] 自动识别衣服区域
- [ ] 移动端应用
已交付(原属暂缓,本季完成,见 §17.16–§17.20、`docs/10-lan-update.md`):
- [x] 在线更新(HTTP 检测 + 通知 + `Launcher.exe` 自动安装 + 回滚)
- [x] 局域网/在线分发(便携包 + HTTP 文件服务 + manifest)
- [x] ~~多版本启动器~~(改为 `app`/`app.old` 单版本切换,不再做多版本并排)
## 19. AI 穿搭模块
前置阅读:
- `docs/11-ai-outfit.md`(本模块完整设计)
- `docs/旧ai穿搭项目.md`(同事旧项目分析,可复用件)
- `docs/04-development-rules.md`、`docs/05-project-architecture.md`(分层)
一句话目标:以 Excel 为数据源,按行读取「衣服图 + 标题/货号」,套提示词调 AI 图像 API 生成「人物穿着该衣服」的效果图,保存 JPG 并把结果路径写回 Excel。
### 19.0 设计与效果图
- [x] 分析旧项目 → `docs/旧ai穿搭项目.md`
- [x] 模块设计文档 `docs/11-ai-outfit.md`(架构/Excel 适配/数据模型/AI 服务/提示词/并发/输出/界面/配置/阶段/验收)
- [x] UI 效果图 `docs/ui-ai-outfit.html` + `docs/ui-ai-outfit.png`(三栏:左设置 / 中「最近结果缩略图条 + 处理明细表」/ 右运行日志;与印花页同款外壳、纯蓝统一)
- [x] 确认取舍:不做实时单图大预览(批量工具,并发会有「显示哪行」歧义);预览=按钮弹 `QDialog`;保留「保存话术」按钮;并发由 Python 线程负责、Qt 仅 signal 回主线程
### 19.1 核心与服务(无 GUI,可单测)— docs/11 §14 阶段 1
前置阅读:`docs/11-ai-outfit.md`(§3–§9)
- [x] 前置依赖:`requirements.txt` 增加 Python 3.7 兼容的 `requests` / `urllib3` / `openpyxl` 锁定版本,并同步 `docs/03-technical-stack.md`
- [x] `core/models.py` 新增 `OutfitTask` / `OutfitResult`(纯 dataclass,Python 3.7 兼容,不依赖 PySide6)
- [x] `services/excel_service.py`:读行 → `List[OutfitTask]`、写回 D/E/F、占用检测、跳过「完成」/按设置重试「失败」/空字段安全跳过、每行即存
- [x] `services/ai_image_service.py`:移植旧项目 `ImageApiClient`(多模型、多请求格式 `chat/gemini/images/images_edits`、传图 data-url、递归取图、URL 归一化、字段校验);注意 PEP585 类型注解改 Python 3.7 写法
- [x] `core/ai_outfit.py`:单行生成纯逻辑编排(提示词渲染 + 调用 + 保存 JPG + 产出 `OutfitResult`);§8「超时按分辨率动态决定(512/1K/2K/4K→180/240/360/600,可被 `timeout_seconds` 覆盖)」由 `ai_image_service.resolution_timeout` 实现
- [x] 单测:`test_excel_service` / `test_ai_image_service` / `test_ai_outfit` / `test_outfit_batch` 共 40+ 用例(Excel 读写、提示词渲染、取图、命名去重、超时映射;API 用 mock);Python 3.7 通过、不依赖 GUI
### 19.2 批量编排 — docs/11 §14 阶段 2
前置阅读:`docs/11-ai-outfit.md`(§8)
- [x] `core/outfit_batch.py` 纯逻辑 `OutfitBatchRunner`:`ThreadPoolExecutor(并发数)` + `RateLimiter(请求间隔)` + 单任务冷却 + 阶梯重试 + 温和停止 + >30s 心跳;全可测(`QObject` 包装放 §19.3 UI 接线)
- [x] 子线程只经回调/signal 回主线程,不直接碰控件(runner 用 callback,UI 层转 signal)
### 19.3 UI 页签 — docs/11 §14 阶段 3
前置阅读:`docs/11-ai-outfit.md`(§10)、`docs/07-ui-design.md`(§4.2)、`docs/ui-ai-outfit.png`
- [x] 页签栏接 `QStackedWidget`(页 0=添加印花工作区+队列,页 1=AI 穿搭面板);启用页签 1(导出上架仍禁用)
- [x] `app/widgets/ai_outfit_panel.py`:左设置(Excel/输出/模型/话术编辑+保存+插入占位符+预览弹窗/生成设置)、中(最近结果缩略图 + 处理明细表)、右(进度+统计+开始/停止+导出失败清单+打开输出目录+实时日志)
- [x] 接线后台线程:`QThread` + `_OutfitWorker(QObject)` 包 `OutfitBatchRunner`,signal 回主线程刷日志/进度/明细/缩略图;每行写回 Excel;结束摘要弹窗;失败清单导出 CSV
- [ ] GUI 实测(真机):选 Excel/模型跑通、停止生效、失败清单正确(离屏冒烟已过,待真机)
### 19.4 配置与提示词 — docs/11 §14 阶段 4
前置阅读:`docs/11-ai-outfit.md`(§7、§11)
- [x] `config_service` 增 `load_ai_models()` / `load_outfit_prompt()` / `save_outfit_prompt()`;`ai_models.json`(密钥明文、**不入库**、BOM 容错)、`outfit_prompt.txt`(无 BOM)、`app_config.json` 并入 `outfit_*`(上次 Excel/输出/模型 + 批量设置),UI 经 `config_changed` 信号回主窗口集中存;`tests/test_config_service.py` 8 用例
- [x] `requirements.txt` 已锁 `requests 2.31`/`openpyxl 3.1.3`/`urllib3 1.26`;`docs/03` 增列依赖
- [ ] 可选:应用内 AI 模型编辑界面(当前由管理员预置 `ai_models.json`,暂不做)
### 19.5 真机联调 — docs/11 §14 阶段 5
- [ ] 真实中转 API + 小批量 Excel 端到端跑通,调穿搭提示词
- [ ] 验收对照 `docs/11` §16
### 19.6 提示词自动附加「批量生成输出要求」— docs/11 §7.1
前置阅读:`docs/11-ai-outfit.md`(§7.1)
背景:旧项目最终提示词 = 用户话术 + 自动附加的输出要求(参考解析度 / 固定 1:1 / 结合标题与参考图 / 不可跑版);当前 cmbot 的 `render_prompt` 只替换 `{title}`,缺这段。决策:简体文案、始终自动附加(不做开关)。
- [x] `core/ai_outfit.py`:加 `OUTPUT_REQUIREMENTS` 常量 + `build_output_requirements(resolution)`;`render_prompt(template, task, resolution=None)` 在替换占位符后附加该段(`resolution` 为空不加);`generate_outfit_image` 传入当前 resolution
- [x] `app/widgets/ai_outfit_panel.py`:内嵌预览改用 `render_prompt`(带当前分辨率),分辨率下拉变化时刷新预览,使「预览 = 实际发送」
- [x] 单测:带 `resolution` 追加尾巴且内容正确、无 `resolution` 不加;`build_output_requirements` 空/非空(加入 `tests/test_ai_outfit.py`,全套 12 文件绿)
- [ ] 同步 `docs/ui-ai-outfit.html` 预览框示例含该段并重渲 `.png`(可选,未做)
### 19.7 切换分辨率 / AI 模型时信息提示 — docs/11 §10.2
前置阅读:`docs/11-ai-outfit.md`(§10.2)
需求:用户手动切换「分辨率」或「AI 模型」下拉时弹信息框告知影响(**只告知、不拦截、不还原**)。决策:每次实际更换都弹一次。
- [x] `app/widgets/ai_outfit_panel.py`:给 `_resolution` / `_model_combo` 接 **`activated`** 信号(非 `currentIndexChanged`,避免启动/载配置误弹);仅值实际改变时 `QMessageBox.information`,保持新选项、不回退
- [x] 分辨率文案带 `ai_image_service.resolution_timeout` 的超时秒数(512/1K/2K/4K→180/240/360/600)+「下次生效」
- [x] AI 模型文案带该模型 `api_type` + 计费/效果可能不同 +「下次生效」
- [x] 离屏验证:程序化赋值(`apply_config`/`_fill_sample_combo`)不弹(0 次);用户 1K→4K 弹 1 次(含 600s)、同项重选不弹、换模型弹(含 api_type)
### 19.8 话术模板(多套命名话术)— docs/11 §7.2
前置阅读:`docs/11-ai-outfit.md`(§7.2、§7.1)、`docs/07-ui-design.md`、`src/app/widgets/template_panel.py`(范式参考)
需求:把单一话术升级为多套命名话术(下拉切换 / 新建 / 另存为 / 重命名 / 删除 / 保存,记住上次)。决策:**全部自定义**(不分内置,播种一套「默认」);**切换前脏数据弹窗**提醒是否保存;**含重命名**。
- [x] `config_service`:`outfit_prompts.json` 读写 `load_outfit_prompts()` / `save_outfit_prompts(list)`(utf-8-sig 读 / 无 BOM 写 / 损坏回退「默认」;始终 ≥1 套;`_normalize_prompts` 丢弃非法项);`app_config` 加 `outfit_prompt_name`(当前选中名);首次迁移旧 `outfit_prompt.txt` → 一套「默认」,否则 `DEFAULT_OUTFIT_PROMPT`
- [x] `ai_outfit_panel.py`:「通用话术」组加 模板下拉 + 新建 / 另存为 / 重命名 / 删除(保留 插入标题 / 保存);切换载入文本 + 刷新预览;记住上次所选并启动恢复(经 `config_changed` 回主窗口集中存 `outfit_prompt_name`)
- [x] 脏数据保护:编辑框与当前套已存文本不同则在切换/新建/另存为前弹「保存 / 不保存 / 取消」(取消还原下拉);名字唯一(空/重名拒绝);删后选邻近、不可删到 0(删除单独二次确认)
- [x] 「开始生成」前把当前编辑存回所选套(`_store_current_text`);生成/预览仍走 `render_prompt`(§7.1 尾巴不变)
- [x] 单测 `tests/test_config_service.py` +6(seed/迁移/损坏回退/roundtrip 无 BOM/丢弃非法/BOM 容错,共 14);离屏验证 新建·另存为·切换·脏保存持久化·重命名·删除·不可删到 0;全套 12 文件绿
### 19.9 修复:全表完成后预览样本行下拉为空 — docs/11 §10.3
前置阅读:`docs/11-ai-outfit.md`(§10.3、§7)
问题:预览样本行用 `load_outfit_tasks()`(给生成用、跳过「完成」行)填,整表生成成功后返回空 → 下拉只剩「(选 Excel 后显示替换效果)」。预览应不看状态。
- [x] `services/excel_service.py`:新增 `read_all_rows(excel)`,返回每一有效数据行(标题/货号/衣服图齐全)的 `OutfitTask`,**忽略 E 列状态**
- [x] `ai_outfit_panel._reload_sample_rows()` 改调 `read_all_rows`(预览专用);生成仍走 `load_outfit_tasks`,`_on_tasks_loaded` 仍用实际任务
- [x] 单测 `tests/test_excel_service.py`:`read_all_rows` 含「完成」「失败」行也返回、空字段行跳过(8 用例);离屏验证全表完成的 Excel 预览下拉有样本行;全套 12 文件绿
### 19.10 无待处理行提示 + 生成不清空预览样本 — docs/11 §10.4
前置阅读:`docs/11-ai-outfit.md`(§10.4、§10.3)
背景:整表都「完成」后点「开始生成」,当前只显示「已加载 0 行待处理任务 / 完成 0,失败 0」,看不懂;且 `_on_tasks_loaded([])` 会把预览样本下拉清空(破坏 §10.3)。
- [x] 改进 1:`_on_finished(total==0)` 弹 `_show_no_pending_message`——统计 `read_all_rows` 的「完成 N / 失败 M」,提示「已完成 N 跳过;失败 M 可勾『重试上次失败的行』;重做已完成请清空状态(E)列」(无有效行时另提示)
- [x] 改进 2:`_on_tasks_loaded` 去掉 `_fill_sample_combo(tasks)`,预览样本下拉只由 `read_all_rows`/`_reload_sample_rows` 维护,与运行解耦
- [x] 离屏验证:全表完成 → `tasks_loaded([])` 后预览下拉仍 2 行、`_on_finished(total=0)` 弹"没有待处理的行"(含已完成1/失败1);`total>0` 仍弹常规"AI 穿搭"结束框;全套 12 文件绿