2026-06-15 16:09:32 +08:00
# 开发任务清单
## 任务文档说明
本文档用于跟踪自动合成印花服饰效果图工具的整体开发任务。后续 AI 或开发者领取任务前,必须先阅读对应任务的前置文档,并严格遵守 `docs/04-development-rules.md` 。
任务状态说明:
- `[x]` 已完成。
- `[ ]` 未开始。
- `[~]` 进行中或部分完成。
## 任务执行顺序
任务编号即建议开发顺序,后续任务依赖前序任务的产出:
```
1(基础服务)→ 2(数据模型)→ 3/4(文件/模板服务)
2026-06-16 10:53:19 +08:00
→ 5(图片合成核心)→ 6(批量任务核心)→ 6.5(低对比组合筛选核心)→ 7(主界面布局)→ 8-13(各 UI 面板)→ 14(微调状态)→ 15(测试)→ 16(打包)
2026-06-15 16:09:32 +08:00
```
任务 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`
任务:
2026-06-15 16:33:33 +08:00
- [x] 先读取 `src/services/log_service.py` 现有内容
- [x] 完善 `src/services/log_service.py`
- [x] 启动时创建 `logs/` 目录
- [x] 日志文件按启动时间或日期命名
- [x] 日志格式包含时间、模块、级别、消息
- [x] 在 `src/main.py` 中初始化日志
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 16:33:33 +08:00
- [x] 启动程序后生成日志文件
- [x] 程序启动信息写入日志
- [x] 不使用 `print` 作为正式日志
2026-06-15 16:09:32 +08:00
### 1.2 路径与资源服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/09-packaging-release.md`
任务:
2026-06-15 16:48:37 +08:00
- [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 中文路径可用
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 16:48:37 +08:00
- [x] 路径函数不使用开发机绝对路径
- [x] 程序目录下缺少 `logs/` 或 `output/` 时可自动创建
2026-06-15 16:09:32 +08:00
### 1.3 配置服务
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
2026-06-15 16:51:46 +08:00
- [x] 先读取 `src/services/config_service.py` 现有内容
- [x] 完善 `src/services/config_service.py`
- [x] 定义默认配置
- [x] 支持读取 JSON 配置
- [x] 支持保存 JSON 配置
- [x] 配置损坏时使用安全默认值并记录日志
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 16:51:46 +08:00
- [x] 缺少配置文件时程序可启动
- [x] 配置文件损坏时程序可启动并记录日志
- [x] 不会无提示清空用户配置
2026-06-15 16:09:32 +08:00
## 2. 核心数据模型
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-15 16:56:36 +08:00
- [x] 先读取 `src/core/models.py` 现有内容
- [x] 完善 `src/core/models.py`
- [x] 定义 `ImageAsset`
- [x] 定义 `TransformState`
- [x] 定义 `Template`
- [x] 定义 `ExportOptions`
- [x] 定义 `BatchOptions`
- [x] 定义 `ComposeResult`
- [x] 定义 `BatchResult`
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 16:56:36 +08:00
- [x] `models.py` 不依赖 PySide6 UI 控件
- [x] 变换状态包含 `x/y/width/height/rotation/keep_aspect_ratio`
- [x] 模型可以被 core、services 和 UI 层复用
2026-06-15 16:09:32 +08:00
## 3. 文件扫描与素材导入
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
2026-06-15 16:58:44 +08:00
- [x] 先读取 `src/services/file_service.py` 现有内容
- [x] 完善 `src/services/file_service.py`
- [x] 支持递归扫描衣服图片文件夹
- [x] 支持递归扫描印花图片文件夹
- [x] 支持格式:PNG、JPG、JPEG、WEBP
- [x] 忽略不支持格式并记录日志
- [x] 保留来源子文件夹信息
- [x] 生成安全输出文件名
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 16:58:44 +08:00
- [x] 可以扫描包含子文件夹的中文路径
- [x] 不修改原始素材文件
- [x] 不支持文件格式不会导致扫描失败
2026-06-15 16:09:32 +08:00
## 4. 模板服务
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
2026-06-15 17:01:40 +08:00
- [x] 先读取 `src/services/template_service.py` 现有内容
- [x] 完善 `src/services/template_service.py`
- [x] 定义内置模板
- [x] 支持读取自定义模板 JSON
- [x] 支持保存自定义模板 JSON
- [x] 支持新增、重命名、删除自定义模板
- [x] 校验模板字段
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 17:01:40 +08:00
- [x] 内置模板和自定义模板可区分
- [x] 单个模板损坏不影响全部模板加载
- [x] UI 不直接操作模板 JSON 文件
2026-06-15 16:09:32 +08:00
## 5. 图片合成核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-15 17:28:02 +08:00
- [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] 输出文件名避免默认覆盖
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 17:28:02 +08:00
- [x] 合成逻辑不依赖 PySide6 UI 控件
- [x] 输出尺寸默认与衣服底图一致
- [x] 透明 PNG 不出现黑底或白底
- [x] 印花部分超出画布时不会报错
2026-06-15 16:09:32 +08:00
## 6. 批量任务核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
任务:
2026-06-15 17:55:55 +08:00
- [x] 先读取 `src/core/batch.py` 现有内容
- [x] 完善 `src/core/batch.py`
- [x] 支持多衣服 × 单印花
- [x] 支持单衣服 × 多印花
- [x] 支持一一匹配
- [x] 支持全组合
- [x] 复用 `core/composer.py`
- [x] 单个任务失败时继续处理剩余任务
- [x] 汇总成功数量、失败数量和失败原因
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 17:55:55 +08:00
- [x] 不复制第二套合成算法
- [x] 单张失败不会中断整个批量任务
- [x] 失败原因可供 UI 和日志使用
2026-06-15 16:09:32 +08:00
2026-06-16 10:53:19 +08:00
## 6.5 低对比组合筛选核心
前置阅读:
- `docs/02-prd.md`
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-16 11:21:21 +08:00
- [x] 定义可见度状态:`正常` 、`偏低` 、`不明显` 、`无法判断`
- [x] 新增低对比分析核心函数,位置应符合架构分层,不能写进 UI 事件
- [x] 基于当前模板或 `TransformState` 获取衣服目标区域
- [x] 只分析衣服目标区域颜色,不使用整张衣服图判断
- [x] 只统计印花 alpha 有效像素,例如 `alpha > 20`
- [x] 计算 RGB 颜色距离
- [x] 计算亮度差
- [x] 根据阈值输出可见度状态和评分
- [x] 单个组合分析失败时返回 `无法判断` ,不得中断整个队列
- [x] 为后续队列项保存可见度结果预留字段或结果结构
2026-06-16 10:53:19 +08:00
验收:
2026-06-16 11:21:21 +08:00
- [x] 白色衣服配浅色印花时可标记为 `偏低` 或 `不明显`
- [x] 深色衣服配深色印花时可标记为 `偏低` 或 `不明显`
- [x] 透明 PNG 的透明区域不参与印花颜色判断
- [x] 低对比分析不修改原始衣服图片和原始印花图片
- [x] 分析逻辑不依赖 PySide6 UI 控件
- [x] 失败项不会中断批量队列生成或导出流程
2026-06-16 10:53:19 +08:00
2026-06-15 16:09:32 +08:00
## 7. 主界面布局
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
2026-06-15 18:04:29 +08:00
- [x] 先读取 `src/app/main_window.py` 现有内容
- [x] 将 `src/app/main_window.py` 从最小窗口扩展为主布局
2026-06-17 09:05:47 +08:00
- [x] ~~实现顶部标题和版本号~~ (后移除内容区自绘标题栏:与系统窗口标题重复,名称/版本改由系统标题栏显示,见 17.7)
2026-06-15 18:04:29 +08:00
- [x] 实现流程页签区域
- [x] 实现左侧素材栏容器
- [x] 实现中间预览区容器
- [x] 实现右侧参数栏容器
- [x] 实现底部合成队列容器
- [x] 实现底部状态栏
2026-06-15 16:09:32 +08:00
验收:
2026-06-15 18:04:29 +08:00
- [x] 窗口标题显示 `APP_NAME` 和 `APP_VERSION`
- [x] UI 布局接近 `docs/ui-v1.png`
- [x] 不把所有业务逻辑塞进 `main_window.py`
2026-06-15 16:09:32 +08:00
## 8. 素材列表 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
2026-06-16 08:44:17 +08:00
- [x] 先读取 `src/app/widgets/image_list_panel.py` 现有内容
- [x] 实现衣服图片列表
- [x] 实现印花图片列表
- [x] 实现打开文件夹按钮
- [x] 实现全选和取消全选
- [x] 实现单张勾选和取消勾选
- [x] 显示已选数量和总数量(例如:`含子文件夹 · 共 12` )
- [x] 显示来源子文件夹标签和文件名
- [x] 实现排序选择器(默认按文件夹+名称排序)
- [x] 点击素材后更新当前预览选择
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 08:44:17 +08:00
- [x] 支持递归导入素材
- [x] 当前预览选中态和批量勾选态可区分
- [x] 不在 UI 层重复实现文件扫描规则
2026-06-15 16:09:32 +08:00
## 9. 图像预览编辑器
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-16 09:01:15 +08:00
- [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`
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:01:15 +08:00
- [x] 预览缩放不改变真实合成参数
- [x] 拖动后坐标为衣服原图像素坐标
- [x] 缩放后宽高同步
- [x] 旋转后角度同步
- [x] 不使用截图作为导出结果
2026-06-15 16:09:32 +08:00
## 10. 参数面板 UI
前置阅读:
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-16 09:06:24 +08:00
- [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`
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:06:24 +08:00
- [x] 参数输入后预览同步更新
- [x] 预览交互后参数输入框同步更新
- [x] 程序化更新输入框不会造成信号循环
2026-06-15 16:09:32 +08:00
## 11. 模板面板 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
2026-06-16 09:15:33 +08:00
- [x] 先读取 `src/app/widgets/template_panel.py` 现有内容
- [x] 完善 `src/app/widgets/template_panel.py`
- [x] 显示模板下拉框
- [x] 支持选择模板
- [x] 支持保存模板
- [x] 支持另存为模板
- [x] 支持重置为模板
- [x] 通过 `template_service` 操作模板
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:15:33 +08:00
- [x] 选择模板后更新预览和参数面板
- [x] UI 不直接修改模板 JSON
- [x] 自定义模板重启后仍可用
2026-06-15 16:09:32 +08:00
## 12. 导出面板 UI
前置阅读:
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
- `docs/09-packaging-release.md`
任务:
2026-06-16 09:18:39 +08:00
- [x] 先读取 `src/app/widgets/export_panel.py` 现有内容
- [x] 完善 `src/app/widgets/export_panel.py`
- [x] 实现输出目录选择
- [x] 实现输出格式选择
- [x] 实现质量选择
- [x] 实现导出当前单张
2026-06-16 17:01:58 +08:00
- [x] ~~实现应用为模板入口~~ (后移除:与模板区域「另存为」重复,见 17.3)
- [x] ~~实现重置为模板入口~~ (后移除:与模板区域「重置为模板」重复,见 17.3)
2026-06-16 09:18:39 +08:00
- [x] 显示当前操作影响范围提示
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:18:39 +08:00
- [x] 缺少衣服或印花时导出按钮禁用
- [x] 输出目录不可写时提示用户并记录日志
- [x] 单张导出调用 `core/composer.py`
2026-06-15 16:09:32 +08:00
## 13. 合成队列 UI
前置阅读:
- `docs/02-prd.md`
- `docs/05-project-architecture.md`
- `docs/07-ui-design.md`
任务:
2026-06-16 09:25:45 +08:00
- [x] 实现底部合成队列表格(建议使用 `QTableView` + 模型视图结构)
- [x] 实现队列头部批量模式切换:多衣服×单印花 / 单衣服×多印花 / 一一匹配 / 全组合(矩阵)
- [x] 实现队列统计显示(共 N 项 · 完成 · 进行 · 失败 · 待导出)
- [x] 实现「重置全部」按钮
- [x] 实现「导出选中」按钮
- [x] 实现「开始批量导出」按钮(导出中切换为停止/暂停)
- [x] 实现队列折叠/展开
- [x] 显示任务状态(已完成 / 待导出 / 导出中 / 失败)
- [x] 导出中任务显示进度条
- [x] 显示失败原因
- [x] 支持选中队列项并更新预览区和参数面板
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:25:45 +08:00
- [x] 队列统计显示总数、完成、进行、失败、待导出
- [x] 导出中任务显示进度条
- [x] 失败行显示可理解原因
- [x] 选中队列项可以加载对应状态
2026-06-15 16:09:32 +08:00
## 14. 微调状态
前置阅读:
- `docs/07-ui-design.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-16 09:31:16 +08:00
- [x] 定义队列项微调状态
- [x] 手动调整队列项后标记为 `已微调`
- [x] 微调项保存独立 `TransformState`
- [x] 批量导出时微调项使用自己的参数
- [x] 未微调项使用模板参数
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:31:16 +08:00
- [x] 已微调项不会被无提示覆盖
- [x] 队列表格能显示 `模板` 和 `已微调`
2026-06-15 16:09:32 +08:00
## 15. 测试
前置阅读:
- `docs/04-development-rules.md`
- `docs/05-project-architecture.md`
- `docs/08-image-editor-design.md`
任务:
2026-06-16 09:38:06 +08:00
- [x] 编写 `tests/test_composer.py`
- [x] 编写 `tests/test_templates.py`
- [x] 测试透明 PNG 合成
- [x] 测试缩放参数
- [x] 测试旋转中心
- [x] 测试文件扫描
- [x] 测试模板读写
- [x] 测试批量任务生成
2026-06-15 16:09:32 +08:00
验收:
2026-06-16 09:38:06 +08:00
- [x] 核心逻辑测试不依赖 GUI
- [x] 测试可在 Python 3.7 环境运行
2026-06-15 16:09:32 +08:00
## 16. 打包
前置阅读:
- `docs/03-technical-stack.md`
- `docs/04-development-rules.md`
- `docs/09-packaging-release.md`
任务:
2026-06-16 11:49:25 +08:00
- [x] 创建打包脚本
- [x] 验证 PyInstaller `onedir` 打包
- [x] 打包资源文件
- [x] 打包默认配置和模板
- [x] 验证打包后程序启动
- [x] 验证标题栏版本号
- [x] 验证单张导出
2026-06-15 16:09:32 +08:00
验收:
- [ ] 打包产物可在无 Python 环境的 Windows 电脑启动
2026-06-16 11:49:25 +08:00
- [x] 发布包不包含源代码、缓存、测试输出和私人配置
2026-06-15 16:09:32 +08:00
2026-06-16 17:01:58 +08:00
## 17. 体验优化(持久化与独立重置)
第二阶段根据实际使用反馈新增。
### 17.1 用户偏好持久化
前置阅读:
- `docs/02-prd.md` (6.6 模板选择、6.9 批量合成导出、9 数据与文件结构)
- `docs/05-project-architecture.md` ( 4.12 config_service)
任务:
2026-06-18 11:37:17 +08:00
- [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()` 与批量模式变化信号
2026-06-16 17:01:58 +08:00
验收:
2026-06-18 11:37:17 +08:00
- [x] 重启后自动恢复上次选择的模板和批量模式
- [x] 自定义模板被删除或批量模式值非法时安全回退,不报错
- [x] UI 控件不直接读写配置文件
2026-06-16 17:01:58 +08:00
### 17.2 位置 / 尺寸 / 旋转独立重置
前置阅读:
- `docs/07-ui-design.md` ( 7 右侧参数栏、10 视觉规范)
- `docs/08-image-editor-design.md`
任务:
2026-06-18 11:37:17 +08:00
- [x] 先读取 `src/app/widgets/transform_panel.py` 现有内容
- [x] 在 `位置` / `尺寸` / `旋转` 三个分区标题右侧各加一个独立 `重置` 按钮
- [x] 各重置只把对应参数还原为当前选中模板的值,互不影响(位置→仅 X/Y;尺寸→仅宽/高,位置不变;旋转→仅角度)
- [x] 移除合并的 `重置位置 / 尺寸 / 角度` 按钮与角度旁的 `归零` 按钮
- [x] 重置按钮采用幽灵样式(静止灰、悬停蓝、按下深蓝、禁用更灰),不使用红色
- [x] 未选模板或未加载衣服/印花时三个按钮置灰
- [x] 由 `main_window` 取模板换算值并按项合并进当前 `TransformState`
2026-06-16 17:01:58 +08:00
验收:
2026-06-18 11:37:17 +08:00
- [x] 位置、尺寸、旋转可分别独立重置,互不影响
- [x] 尺寸重置只改变宽 / 高,不改变位置 X / Y
- [x] 按钮视觉层级符合 `docs/07-ui-design.md` 第 10 节
2026-06-16 17:01:58 +08:00
### 17.3 按钮去重(导出面板模板入口)
前置阅读:
- `docs/07-ui-design.md` (7.1 模板区域、7.7 操作按钮)
任务:
2026-06-18 11:37:17 +08:00
- [x] 移除导出面板的 `应用为模板` 与 `重置为模板` 按钮(与模板区域的 `另存为` / `重置为模板` 是同一动作的重复入口)
- [x] 模板相关操作统一留在模板区域,导出面板只负责输出与导出
2026-06-16 17:01:58 +08:00
验收:
2026-06-18 11:37:17 +08:00
- [x] 同一个动作在界面上只出现一次,命名一致
- [x] 导出面板仅保留输出设置与 `导出当前单张`
2026-06-16 17:01:58 +08:00
2026-06-16 17:45:09 +08:00
### 17.4 「重置为模板」归位并更名为「位置尺寸旋转重置为当前模板」
前置阅读:
- `docs/07-ui-design.md` ( 7 右侧参数栏、10 视觉规范)
任务:
2026-06-18 11:37:17 +08:00
- [x] 把模板区域的 `重置为模板` 按钮移出,移到参数栏底部(旋转分区下方)
- [x] 更名为 `位置尺寸旋转重置为当前模板` ,与三个分项 `↺ 重置` 构成「全部 + 分项」两级
- [x] 还原逻辑改为把位置/尺寸/旋转整体设为模板值,并与分项重置同走 `_apply_preview_transform`
- [x] 与三个分项重置一起受「未加载图片时置灰」控制
- [x] 采用安静、全宽、悬停出强调色的样式(不使用红色)
2026-06-16 17:45:09 +08:00
验收:
2026-06-18 11:37:17 +08:00
- [x] 模板区域只剩 `保存` / `另存为` ,不含整体重置
- [x] `位置尺寸旋转重置为当前模板` 在旋转分区下方,未加载图片时置灰
- [x] 点击后位置、尺寸、旋转一并还原为当前选中模板
2026-06-16 17:45:09 +08:00
### 17.5 模板删除按钮
前置阅读:
- `docs/07-ui-design.md` ( 7.1 模板区域)
任务:
2026-06-18 11:37:17 +08:00
- [x] 在模板区域增加 `删除` 按钮(复用 `template_service.delete_template` )
- [x] `删除` 仅对自定义模板可用,选中内置模板时隐藏(与 `保存` 一致)
- [x] 删除前弹二次确认对话框;确认后才删除
- [x] 删除后刷新下拉框并回选第一个内置模板,同步持久化选择
- [x] `删除` 用红色文字提示危险但保持安静样式
2026-06-16 17:45:09 +08:00
验收:
2026-06-18 11:37:17 +08:00
- [x] 可在界面上删除自定义模板,无需手改 `templates.json`
- [x] 内置模板不显示 `删除`
- [x] 删除有二次确认,取消则不删
2026-06-16 17:45:09 +08:00
### 17.6 参数/导出面板紧凑化与命名
前置阅读:
- `docs/07-ui-design.md` (7.2 位置区域、7.3 尺寸区域、7.5 输出设置区域)
任务:
2026-06-18 11:37:17 +08:00
- [x] 「位置」的 X、Y 输入框放在同一行(标签简化为 `X` / `Y` )
- [x] 「尺寸」的宽、高输入框放在同一行(标签简化为 `宽` / `高` ),`锁定宽高比例` 在其下方
- [x] 导出面板「格式」「质量」放在同一行
- [x] 底部整体重置按钮更名为 `位置尺寸旋转重置为当前模板`
- [x] 仅改布局/文案,不动同步与导出逻辑
2026-06-16 17:45:09 +08:00
验收:
2026-06-18 11:37:17 +08:00
- [x] X/Y 一行、宽/高一行、格式/质量一行,界面更紧凑
- [x] 拖动/缩放预览后输入框仍正常同步
- [x] 质量仍仅在 JPG 时可用
2026-06-16 17:45:09 +08:00
2026-06-17 09:05:47 +08:00
### 17.7 移除重复的自绘标题栏与无效「设置」按钮
前置阅读:
- `docs/07-ui-design.md` ( 3 整体布局、4.1 标题栏)
任务:
- [x] 移除标题栏里点了无反应的占位「设置」按钮
- [x] 移除内容区自绘标题栏(与系统窗口标题重复),名称/版本由系统标题栏(`setWindowTitle` )显示
- [x] 同步清理相关样式与未用常量
验收:
- [x] 界面不再出现重复的软件名称/版本号
- [x] 系统窗口标题仍显示 `APP_NAME` 与 `APP_VERSION`
- [x] 工作区因移除标题栏而获得更多纵向空间
2026-06-17 15:13:01 +08:00
### 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] 批量导出跟随所选输出目录与质量
2026-06-17 15:51:52 +08:00
### 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] 已微调项仍显示其自身参数,不被覆盖
2026-06-17 16:42:20 +08:00
### 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 实测:混合尺寸批量导出,结果图印花位置/大小均正确
2026-06-17 17:05:46 +08:00
### 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] 单测通过;更新源为空/不可达时静默跳过
2026-06-17 17:17:47 +08:00
- [x] GUI 实测:配置可达更新源 + 高版本 manifest → 启动后显示横幅,点击打开目录
2026-06-17 17:12:43 +08:00
- [x] GUI 实测:更新源不可达 → 正常启动、无横幅、无卡顿
2026-06-17 17:05:46 +08:00
2026-06-17 17:29:56 +08:00
### 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)全过
2026-06-18 10:04:29 +08:00
- [x] 接入真实安装结构:新增 `scripts/install_local.ps1` 产出 `%LOCALAPPDATA%\CMBot\app` / `data` / `staging` 启动器布局
2026-06-18 08:54:16 +08:00
- [ ] 真实双机实测
### 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` 同步配置项
2026-06-18 09:43:23 +08:00
- [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`
2026-06-18 09:45:35 +08:00
- [x] 发布流程脚本:build → 打 zip → 算 SHA-256 → 写 manifest → 上传
2026-06-18 10:19:03 +08:00
- [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` 切换)
2026-06-18 08:54:16 +08:00
- [ ] 生产前将更新源切到 HTTPS、客户端改用只读账号
2026-06-18 10:19:03 +08:00
- [ ] 真实环境(cm.xiapi.com)端到端实测
2026-06-17 17:29:56 +08:00
2026-06-18 10:37:48 +08:00
### 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)
2026-06-18 10:39:52 +08:00
- [x] `get_data_dir()` 三级回退:`CMBOT_DATA_DIR` → 打包态 `~/.cmbot` → 开发态项目根;`tests/test_file_service.py` 5 个单测
2026-06-18 10:49:45 +08:00
- [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 真实端到端验证
2026-06-18 10:56:53 +08:00
- [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 跑
2026-06-18 11:05:06 +08:00
- [x] 退休 `scripts/update.ps1` 与 `scripts/install_local.ps1` (已删除,git 历史可查)
2026-06-18 10:37:48 +08:00
- [ ] 端到端实测(解压到 D 盘运行、自更新、回滚)
2026-06-18 11:03:36 +08:00
### 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 对话框
2026-06-18 11:09:29 +08:00
- [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] 取消不改动配置
2026-06-18 11:03:36 +08:00
验收:
2026-06-18 11:09:29 +08:00
- [x] 单测:`load_manifest` 返回 dict / 缺失抛错(2 个);对话框 Qt 符号导入校验通过
- [ ] GUI 实测:改更新地址/账号/密码并持久化到 `~/.cmbot/config/app_config.json`
- [ ] GUI 实测:测试连接正确反馈三种结果
- [ ] GUI 实测:改完无需重启 launcher,app 内横幅按新配置刷新
2026-06-18 11:03:36 +08:00
2026-06-18 12:03:23 +08:00
### 17.21 更新改为非阻塞:app 内下载 + 启动器只应用
前置阅读:
- `docs/10-lan-update.md` (§3 两阶段架构、§6 时机、§8 流程、§12 体验)
2026-06-18 14:02:14 +08:00
- `docs/07-ui-design.md` (4.3 有新版本提示、4.4 设置对话框「检查并更新」)
2026-06-18 12:03:23 +08:00
背景:
- 原启动器在启动时下载更新、阻塞进主界面(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` 重写
2026-06-18 14:02:14 +08:00
- [x] `main_window` :发现新版在 `⚙ 配置` 左侧显示蓝色提示文字(删除旧阻塞横幅与「打开更新目录」)
2026-06-18 12:03:23 +08:00
- [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 对齐
2026-06-18 14:02:14 +08:00
- [ ] GUI 实测:提示文字出现 → 设置里更新 → 重启生效(真机)
2026-06-18 12:03:23 +08:00
2026-06-18 14:26:04 +08:00
### 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 对齐
2026-06-18 14:29:09 +08:00
- [x] `file_service.get_output_dir()` :打包态优先 `<安装根>\合并后的图片` (安装根 = `get_app_dir().parent` ),不可写回退 `get_data_dir()/output` ;开发态用项目目录;`tests/test_file_service.py` 3 个单测
- [x] 导出面板默认值随之变化(沿用 `get_output_dir()` ,无需单独改)
2026-06-18 14:26:04 +08:00
- [ ] GUI 实测:打包运行后默认导出到 `<安装根>\合并后的图片` ,且更新后仍在
2026-06-23 09:33:28 +08:00
### 17.23 批量导出后生成 AI 穿搭 Excel — docs/02 §6.12 / §9
前置阅读:
- `docs/02-prd.md` (§6.9 批量合成导出、§6.12 AI 穿搭 Excel、§9 目录结构)
- `docs/11-ai-outfit.md` (§4 列定义、§4.1 目录行、§9.1 目录行输出)
- `src/app/widgets/queue_panel.py` ( `_start_batch` 、`_new_run_dir` 、`_export_item` )
- `src/services/excel_service.py` ( `write_outfit_result` ,了解现有 openpyxl 写法)
背景:
添加印花批量导出后,输出到 `<合并后的图片>/<时间戳>/<印花名>/` 。AI 穿搭的「C 列支持目录」(§19.12)可直接消费这些子目录,只缺一个指向它们的 Excel 文件。批量结束后自动生成 `<合并后的图片>/<时间戳>.xlsx` (六列格式,每印花子目录一行),用户切到「AI 穿搭」页、选该 Excel,即可直接对合成图批量生成穿搭效果图。
任务:
- [ ] `src/services/excel_service.py` :新增 `write_outfit_source_excel(excel_path, rows)`
- `rows` 为有序列表,每项为 `(print_name: str, subdir_path: str)`
- 写表头 `["标题","货号","衣服图路径","生成结果图片路径","完成状态","失败原因"]` ,数据从第 2 行起
- A=`print_name` , B=空,C=`subdir_path` (末尾含 `/` ),D/E/F=空
- 用 openpyxl 创建新工作簿并保存;不抛出、不改动 AI 穿搭现有函数
- [ ] `src/app/widgets/queue_panel.py` : `_start_batch` 完成后
- 扫描 `run_dir` 的直接子目录,过滤出**包含至少一个图片文件**的(即有成功合成图),按目录名排序
- 若有效子目录 > 0,调用 `write_outfit_source_excel` 生成 `<run_dir>.xlsx` ( `Path(run_dir).with_suffix(".xlsx")` )
- 写入失败只记日志,不影响批量完成的交互反馈
- [ ] `tests/test_excel_service.py` :
- `write_outfit_source_excel` 生成的文件表头正确、行数 = 传入 rows 数、A/B/C 列值正确(A=print_name, B=空,C=subdir 末尾有 `/` )、D/E/F 为空
- [ ] 验收:批量导出完成后 `<合并后的图片>/` 下出现 `<时间戳>.xlsx` ;打开 AI 穿搭、选该文件,预览下拉显示各印花名行(货号列显示「无货号」);「开始生成」触发目录扇出,正常输出穿搭图
2026-06-23 09:37:40 +08:00
### 17.24 批量导出完成弹窗提示 AI 穿搭 Excel — docs/02 §6.12
前置阅读:
- `docs/02-prd.md` (§6.12 AI 穿搭 Excel)
- `src/app/widgets/queue_panel.py` ( `_start_batch` 、`_write_outfit_excel` )
背景:
§17.23 已在批量导出完成后生成同名 xlsx,但用户感知不到。需在 Excel 成功生成后弹出信息框,告知文件路径并引导切换到「AI 穿搭」页使用。
任务:
- [ ] `queue_panel.py` : `_write_outfit_excel` 成功生成 Excel 后返回 `excel_path` ,失败/跳过返回 `None`
- [ ] `_start_batch` 拿到返回值后,若不为 `None` ,调用 `QMessageBox.information` 弹出提示:
- 标题:`AI 穿搭 Excel 已生成`
- 内容:`已生成 AI 穿搭 Excel 文件:\n<excel_path>\n\n可切换到「AI 穿搭」页,选择该文件直接开始生成穿搭图。`
- [ ] 验收:批量导出完成后弹框显示 xlsx 路径;点「确定」关闭;若无有效印花子目录则不弹框
2026-06-16 17:01:58 +08:00
## 18. 后续暂缓任务
2026-06-15 16:09:32 +08:00
以下任务第一阶段暂不做,后续需要时再新增设计文档:
- [ ] 按电脑名控制版本
2026-06-18 11:37:17 +08:00
- [ ] 强制更新与版本保留策略(`mandatory` / `min_supported` 生效、`app.old` 保留策略;docs/10 §16 阶段④)
2026-06-18 17:13:34 +08:00
- [~] AI 穿搭(已转正、进入设计/实现,见 §19)
2026-06-15 16:09:32 +08:00
- [ ] 导出上架流程
- [ ] 自动抠图
- [ ] 自动识别衣服区域
- [ ] 移动端应用
2026-06-18 11:37:17 +08:00
已交付(原属暂缓,本季完成,见 §17.16–§17.20、`docs/10-lan-update.md` ):
- [x] 在线更新(HTTP 检测 + 通知 + `Launcher.exe` 自动安装 + 回滚)
- [x] 局域网/在线分发(便携包 + HTTP 文件服务 + manifest)
- [x] ~~多版本启动器~~ (改为 `app` /`app.old` 单版本切换,不再做多版本并排)
2026-06-18 17:13:34 +08:00
## 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)
2026-06-18 17:26:16 +08:00
- [x] 前置依赖:`requirements.txt` 增加 Python 3.7 兼容的 `requests` / `urllib3` / `openpyxl` 锁定版本,并同步 `docs/03-technical-stack.md`
2026-06-18 17:30:18 +08:00
- [x] `core/models.py` 新增 `OutfitTask` / `OutfitResult` (纯 dataclass, Python 3.7 兼容,不依赖 PySide6)
2026-06-18 17:35:00 +08:00
- [x] `services/excel_service.py` :读行 → `List[OutfitTask]` 、写回 D/E/F、占用检测、跳过「完成」/按设置重试「失败」/空字段安全跳过、每行即存
2026-06-18 17:40:56 +08:00
- [x] `services/ai_image_service.py` :移植旧项目 `ImageApiClient` (多模型、多请求格式 `chat/gemini/images/images_edits` 、传图 data-url、递归取图、URL 归一化、字段校验);注意 PEP585 类型注解改 Python 3.7 写法
2026-06-18 18:03:52 +08:00
- [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
2026-06-18 17:13:34 +08:00
### 19.2 批量编排 — docs/11 §14 阶段 2
前置阅读:`docs/11-ai-outfit.md` (§8)
2026-06-18 18:03:52 +08:00
- [x] `core/outfit_batch.py` 纯逻辑 `OutfitBatchRunner` : `ThreadPoolExecutor(并发数)` + `RateLimiter(请求间隔)` + 单任务冷却 + 阶梯重试 + 温和停止 + >30s 心跳;全可测(`QObject` 包装放 §19.3 UI 接线)
- [x] 子线程只经回调/signal 回主线程,不直接碰控件(runner 用 callback, UI 层转 signal)
2026-06-18 17:13:34 +08:00
### 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`
2026-06-22 08:52:37 +08:00
- [x] 页签栏接 `QStackedWidget` (页 0=添加印花工作区+队列,页 1=AI 穿搭面板);启用页签 1(导出上架仍禁用)
- [x] `app/widgets/ai_outfit_panel.py` :左设置(Excel/输出/模型/话术编辑+保存+插入占位符+预览弹窗/生成设置)、中(最近结果缩略图 + 处理明细表)、右(进度+统计+开始/停止+导出失败清单+打开输出目录+实时日志)
- [x] 接线后台线程:`QThread` + `_OutfitWorker(QObject)` 包 `OutfitBatchRunner` ,signal 回主线程刷日志/进度/明细/缩略图;每行写回 Excel;结束摘要弹窗;失败清单导出 CSV
- [ ] GUI 实测(真机):选 Excel/模型跑通、停止生效、失败清单正确(离屏冒烟已过,待真机)
2026-06-18 17:13:34 +08:00
### 19.4 配置与提示词 — docs/11 §14 阶段 4
前置阅读:`docs/11-ai-outfit.md` (§7、§11)
2026-06-22 08:52:37 +08:00
- [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` ,暂不做)
2026-06-18 17:13:34 +08:00
### 19.5 真机联调 — docs/11 §14 阶段 5
- [ ] 真实中转 API + 小批量 Excel 端到端跑通,调穿搭提示词
- [ ] 验收对照 `docs/11` §16
2026-06-22 10:35:11 +08:00
### 19.6 提示词自动附加「批量生成输出要求」— docs/11 §7.1
前置阅读:`docs/11-ai-outfit.md` (§7.1)
背景:旧项目最终提示词 = 用户话术 + 自动附加的输出要求(参考解析度 / 固定 1:1 / 结合标题与参考图 / 不可跑版);当前 cmbot 的 `render_prompt` 只替换 `{title}` ,缺这段。决策:简体文案、始终自动附加(不做开关)。
2026-06-22 10:40:28 +08:00
- [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` (可选,未做)
2026-06-22 11:26:52 +08:00
### 19.7 切换分辨率 / AI 模型时信息提示 — docs/11 §10.2
前置阅读:`docs/11-ai-outfit.md` (§10.2)
需求:用户手动切换「分辨率」或「AI 模型」下拉时弹信息框告知影响(**只告知、不拦截、不还原**)。决策:每次实际更换都弹一次。
2026-06-22 11:31:56 +08:00
- [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)
2026-06-22 11:52:10 +08:00
### 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` (范式参考)
需求:把单一话术升级为多套命名话术(下拉切换 / 新建 / 另存为 / 重命名 / 删除 / 保存,记住上次)。决策:**全部自定义**(不分内置,播种一套「默认」);**切换前脏数据弹窗**提醒是否保存;**含重命名**。
2026-06-22 14:12:34 +08:00
- [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 文件绿
2026-06-22 15:05:01 +08:00
### 19.9 修复:全表完成后预览样本行下拉为空 — docs/11 §10.3
前置阅读:`docs/11-ai-outfit.md` (§10.3、§7)
问题:预览样本行用 `load_outfit_tasks()` (给生成用、跳过「完成」行)填,整表生成成功后返回空 → 下拉只剩「(选 Excel 后显示替换效果)」。预览应不看状态。
2026-06-22 15:09:56 +08:00
- [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 文件绿
2026-06-22 15:14:57 +08:00
### 19.10 无待处理行提示 + 生成不清空预览样本 — docs/11 §10.4
前置阅读:`docs/11-ai-outfit.md` (§10.4、§10.3)
背景:整表都「完成」后点「开始生成」,当前只显示「已加载 0 行待处理任务 / 完成 0,失败 0」,看不懂;且 `_on_tasks_loaded([])` 会把预览样本下拉清空(破坏 §10.3)。
2026-06-22 15:17:43 +08:00
- [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 文件绿
2026-06-22 15:56:25 +08:00
### 19.11 AI 穿搭按钮配色(对齐添加印花)— docs/11 §10.5
前置阅读:`docs/11-ai-outfit.md` (§10.5)、`src/app/widgets/image_list_panel.py` / `queue_panel.py` / `template_panel.py` (按钮 QSS 范式)
背景:AI 穿搭面板无 stylesheet,按钮全是系统灰;复用印花页的「主操作蓝 / 次级灰 / 危险红」三类按钮规范。
- [x] `ai_outfit_panel.py` :面板 `setObjectName("aiOutfitPanel")` + `_apply_styles()` ( `_build_ui` 末尾调用),QSS 以 `#aiOutfitPanel` 限定作用域,避免波及弹窗按钮
- [x] 主操作蓝(`#openFolderBtn` /`#queueBatchBtn` 同款)= 开始生成(`aiStartBtn` );危险安静红(`#templateDeleteBtn` 同款)= 停止生成(`aiStopBtn` ) + 话术删除(`aiPromptDeleteBtn` );其余走面板内 `QPushButton` 默认次级灰(`#queueActionBtn` /`#templateBtn` 同款)
- [x] 给 开始/停止/话术删除 三个按钮设 objectName;纯样式、不动行为
- [x] 离屏 Qt grab 截图核对:开始=蓝、停止=红(禁用→灰)、话术删除=红、其余=灰描边;面板 stylesheet 非空;全套 12 文件绿
2026-06-22 17:03:04 +08:00
### 19.12 C 列支持「图片目录」→ 多图扇出到同名子目录 — docs/11 §4.1 / §9.1
前置阅读:`docs/11-ai-outfit.md` (§4.1、§9.1)、`src/core/ai_outfit.py` ( `generate_outfit_image` /`make_outfit_output_path` /`safe_product_filename` )、`src/core/outfit_batch.py` 、`src/app/widgets/ai_outfit_panel.py` ( `_OutfitWorker` /`_add_result_thumb` /`_on_progress` /`_basename` )
背景:C 列除单张图片文件外,也可填一个目录(`d:/images/a/` )。对目录内每张图各生成一张穿搭图(N→N),全部存到 `输出目录/a/` ,文件名沿用源图名;Excel 仍整行一个状态:D=子目录、E=全成功才「完成」、F=失败张数/原因。已确认:① N→N;② D 写子目录路径、整行一状态;③ 沿用源图名。
设计取舍:保持「一行=一个 OutfitTask=一个 worker=一次回写」,多图扇出放进 `generate_outfit_image` 内部 → 批处理器/进度/明细表/回写几乎不动。目录内顺序生成、逐张按「新请求间隔」本地节流(并发=1 即全局节流);失败重试跳过已存在输出,幂等。
- [x] `core/ai_outfit.py` : `list_directory_images(dir)` (顶层、扩展名过滤、排序、忽略子目录);`make_outfit_subdir_path(output_dir, subdir, stem)` ( `输出目录/<安全子目录>/<安全源图名>.jpg` ,不加 `_n` 后缀)
- [x] `core/ai_outfit.py` : `generate_outfit_image` 加目录分支(新参 `request_interval` /`image_log` )——列图片、空目录→失败、复用一个 `ImageApiClient` 、逐张跳过已存在/节流/存子目录/发日志、聚合成单个 `OutfitResult` ( `output_path` =子目录、`output_paths` =各 jpg、`success` =全成功且≥1、`error` =失败张数)
- [x] `core/models.py` : `OutfitResult` 加 `output_paths: List[str]` (单文件模式留空)
- [x] `ai_outfit_panel.py` : `gen` 闭包传 `request_interval` /`image_log=self.log.emit` ; `_add_result_thumb` 逐张加缩略图;`_on_progress` 明细「结果」列显示「子目录 (N 张)」;`_basename` 处理目录末尾分隔符
- [x] `tests/test_ai_outfit.py` :扇出/幂等跳过/空目录/部分失败/命名过滤等用例;既有单文件用例保持绿;全套 12 文件 py37 全绿
- [x] 离屏冒烟:临时 Excel 的 C 指向含 3 张图的目录 + mock API,断言 `输出/<目录名>/` 下 3 个 jpg、D=子目录、E=完成、缩略图 3 张
2026-06-22 17:15:05 +08:00
2026-06-22 17:35:56 +08:00
### 19.13 AI 模型配置模板 — docs/11 §6.1 / docs/10 §5
前置阅读:`docs/11-ai-outfit.md` (§6.1、§11)、`docs/10-lan-update.md` (§4、§5)、`src/services/config_service.py` ( `load_ai_models` )、`src/launcher.py` (首次播种)。
背景:当前 AI 模型配置只能由管理员手动创建 `~/.cmbot/config/ai_models.json` 。需要提供出厂模板,使用现有 `api_config.json` 中的两个模型(GPT Image 2 / Nano Banana 2)转换为当前程序可直接读取的 `ai_models.json` 列表结构,但**不得提交真实 API key**。
- [x] 新增 `packaging/default_config/ai_models.json` ,格式为 `{ "models": [...] }` ; `display_name` → `name` ;保留 `url/model/api_type/timeout_seconds/connect_timeout_seconds/extra_body` ; `api_key` 留空或占位
- [x] 更新 `packaging/default_config/app_config.json` ,新增 `outfit_model: "Nano Banana 2"` ,对应旧配置的 `last_selected_model=nano_banana_2`
- [x] 更新 `src/launcher.py` 首次播种白名单,把 `ai_models.json` 与 `app_config.json` 、`templates.json` 一起复制到 `~/.cmbot/config` ;已有用户文件不得覆盖
- [x] 补 `tests/test_launcher.py` :首次播种复制 `ai_models.json` ,已有 `ai_models.json` 不覆盖
- [x] 补配置读取验证:`load_ai_models()` 能读取出厂模板的两个模型名,且模板不含真实 key
- [x] 验证:`python -m unittest discover -s tests` 通过;主窗口启动后 AI 模型下拉可显示模板模型(未填 key 时开始生成仍应提示缺 `api_key` )
2026-06-22 17:15:05 +08:00
### 19.14 货号(B列)改为可选 — docs/11 §4 / §9
前置阅读:`docs/11-ai-outfit.md` (§4、§9、§4.1)、`src/services/excel_service.py` ( `load_outfit_tasks` /`read_all_rows` 完整性判定)、`src/core/ai_outfit.py` ( `make_outfit_output_path` /`generate_outfit_image` 单文件分支)、`src/app/widgets/ai_outfit_panel.py` ( `_fill_sample_combo` )
背景:用户表的「商品id(货号)」整列为空,而行有效性要求 标题+货号+图三者非空 → `read_all_rows` 与 `load_outfit_tasks` 都返回 0 行:预览下拉显示占位符,且「开始生成」也判定无待处理行。货号不进提示词、目录行输出名沿用源图名,对目录用法是多余约束。已确认:货号改可选。
- [x] `excel_service.py` : `read_all_rows` 与 `load_outfit_tasks` 完整性判定改为**只要求 标题 + 衣服图路径**非空;货号可空(状态过滤不变)
- [x] `core/ai_outfit.py` :单文件分支货号为空时输出名回退 `Path(garment_path).stem` (货号非空仍用 `货号.jpg` );目录行不变
- [x] `ai_outfit_panel.py` :样本下拉标签货号为空时显示「(无货号)」,明细表货号列允许空
- [x] `tests/test_excel_service.py` / `test_ai_outfit.py` :空货号行被 `read_all_rows` /`load_outfit_tasks` 收录;单文件空货号 → 输出按源图名命名;既有用例保持绿
- [x] 离屏冒烟:用「标题填、货号空、C=目录」的表,断言预览下拉有行、生成产出到子目录、E=完成;全套 py37 全绿
2026-06-22 17:35:56 +08:00
### 19.15 `ai_models.json` 运行时兜底补种 — docs/11 §6.1 / docs/10 §5
前置阅读:`docs/11-ai-outfit.md` (§6.1)、`docs/10-lan-update.md` (§5)、`src/services/config_service.py` ( `load_ai_models` )、`src/services/file_service.py` ( `get_app_dir` /`get_config_path` )。
背景:19.13 已把 `ai_models.json` 加入出厂模板和新启动器播种白名单。但用户通过自更新升级时,启动器播种发生在应用新版 `app` 之前,读取的是旧版 `app\config` ;且旧 `Launcher.exe` 不自更新,可能根本不知道 `ai_models.json` 。结果新版 `app\config\ai_models.json` 已存在,但 `~/.cmbot/config/ai_models.json` 仍缺失。
设计取舍:由主程序运行时兜底补种新增配置文件。`load_ai_models()` 读取用户配置前,如果 `get_config_path("ai_models.json")` 不存在,则尝试从当前程序根 `get_app_dir()/config/ai_models.json` 复制到用户配置目录;已有用户文件永不覆盖。模板 key 为空,管理员仍需在 `~/.cmbot/config/ai_models.json` 填真实 key。
- [x] `config_service.py` 增加私有 helper(如 `_seed_factory_config_if_missing(filename)` ),只负责「目标不存在时,从当前 `app\config` 复制」
- [x] `load_ai_models()` 在读取前调用该 helper,确保旧 Launcher/自更新场景也能补出 `~/.cmbot/config/ai_models.json`
- [x] helper 只在源文件存在且目标不存在时复制;复制失败记录日志并继续返回空列表,不影响程序启动
- [x] 不覆盖用户已有 `ai_models.json` ,不合并、不重写真实 key
- [x] 补 `tests/test_config_service.py` :缺用户文件 + 有出厂模板 → 自动复制并加载;已有用户文件 → 不覆盖;源模板不存在 → 返回空且不抛异常
- [x] 验证:相关单测和全套 `python -m unittest discover -s tests` 通过;模拟旧 Launcher 场景(只放新版 `app\config\ai_models.json` ,用户目录缺文件)时 AI 模型下拉能显示模板模型
2026-06-22 17:45:00 +08:00
### 19.16 AI 穿搭默认输出目录改为「穿搭图片」 — docs/11 §9 / docs/10 §4-5
前置阅读:`docs/11-ai-outfit.md` (§9、§9.1、§11)、`docs/10-lan-update.md` (§4、§5)、`src/services/file_service.py` (默认目录辅助函数)、`src/app/widgets/ai_outfit_panel.py` (输出目录默认值和配置恢复)、`src/core/ai_outfit.py` (输出路径生成)。
背景:添加印花页默认导出目录是程序旁的 `合并后的图片\` 。AI 穿搭生成的是人物穿搭效果图,继续放到 `合并后的图片\` 容易和印花合成产物混在一起。AI 穿搭应使用独立默认目录 `穿搭图片\` ,仍放在安装根(`Launcher.exe` 旁),便于用户直接查找且不随 `app\` 更新替换。
设计取舍:新增 AI 穿搭专用默认输出目录辅助函数,不改变添加印花页的 `get_output_dir()` 行为;用户已手动选择的 `outfit_output_dir` 继续优先,只有为空时才使用新默认目录。
- [x] 文档已更新:`docs/11-ai-outfit.md` / `docs/10-lan-update.md` / `docs/ui-ai-outfit.html` 均指向 `穿搭图片\`
2026-06-22 17:49:59 +08:00
- [x] `file_service.py` 增加 AI 穿搭默认输出目录辅助函数(如 `get_outfit_output_dir()` ):打包态优先 `<安装根>\穿搭图片` ,不可写回退 `get_data_dir()/output/穿搭图片` ,开发态用项目目录下 `穿搭图片`
- [x] `ai_outfit_panel.py` 默认输出目录改用该 helper; `outfit_output_dir` 非空时仍使用用户保存值
- [x] `core/ai_outfit.py` 目录行输出保持 `AI 穿搭输出目录/<目录叶子名>/<源图名>.jpg` ,单文件行仍按当前输出目录落盘
- [x] 补测试:默认目录路径、不可写回退、AI 穿搭面板默认值、目录行输出到 `穿搭图片/<目录名>/`
- [~] 验证:相关单测通过,离屏启动 AI 穿搭页时输出框默认显示 `穿搭图片` ;全套 `python -m unittest discover -s tests` 当前被工作区未提交的 `packaging/default_config/ai_models.json` 改动阻塞(模型顺序/API key 与出厂模板规范不一致),待清理该文件后重跑
2026-06-22 18:05:26 +08:00
### 19.17 AI 穿搭并发语义改为「行顺序 + 图片并发」 — docs/11 §8 / §9.1 / §10
前置阅读:`docs/11-ai-outfit.md` (§4.1、§8、§9.1、§10)、`src/core/outfit_batch.py` 、`src/core/ai_outfit.py` 、`src/app/widgets/ai_outfit_panel.py` 。
背景:当前「并发数」实际用于 Excel 行任务并发;但现在 C 列常见用法是一行一个图片目录,目录内有多张衣服图。用户期望 Excel 行按顺序处理、每行一次聚合写回,界面并发参数用于控制**当前目录内同时处理多少张图片**。这样既符合「一行=一个子目录」的数据结构,也能把提速点放在同一目录内的多图生成上。
设计取舍:外层 `OutfitBatchRunner` 固定 Excel 行并发为 1;界面文案改为「图片并发数」。配置字段可先兼容复用现有 `outfit_concurrency` ,但业务含义改为目录内图片并发;若后续改字段名,需要迁移旧配置。
- [x] 文档更新:`docs/11-ai-outfit.md` 明确 Excel 行并发固定 1、图片并发数只作用于目录内部;`docs/ui-ai-outfit.html` 示例文案改为「图片并发数」
2026-06-22 18:10:54 +08:00
- [x] `ai_outfit_panel.py` :右侧生成设置 label 从「并发数」改为「图片并发数」,日志启动行同时显示「Excel 行并发 1 / 图片并发 N」
- [x] `_OutfitWorker` / `OutfitBatchRunner` :外层 Excel 行任务固定顺序处理,不再用界面并发值作为行 `max_workers`
- [x] `generate_outfit_image` :增加或接入 `image_concurrency` 参数;单文件行保持顺序单张处理
- [x] `core/ai_outfit.py` 目录分支:使用 `ThreadPoolExecutor(max_workers=图片并发数)` 并发生成目录内图片;已存在输出仍跳过,部分失败仍聚合到整行结果
- [x] 保留「新请求间隔」启动节流;图片并发数 >1 时日志/缩略图完成顺序允许与文件名排序不同
- [x] 补测试:Excel 行顺序处理、目录内图片并发、单文件行不并发、已存在跳过、部分失败聚合、UI label/default/config 兼容
- [~] 验证:语法检查、`test_outfit_batch.py` 、`test_ai_outfit.py` 、`test_ai_outfit_panel.py` 通过;全套 `python -m unittest discover -s tests` 当前被工作区未提交的 `packaging/default_config/ai_models.json` 改动阻塞(模型顺序与出厂模板规范不一致),待清理该文件后重跑
2026-06-23 11:04:38 +08:00
### 19.18 AI 穿搭左栏「标题生成」 — docs/11 §17
前置阅读:
- `docs/11-ai-outfit.md` (§17 标题生成、§7 提示词、§10 界面、§4.1 目录行)
- `src/services/ai_image_service.py` ( `AiModelConfig` /`image_to_data_url` /`detect_api_type` /`normalize_api_url` /`build_payload` /`generate` ,复用 HTTP 管道)
- `src/core/ai_outfit.py` ( `render_prompt` /`looks_like_directory` /`list_directory_images` )
- `src/services/excel_service.py` ( `read_all_rows` /`write_outfit_result` , COL_TITLE)
- `src/services/config_service.py` ( `load_ai_models` /`load_outfit_prompt` /`DEFAULT_CONFIG` )
- `src/app/widgets/ai_outfit_panel.py` ( `_build_left` /`_OutfitWorker` /预览相关方法)
背景:
「添加印花」批量导出的 Excel,A 列「标题」是占位印花名。需在 AI 穿搭页左栏新增**独立**的「生成标题」:用户写标题提示词,AI 看该行衣服图(视觉)生成电商标题,逐行回填 Excel A 列并刷新 GUI;之后「开始生成」跑图即用新标题。已确认:写回 A 列;独立按钮;看图(视觉);独立「标题模型」下拉;逐行各生成 1 条、第 n 条回填第 n 行 A、重载刷新 GUI。
任务:
2026-06-23 11:44:19 +08:00
- [x] `src/services/ai_text_service.py` (新建):`AiTextClient(config, session=None)` 复用 `ai_image_service` 管道;`generate_text(prompt, image_path=None)` ( chat/gemini 带图视觉文本输出,images/images_edits 抛 `AiTextServiceError` );`extract_text_from_response` ( chat/gemini 取文本,返回第一条标题:首个非空行、去序号/引号、单行化)
- [x] `src/core/ai_title.py` (新建):`render_title_prompt(template, task)` (替换 `{title}` /`{product_id}` ,不加图片输出要求尾巴);`generate_title(task, prompt_template, model_config, api_client=None) -> TitleResult` (目录行取 `list_directory_images` 首图,无图失败,never raises)
- [x] `src/core/models.py` :新增 `TitleResult` ( task/success/generated_title/error/attempts)
- [x] `src/services/excel_service.py` :新增 `write_title_result(excel_path, row_index, title)` (只写 A 列并保存,不动 D/E/F)
- [x] `src/services/config_service.py` : `DEFAULT_CONFIG` 增 `title_model` ;新增 `load_title_prompt` /`save_title_prompt` ( `title_prompt.txt` ) + `DEFAULT_TITLE_PROMPT`
- [x] `src/app/widgets/ai_outfit_panel.py` :移除预览块(`_sample_combo` /`_preview_*` /`_refresh_preview` /`_fill_sample_combo` /`_reload_sample_rows` 及信号连接);新增「标题生成」组(提示词编辑 + `标题模型` 下拉 + 保存 + 生成标题)置于话术组上方;`apply_config` /`_emit_config` 接 `title_model` +标题提示词;`_TitleWorker` 顺序逐行生成+立即回填+刷新明细表标题列,完成后重载 Excel;「生成标题」与「开始生成」互斥
- [x] 测试:`tests/test_ai_text_service.py` (文本解析含多行只取第一条、images_edits 抛错、payload 含图);`tests/test_ai_title.py` (render、单文件/目录首图/无图失败/异常);`tests/test_excel_service.py` 加 `write_title_result` 只改 A;删预览后 `ai_outfit_panel` 离屏可构建
- [x] 验证:相关单测 + 全套 py37 通过(`test_config_service` 的 `ai_models.json` 顺序失败属并行 §19.13 遗留,与本改动无关);离屏冒烟(mock `AiTextClient` ):选印花 Excel → 生成标题 → A 列改写、明细表刷新、D/E/F 未动、面板无预览控件
> 已提交 `2fa488b`(标题模型当时为下拉,§19.19 改为配置定名)。
### 19.19 标题模型改为「配置定名」、去掉下拉 — docs/11 §17.3 / §17.6
前置阅读:
- `docs/11-ai-outfit.md` (§17.3 模型配置定名、§17.6 决策、§10 左栏)
- `src/app/widgets/ai_outfit_panel.py` ( `_build_title_group` /`_fill_model_combo` /`_selected_title_model_config` /`apply_config` /`_emit_config` /`_set_title_running` )
- `src/services/config_service.py` ( `DEFAULT_CONFIG` 的 `title_model` )
- `docs/ai_models.sample.json` ( chat 文本模型示例)
背景:
§19.18 给标题模型放了独立下拉。复盘改为「配置定名、去下拉」:标题模型是「配一次就固定」的,不像分辨率/话术需要每次切换;左栏 ~360px 已叠两个提示词组,少一个下拉更干净;还消掉「误选图片模型」的坑。模型名放 `app_config.title_model` (默认 `GPT-5.5 文本` ),运行时按名字查 `ai_models.json` ,换模型只改配置不改代码。
任务:
2026-06-23 14:30:57 +08:00
- [x] `ai_outfit_panel.py` : `_build_title_group` 去掉「标题模型」下拉(`_title_model_combo` 及其 `_compact_combo` /填充/`_set_title_running` 里的禁用);`apply_config` 不再填标题下拉、`_emit_config` 不再写 `title_model` (值由配置/手动维护,UI 不覆盖)
- [x] `ai_outfit_panel.py` : `_selected_title_model_config()` 改为 `_resolve_title_model_config()` ——读 `app_config.title_model` 名字,在 `self._models` 里按 `name` 查;命中校验 `api_config_errors` 后返回 `AiModelConfig` ;找不到/未配置时 `QMessageBox.warning` 明确报错(提示加文本模型并设 `title_model` )。`_start_title` 改用它;面板需持有当前 `title_model` 值(`apply_config` 存一份 `self._title_model_name` )
- [x] `config_service.py` : `DEFAULT_CONFIG["title_model"]` 默认值由 `""` 改为 `"GPT-5.5 文本"` ;注释改为「标题模型名字(对应 ai_models.json 的 name,§17.3 配置定名)」
- [x] `docs/ai_models.sample.json` :补一条 `name="GPT-5.5 文本"` 、`api_type=chat` 、`model=gpt-5.5` (占位 key)的文本模型示例,作为标题模型样板
- [x] 测试:更新 `test_ai_outfit_panel.py` (断言**无**标题模型下拉、`_title_model_combo` 不存在);新增 `_resolve_title_model_config` 命中/缺失(报错返回 None)用例
- [x] 验证:相关单测 + 全套 py37 通过;离屏冒烟:`title_model` 命中 `ai_models.json` → 生成走该模型;改名/缺失 → 明确报错
### 19.20 穿搭话术:按钮行微调 +「插入标题」保留 + 预览改弹窗 — docs/11 §7 / §7.3 / §10
前置阅读:
- `docs/11-ai-outfit.md` (§7 提示词、§7.1 输出要求、§7.2 多套模板、§7.3 预览弹窗、§10 界面)
- `src/core/ai_outfit.py` ( `render_prompt` /`build_output_requirements` )
- `src/app/widgets/ai_outfit_panel.py` ( `_build_left` 话术组、`_prompt_edit` 、模板按钮行、`_save_prompt` /`_store_current_text` 、`_start` /`_OutfitWorker` 、§19.18 删掉的样本下拉/预览逻辑可参考)
- `tests/test_ai_outfit.py` ( `render_prompt` 既有用例,**不改 render_prompt,保持原样**)
背景:
话术组小改版(用户最终确认):①「保存话术」→「保存」、移到模板按钮行「重命名」之后(行变 `新建/另存为/重命名/保存/删除` );② **保留「插入标题」** + `{title}` 占位符(标题不自动前置——用户自定位置、不与旧话术重复);③ 「最终提示词预览」因左栏空间紧张(标题生成组 + 话术组并存)**改为按需弹窗**:编辑框下方「预览最终提示词」按钮 → 非模态 `QDialog` ,内含数据行下拉 + 只读替换后提示词(含 §7.1 输出要求)。`render_prompt` 与 `DEFAULT_OUTFIT_PROMPT` **不改** 。
任务:
- [x] `ai_outfit_panel.py` 模板按钮行:在「重命名」与「删除」之间插入「保存」(连 `_save_prompt` );删掉编辑框下方原「保存话术」按钮;**保留**编辑框下方「插入标题」按钮,旁边新增「预览最终提示词」按钮
- [x] `ai_outfit_panel.py` 预览弹窗(新 `_OutfitPreviewDialog` 或方法):非模态 `QDialog` ,含数据行下拉(`_compact_combo` + `read_all_rows(excel)` 填,状态无关)+ 只读 `QPlainTextEdit` ;内容 = `render_prompt(话术正文, 选中行, 当前分辨率)` ;缺 `{title}` 时顶部提示;未选 Excel/无行 → 显示话术原文 + 提示
- [x] `ai_outfit_panel.py` 弹窗联动:话术 `textChanged` / 分辨率 `currentIndexChanged` / 数据行下拉变化 → 刷新弹窗预览(仅弹窗存在时);弹窗用 `_reload_sample_rows` 思路在打开时/选 Excel 后填行
- [x] `ai_outfit_panel.py` 生成:保持现有 `_start` 的「缺 `{title}` 弹窗询问是否继续」(标题靠占位符);不引入自动前置
- [x] 测试:面板离屏断言——按钮行含「保存」、编辑框下方仍有「插入标题」、有「预览最终提示词」按钮;打开预览弹窗后选数据行/改分辨率,预览文本 = `render_prompt` 结果(含输出要求);`render_prompt` 既有用例保持绿(不改该函数)
2026-06-23 14:59:04 +08:00
- [x] 验证:相关单测 + 全套 py37 通过;离屏冒烟:点「预览最终提示词」弹窗 → 选数据行标题替换、切分辨率输出要求刷新;左栏不因预览常驻而需要滚动
### 19.21 标题生成:命中图片模型时开跑前拦截 — docs/11 §17.3 / §17.5
前置阅读:
- `docs/11-ai-outfit.md` (§17.3 模型配置定名、§17.5 验收)
- `src/app/widgets/ai_outfit_panel.py` ( `_find_title_model` /`_resolve_title_model_config` /`_start_title` )
- `src/services/ai_image_service.py` ( `API_IMAGES` /`API_IMAGES_EDITS` /`detect_api_type` )
- `tests/test_ai_outfit_panel.py` ( `_find_title_model` 命中/缺失用例)
背景:
现状①完全没配模型、②`title_model` 名字找不到对应条目,都已在 `_find_title_model` 开跑前弹窗+中止。缺口是③:名字**命中了、但那条是图片模型**(`api_type=images` /`images_edits` ,如误指到 GPT Image 2)——当前不在开跑前拦,会开跑后**逐行失败**(日志/明细写「该模型是图片接口…」,不烧 API)。补一个开跑前拦截,体验对齐①②。
边界:`api_type=chat` 但实际返回图片的模型(如 Nano Banana)类型上判不出,仍只能运行时由「未找到文字标题」逐行暴露——固有限制,不在本任务范围。
任务:
- [x] `ai_outfit_panel.py` `_find_title_model` :命中条目后,用 `detect_api_type(url, api_type)` 判类型;若 ∈ {`API_IMAGES` , `API_IMAGES_EDITS` } → 返回错误「『{名字}』是图片模型({api_type}),不能生成文字标题,请改选 chat/gemini 文本模型」(`config` 为 None)。校验顺序:先 `api_config_errors` ,再图片类型判定
- [x] 行为不变确认:`_resolve_title_model_config` 拿到 error 仍是 `QMessageBox.warning` + 返回 None; `_start_title` 据此中止(已有逻辑,无需改)
- [x] 测试:`test_ai_outfit_panel.py` 加用例——`title_model` 命中 `images_edits` 模型 → `_find_title_model` 返回 `(None, 含「图片模型」的错误)` ;既有命中(chat)/缺失用例保持绿
- [x] 验证:相关单测 + 全套 py37 通过;离屏冒烟:`title_model` 指向图片模型 → 点「生成标题」前即弹窗中止、不开跑