docs: set outfit output directory

This commit is contained in:
2026-06-22 17:45:00 +08:00
parent 74338c383e
commit e19eef5b43
4 changed files with 32 additions and 12 deletions
+7 -4
View File
@@ -67,7 +67,8 @@
config\ # 出厂默认配置/模板,首次运行播种到 ~/.cmbot(见第 5 节) config\ # 出厂默认配置/模板,首次运行播种到 ~/.cmbot(见第 5 节)
app.old\ # 上一个可用版本,仅用于回滚,可不存在 app.old\ # 上一个可用版本,仅用于回滚,可不存在
staging\ # 下载/解压临时区,安装成功后清理 staging\ # 下载/解压临时区,安装成功后清理
合并后的图片\ # 默认导出目录(就在程序旁、好找、不随更新替换;可在导出面板改) 合并后的图片\ # 添加印花默认导出目录(就在程序旁、好找、不随更新替换;可在导出面板改)
穿搭图片\ # AI 穿搭默认输出目录(就在程序旁、好找、不随更新替换;可在 AI 穿搭页改)
%USERPROFILE%\.cmbot\ # 用户数据,独立于程序位置,始终可写、按用户隔离 %USERPROFILE%\.cmbot\ # 用户数据,独立于程序位置,始终可写、按用户隔离
config\ config\
@@ -75,7 +76,7 @@
templates.json # 用户自定义模板 templates.json # 用户自定义模板
ai_models.json # AI 穿搭模型配置(管理员填写 key,首次可由出厂模板播种) ai_models.json # AI 穿搭模型配置(管理员填写 key,首次可由出厂模板播种)
logs\ logs\
output\ # 默认导出目录的回退位置(安装根不可写时) output\ # 默认导出目录的回退位置(安装根不可写时,AI 穿搭回退到 output\穿搭图片)
``` ```
说明: 说明:
@@ -86,7 +87,8 @@
- `app\config\`:出厂默认配置/模板,仅作首次运行的播种来源,运行时不读写。 - `app\config\`:出厂默认配置/模板,仅作首次运行的播种来源,运行时不读写。
- `app.old\`:上一个可用程序目录。更新失败或新版启动异常时,可把它改回 `app\` 完成回滚。 - `app.old\`:上一个可用程序目录。更新失败或新版启动异常时,可把它改回 `app\` 完成回滚。
- `staging\`:下载的 zip 与解压临时目录。校验通过后,`staging\app.new\` 才会切换为 `app\`。 - `staging\`:下载的 zip 与解压临时目录。校验通过后,`staging\app.new\` 才会切换为 `app\`。
- `合并后的图片\`:**默认导出目录**,放在安装根(`Launcher.exe` 旁),便于用户直接找到合成结果,且不随 `app\` 更新替换。用户可在导出面板改成任意目录;安装根不可写时回退 `~/.cmbot/output`。 - `合并后的图片\`:**添加印花默认导出目录**,放在安装根(`Launcher.exe` 旁),便于用户直接找到合成结果,且不随 `app\` 更新替换。用户可在导出面板改成任意目录;安装根不可写时回退 `~/.cmbot/output`。
- `穿搭图片\`:**AI 穿搭默认输出目录**,同样放在安装根(`Launcher.exe` 旁),用于保存 AI 生成的人物穿搭图;与 `合并后的图片\` 分开,避免两类产物混在一起。用户可在 AI 穿搭页改成任意目录;安装根不可写时回退 `~/.cmbot/output/穿搭图片`。
- `~/.cmbot\`:配置/模板/日志集中存放,**与程序位置、版本切换均无关**。即便 `app\` 所在目录只读(更新失败),配置/模板仍可正常写。 - `~/.cmbot\`:配置/模板/日志集中存放,**与程序位置、版本切换均无关**。即便 `app\` 所在目录只读(更新失败),配置/模板仍可正常写。
- 安装根(含 `app\`、`app.old\`、`staging\`、`Launcher.exe`)需免提权可写**才能自更新**;不可写时仅「更新失败」,不影响数据读写。 - 安装根(含 `app\`、`app.old\`、`staging\`、`Launcher.exe`)需免提权可写**才能自更新**;不可写时仅「更新失败」,不影响数据读写。
@@ -102,7 +104,8 @@
- 路径函数划分: - 路径函数划分:
- `get_resource_path()` 指向**程序根**下的 `resources/`。 - `get_resource_path()` 指向**程序根**下的 `resources/`。
- `get_config_path()`、`get_log_dir()` 指向**数据根**下的对应目录。 - `get_config_path()`、`get_log_dir()` 指向**数据根**下的对应目录。
- `get_output_dir()`(默认导出目录)特例:打包态优先返回 `<安装根>\合并后的图片`(就在程序旁、好找、不随更新替换),不可写时回退数据根 `output\`;开发态用项目目录。用户在导出面板的选择(`output_dir`)仍优先。 - `get_output_dir()`(添加印花默认导出目录)特例:打包态优先返回 `<安装根>\合并后的图片`(就在程序旁、好找、不随更新替换),不可写时回退数据根 `output\`;开发态用项目目录。用户在导出面板的选择(`output_dir`)仍优先。
- AI 穿搭默认输出目录独立于 `get_output_dir()`:打包态优先返回 `<安装根>\穿搭图片`,不可写时回退 `~/.cmbot/output/穿搭图片`;开发态用项目目录下的 `穿搭图片`。用户在 AI 穿搭页选择的 `outfit_output_dir` 仍优先。
- 数据根解析规则(`get_data_dir()`,三级回退): - 数据根解析规则(`get_data_dir()`,三级回退):
1. 环境变量 `CMBOT_DATA_DIR` 非空时使用它(覆盖口,供测试或特殊部署)。 1. 环境变量 `CMBOT_DATA_DIR` 非空时使用它(覆盖口,供测试或特殊部署)。
2. 打包态(`sys.frozen`)→ `~/.cmbot`(即 `%USERPROFILE%\.cmbot`)。**不依赖启动器注入环境变量**:即使用户绕过 `Launcher.exe` 直接双击 `app\CMBot.exe`,数据也落在 `~/.cmbot`。 2. 打包态(`sys.frozen`)→ `~/.cmbot`(即 `%USERPROFILE%\.cmbot`)。**不依赖启动器注入环境变量**:即使用户绕过 `Launcher.exe` 直接双击 `app\CMBot.exe`,数据也落在 `~/.cmbot`。
+6 -4
View File
@@ -79,7 +79,7 @@ C 列除了单张图片文件,**也可以是一个目录**(如 `d:/images/a/
(扩展名 `.png/.jpg/.jpeg/.webp/.gif`),**不递归**子目录,按文件名排序。 (扩展名 `.png/.jpg/.jpeg/.webp/.gif`),**不递归**子目录,按文件名排序。
- 该行仍是**一个任务、一次回写**;对目录内**每一张**图各生成一张穿搭图(N→N), - 该行仍是**一个任务、一次回写**;对目录内**每一张**图各生成一张穿搭图(N→N),
共用本行的标题/货号与话术。 共用本行的标题/货号与话术。
- 输出落到 `输出目录/<目录叶子名>/`(见 §9.1);Excel 回写仍是**整行一个状态**: - 输出落到 `AI 穿搭输出目录/<目录叶子名>/`(默认是程序旁的 `穿搭图片\`,见 §9.1);Excel 回写仍是**整行一个状态**:
D=子目录绝对路径、E=全部成功才 `完成` 否则 `失败`、F=失败张数/原因。 D=子目录绝对路径、E=全部成功才 `完成` 否则 `失败`、F=失败张数/原因。
- 目录不存在或目录内没有图片 → 该行 `失败` 并记录原因,不中断其它行。 - 目录不存在或目录内没有图片 → 该行 `失败` 并记录原因,不中断其它行。
@@ -223,7 +223,8 @@ Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只
## 9. 输出 ## 9. 输出
- 格式 JPG,1:1,压缩到 ≤2MB;质量三档(小文件 75 / 均衡 85 / 高清 92)。 - 格式 JPG,1:1,压缩到 ≤2MB;质量三档(小文件 75 / 均衡 85 / 高清 92)。
- 默认输出目录沿用 cmbot `get_output_dir()`(程序旁的「合并后的图片」,见 `docs/10` §5),界面可改。 - AI 穿搭使用独立默认输出目录:打包态优先为安装根(`Launcher.exe` 旁)的 `穿搭图片\`,不可写时回退到 `~/.cmbot/output/穿搭图片\`;开发态可使用项目目录下的 `穿搭图片\`。界面仍可改为任意目录,用户选择值继续通过 `outfit_output_dir` 记住。
- `穿搭图片\` 与添加印花页的 `合并后的图片\` 分开,避免两类产物混在一起;二者都不放在 `app\` 内,避免自更新替换 `app\` 时误删输出。
- 命名:`货号.jpg`,重名自动 `_1`/`_2`,非法字符替换为 `_`(不改 Excel 原始货号)。**货号为空时命名回退用源图名**(`<源图名>.jpg`,与目录行一致)。 - 命名:`货号.jpg`,重名自动 `_1`/`_2`,非法字符替换为 `_`(不改 Excel 原始货号)。**货号为空时命名回退用源图名**(`<源图名>.jpg`,与目录行一致)。
- 成功后把**实际新图绝对路径**写回 Excel D 列。 - 成功后把**实际新图绝对路径**写回 Excel D 列。
@@ -231,9 +232,9 @@ Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只
当 C 列是目录(§4.1)时: 当 C 列是目录(§4.1)时:
- 每张源图各生成一张穿搭图,存到 `输出目录/<目录叶子名>/<源图名>.jpg` - 每张源图各生成一张穿搭图,存到 `AI 穿搭输出目录/<目录叶子名>/<源图名>.jpg`
(子目录名 = 目录叶子名,文件名沿用源图名;二者均按 §9 规则替换非法字符)。 (子目录名 = 目录叶子名,文件名沿用源图名;二者均按 §9 规则替换非法字符)。
例:`d:/images/a/img1.png` → `输出目录/a/img1.jpg`。 例:`d:/images/a/img1.png` → `穿搭图片/a/img1.jpg`。
- **不加 `_1`/`_2` 去重后缀**:目标文件已存在视为「已生成」并**跳过**,使整行可 - **不加 `_1`/`_2` 去重后缀**:目标文件已存在视为「已生成」并**跳过**,使整行可
幂等重试——重试只补做缺失/失败的那几张,已成功的不重复调 API。 幂等重试——重试只补做缺失/失败的那几张,已成功的不重复调 API。
- 整行回写 Excel 时,D 列写**子目录**绝对路径(而非单个文件)。 - 整行回写 Excel 时,D 列写**子目录**绝对路径(而非单个文件)。
@@ -344,6 +345,7 @@ Excel 行 → `OutfitTask` 列表的转换由 `excel_service` 完成;核心只
- 发布包可附带 `app\config\ai_models.json` 出厂模板(见 §6.1),用于首次播种;模板中的 `api_key` 必须为空或占位,真实 key 只写入用户数据目录。 - 发布包可附带 `app\config\ai_models.json` 出厂模板(见 §6.1),用于首次播种;模板中的 `api_key` 必须为空或占位,真实 key 只写入用户数据目录。
- **提示词** → `~/.cmbot/config/outfit_prompt.txt`。 - **提示词** → `~/.cmbot/config/outfit_prompt.txt`。
- **批量设置 + 上次 Excel/输出路径** → 并入 `app_config.json`(`config_service` 集中读写,UI 不直接读写配置文件,遵守 `docs/04` 第 6 节 / `docs/05` 4.12)。 - **批量设置 + 上次 Excel/输出路径** → 并入 `app_config.json`(`config_service` 集中读写,UI 不直接读写配置文件,遵守 `docs/04` 第 6 节 / `docs/05` 4.12)。
- **AI 穿搭默认输出目录** → 安装根旁的 `穿搭图片\`(不可写时回退 `~/.cmbot/output/穿搭图片\`);用户手动选择的目录存入 `app_config.json` 的 `outfit_output_dir`。
- 失败记录 / 日志沿用 `~/.cmbot/logs` 与现有日志服务。 - 失败记录 / 日志沿用 `~/.cmbot/logs` 与现有日志服务。
## 12. 依赖与兼容 ## 12. 依赖与兼容
+4 -4
View File
@@ -228,7 +228,7 @@
<span class="chipmeta err">失败 3</span> <span class="chipmeta err">失败 3</span>
</div> </div>
<div class="frow"><span class="flbl">输出</span> <div class="frow"><span class="flbl">输出</span>
<div class="path"><span class="p">D:\\CMBot\\合并后的图片</span><span class="br">…</span></div> <div class="path"><span class="p">D:\\CMBot\\穿搭图片</span><span class="br">…</span></div>
</div> </div>
<div class="grp"><div class="grp-t">通用话术</div> <div class="grp"><div class="grp-t">通用话术</div>
@@ -318,8 +318,8 @@
<th class="bidx">行</th><th>标题</th><th>货号</th><th>衣服图</th><th>状态</th><th>结果 / 原因</th> <th class="bidx">行</th><th>标题</th><th>货号</th><th>衣服图</th><th>状态</th><th>结果 / 原因</th>
</tr></thead> </tr></thead>
<tbody> <tbody>
<tr><td class="bidx">2</td><td>纯棉宽松短袖T恤</td><td>TY027</td><td><span class="gthumb"></span>TY027_白底.png</td><td><span class="pill done">完成</span></td><td><span class="lnk">合并后的图片\\TY027.jpg</span></td></tr> <tr><td class="bidx">2</td><td>纯棉宽松短袖T恤</td><td>TY027</td><td><span class="gthumb"></span>TY027_白底.png</td><td><span class="pill done">完成</span></td><td><span class="lnk">穿搭图片\\TY027.jpg</span></td></tr>
<tr><td class="bidx">3</td><td>美式复古印花卫衣</td><td>TY028</td><td><span class="gthumb d"></span>TY028_白底.png</td><td><span class="pill done">完成</span></td><td><span class="lnk">合并后的图片\\TY028.jpg</span></td></tr> <tr><td class="bidx">3</td><td>美式复古印花卫衣</td><td>TY028</td><td><span class="gthumb d"></span>TY028_白底.png</td><td><span class="pill done">完成</span></td><td><span class="lnk">穿搭图片\\TY028.jpg</span></td></tr>
<tr><td class="bidx">4</td><td>高腰显瘦牛仔裤</td><td>TY029</td><td><span class="gthumb" style="background:#4a6b8a"></span>TY029_白底.png</td><td><span class="pill err">失败</span></td><td class="muted">衣服图打不开:文件不存在</td></tr> <tr><td class="bidx">4</td><td>高腰显瘦牛仔裤</td><td>TY029</td><td><span class="gthumb" style="background:#4a6b8a"></span>TY029_白底.png</td><td><span class="pill err">失败</span></td><td class="muted">衣服图打不开:文件不存在</td></tr>
<tr class="sel"><td class="bidx">13</td><td>碎花连衣裙夏季新款</td><td>TY030</td><td><span class="gthumb"></span>TY030_白底.png</td><td><span class="pill run">生成中</span></td><td class="muted">第 1 次尝试 · 78%</td></tr> <tr class="sel"><td class="bidx">13</td><td>碎花连衣裙夏季新款</td><td>TY030</td><td><span class="gthumb"></span>TY030_白底.png</td><td><span class="pill run">生成中</span></td><td class="muted">第 1 次尝试 · 78%</td></tr>
<tr><td class="bidx">14</td><td>设计感小众衬衫</td><td>TY031</td><td><span class="gthumb d"></span>TY031_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr> <tr><td class="bidx">14</td><td>设计感小众衬衫</td><td>TY031</td><td><span class="gthumb d"></span>TY031_白底.png</td><td><span class="pill wait">待处理</span></td><td class="muted">—</td></tr>
@@ -394,7 +394,7 @@
<div class="status"> <div class="status">
<div class="s"><span class="dot v"></span>数据源:<b>商品表.xlsx</b></div><div class="vsep"></div> <div class="s"><span class="dot v"></span>数据源:<b>商品表.xlsx</b></div><div class="vsep"></div>
<div class="s">模型:<b>Nano Banana 2</b></div><div class="vsep"></div> <div class="s">模型:<b>Nano Banana 2</b></div><div class="vsep"></div>
<div class="s">输出:<b>合并后的图片</b></div> <div class="s">输出:<b>穿搭图片</b></div>
<div class="sp"></div> <div class="sp"></div>
<div class="s"><span class="dot a"></span>生成中 15/60</div><div class="vsep"></div> <div class="s"><span class="dot a"></span>生成中 15/60</div><div class="vsep"></div>
<div class="s" style="color:#c42b1c">1 项失败</div><div class="vsep"></div> <div class="s" style="color:#c42b1c">1 项失败</div><div class="vsep"></div>
+15
View File
@@ -1200,3 +1200,18 @@
- [x] 不覆盖用户已有 `ai_models.json`,不合并、不重写真实 key - [x] 不覆盖用户已有 `ai_models.json`,不合并、不重写真实 key
- [x] 补 `tests/test_config_service.py`:缺用户文件 + 有出厂模板 → 自动复制并加载;已有用户文件 → 不覆盖;源模板不存在 → 返回空且不抛异常 - [x] 补 `tests/test_config_service.py`:缺用户文件 + 有出厂模板 → 自动复制并加载;已有用户文件 → 不覆盖;源模板不存在 → 返回空且不抛异常
- [x] 验证:相关单测和全套 `python -m unittest discover -s tests` 通过;模拟旧 Launcher 场景(只放新版 `app\config\ai_models.json`,用户目录缺文件)时 AI 模型下拉能显示模板模型 - [x] 验证:相关单测和全套 `python -m unittest discover -s tests` 通过;模拟旧 Launcher 场景(只放新版 `app\config\ai_models.json`,用户目录缺文件)时 AI 模型下拉能显示模板模型
### 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` 均指向 `穿搭图片\`
- [ ] `file_service.py` 增加 AI 穿搭默认输出目录辅助函数(如 `get_outfit_output_dir()`):打包态优先 `<安装根>\穿搭图片`,不可写回退 `get_data_dir()/output/穿搭图片`,开发态用项目目录下 `穿搭图片`
- [ ] `ai_outfit_panel.py` 默认输出目录改用该 helper;`outfit_output_dir` 非空时仍使用用户保存值
- [ ] `core/ai_outfit.py` 目录行输出保持 `AI 穿搭输出目录/<目录叶子名>/<源图名>.jpg`,单文件行仍按当前输出目录落盘
- [ ] 补测试:默认目录路径、不可写回退、AI 穿搭面板默认值、目录行输出到 `穿搭图片/<目录名>/`
- [ ] 验证:相关单测和全套 `python -m unittest discover -s tests` 通过;离屏启动 AI 穿搭页时输出框默认显示 `穿搭图片`