Commit Graph
89 Commits
Author SHA1 Message Date
admin f033943db0 fix: seed outfit factory config 2026-06-23 17:51:46 +08:00
admin cad8b61bcf docs: define outfit factory config seeding 2026-06-23 17:45:52 +08:00
adminandClaude Opus 4.8 a9360f9421 fix(ai-outfit): 标题生成对「标题(A)空」的印花表不再误报无行 (§19.27)
catch-22:「生成标题」用 read_all_rows 取行,而它要求 标题(A)+原始图片路径(C)
都非空;但标题生成的目的就是填 A。印花导出表若 A 空(被清空/早期导出),
所有行被跳过 → 误报「该表没有可处理的行」。实测 output/20260623_094529.xlsx:
A 全空、C 是印花子目录。

- excel_service.read_all_rows 增 require_title=True 开关:False 时只在
  原始图片路径(C) 空时跳过、允许 标题(A) 空;默认 True 行为不变(预览/
  无待处理统计/图片生成 load_outfit_tasks 仍要求 A)
- ai_outfit_panel._TitleWorker.run 改调 read_all_rows(..., require_title=False)

测试:A 空+C 有的行 require_title=False 收录、默认跳过;C 空两种都跳过。
全套 py37 通过(test_config_service 的 packaging 模板失败属并行 §19.13,无关)。
离屏冒烟:真表 20260623_094529.xlsx 默认 0 行→require_title=False 加载 6 行;
worker 把 6 条标题按序回填 A、C(印花目录)不动。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:35:13 +08:00
adminandClaude Opus 4.8 be1eb9ee36 style(ai-outfit): 明细表再缩「衣服图/结果·原因」、标题更宽 (§19.26)
承接 §19.25:_configure_detail_table_columns 把「衣服图」(col3) 初值
150→90、「结果/原因」(col5) 220→130(仍 Interactive 可手拖);「标题」
Stretch 自动吸收省下的宽度变更宽,短固定列(行/货号/状态)不变。

测试:列宽用例补断言 columnWidth(3)<=90、columnWidth(5)<=130。
全套 py37 通过(test_config_service 的 packaging 模板失败属并行 §19.13,无关)。
离屏冒烟:衣服图=90 / 结果·原因=130(Interactive)、标题 Stretch。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 17:09:42 +08:00
admin 04bc587538 fix: adjust outfit detail table widths 2026-06-23 16:58:16 +08:00
adminandClaude Opus 4.8 7313c8a443 fix(ai-outfit): 停止生成打断当前目录行 + 「停止中…」反馈 (§19.24)
修「开始生成→几秒后停止→两个按钮都灰几十秒像卡死」:非死锁,是温和停止
在等当前目录行收尾,而 _generate_directory_outfit 一次性 submit 全部图片、
shutdown(wait=True) 等全跑完,停止信号进不到该循环;且 _stop 后无反馈。

- outfit_batch:run() 经 _accepts_should_stop 给 generate_func 传 should_stop
  (= _stop_event.is_set),2-arg 才传、1-arg 仍兼容(含既有 mock)
- ai_outfit:generate_outfit_image/_generate_directory_outfit 增 should_stop;
  目录循环改有界提交——始终最多 image_concurrency 张在飞,每张完成后、提交
  下一张前查 should_stop,已停止则停止提交、在飞收尾即返回(结果注明
  「已停止,N 张未生成」,已生成的带 output_paths)
- 面板:_OutfitWorker.gen(task, should_stop) 透传;_stop() 把停止按钮改
  「停止中…」;_set_running(False) 复位「停止生成」

测试:目录行 should_stop 触发后只生成到停止点、返回「已停止」结果;2-arg
generate_func 收到 should_stop;既有用例(1-arg mock、目录并发/部分失败)保持绿。
全套 py37 通过(test_config_service 的 packaging 模板失败属并行 §19.13,无关)。
离屏冒烟:stop 截断 run 且 runner 及时返回;面板停止按钮「停止中…」→结束复位。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:46:49 +08:00
adminandClaude Opus 4.8 61d8e20d86 fix(ai-outfit): 标题解析改为「按逗号分割」,避开 Markdown 表格坑 (§19.23)
实测模型常返回 Markdown 表格(編號/標題/字元數估算 三列),按行解析会把
表头/分隔线/带 | 数字的行当成标题,写进 Excel A 全是「| 編號 | 標題 | … |」。
改用「提示词约定标题逗号分隔 + 解析按逗号拆分」,简单可靠。

- ai_text_service._clean_titles:从按行拆改为按逗号拆(半角 , / 全角 ,,
  换行作兜底分隔),每段走 _clean_title_line 去空白/序号/引号、丢空
- config_service.DEFAULT_TITLE_PROMPT:改逗号分隔风格,明确禁 Markdown 表格/
  换行/序号/編號/字元數/引号/表情
- extract_text_from_response(取首条)、generate_texts/generate_titles/
  _TitleWorker 均不变

测试:逗号分隔(全角/半角)→ 多条;逗号+换行混用都拆开;逐段去序号/引号;
Markdown 表头单行不再炸成多条垃圾。全套 py37 通过(test_config_service 的
packaging 模板失败属并行 §19.13,与本改动无关)。离屏冒烟:逗号响应解析 +
按序回填 Excel A。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 16:24:34 +08:00
adminandClaude Opus 4.8 e7fab724fc refactor(ai-outfit): 标题生成改为「一次请求·纯提示词·多条按序填」 (§19.22)
把标题生成从「逐行看图、各生成1条、每行一次请求」改为一次请求、纯提示词
(不传图)、生成多条、按序回填(数量由用户写进提示词,docs/11 §17.1/§17.7)。

- ai_text_service:新增 extract_titles_from_response(多行→多条、逐行去
  序号/引号、丢空)+ AiTextClient.generate_texts(一次POST返回多条,与
  generate_text 共用 _post);generate_text/extract_text 改为取首条
- ai_title:generate_titles(一次请求、image_path=None 纯文本);移除
  generate_title/_reference_image/render_title_prompt(不再逐行看图/替换占位符)
- config_service:DEFAULT_TITLE_PROMPT 改批量风格(生成多条、每行一条)
- 面板 _TitleWorker:一次 generate_titles → 按序 write_title_result 回填;
  N>行数多的丢+日志、N<行数后面行留空+日志;请求异常→日志+成功0;去掉行间节流
- _start_title 不再传 request_interval

测试:extract_titles 多行→多条/去序号引号、generate_texts 多条且无图、
generate_titles 一次请求/异常。全套 py37 通过(test_config_service 的
packaging 模板失败属并行 §19.13,与本改动无关)。离屏冒烟:3/3 精确、
5/3 丢弃、2/3 留空 三种分发均按序回填 Excel A + 日志正确。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 15:59:37 +08:00
adminandClaude Opus 4.8 52bc6c0c50 feat(ai-outfit): 标题模型命中图片模型时开跑前拦截 (§19.21)
补齐缺口③:title_model 名字命中了、但那条是纯图片接口
(api_type=images/images_edits,如误指 GPT Image 2)时,过去会开跑后
逐行失败;现在 _find_title_model 在 api_config_errors 后用 detect_api_type
判类型,命中图片接口即返回错误「『{名字}』是图片模型({api_type}),不能
生成文字标题,请改选 chat/gemini 文本模型」→ _resolve_title_model_config
弹窗 + _start_title 中止,体验对齐①没配模型/②名字找不到。

边界:api_type=chat 但实际返图的模型(如 Nano Banana)类型上判不出,仍
运行时由「未找到文字标题」逐行暴露,固有限制。

测试:_find_title_model 命中 images_edits / URL 自动判图片 两例报错;
既有命中(chat)/缺失用例保持绿。离屏冒烟:title_model 指向图片模型 →
点「生成标题」前即弹窗中止、线程不启动。全套 py37 通过
(test_config_service 的 ai_models.json 顺序失败属并行 §19.13,与本改动无关)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:59:04 +08:00
adminandClaude Opus 4.8 81351c77b5 feat(ai-outfit): 话术按钮行微调 + 保留插入标题 + 最终提示词预览改弹窗 (§19.20)
话术组小改版(保留 {title} 占位符、不自动前置):
- 「保存话术」→「保存」,从编辑框下方上移到模板按钮行「重命名」之后
  (行变 新建/另存为/重命名/保存/删除)
- 保留「插入标题」按钮 + {title} 占位符(用户自定标题位置,不与旧话术重复)
- 最终提示词预览改按需弹窗:编辑框下方「预览最终提示词」按钮 → 非模态
  _OutfitPreviewDialog,含数据行下拉(read_all_rows,状态无关)+ 只读替换后
  提示词(含 §7.1 输出要求);话术/分辨率/数据行变化时实时刷新
- 左栏空间理由:标题生成组 + 话术组并存,常驻预览会逼出滚动;弹窗零常驻高度

render_prompt 与 DEFAULT_OUTFIT_PROMPT 不改(纯面板改动)。
测试:面板断言按钮行含「保存」、保留「插入标题」+「预览最终提示词」、
预览弹窗按选中行替换 {title} 并附输出要求。全套 py37 通过
(test_config_service 的 ai_models.json 顺序失败属并行 §19.13,与本改动无关)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:30:57 +08:00
adminandClaude Opus 4.8 c47e9dee95 refactor(ai-outfit): 标题模型改为「配置定名」、去掉下拉 (§19.19)
标题模型是「配一次就固定」的,不像分辨率/话术需每次选;左栏 ~360px 已叠两个
提示词组,去掉下拉更干净,还消掉「误选图片模型」的坑。模型名放 app_config.title_model
(默认 GPT-5.5 文本),运行时按名字查 ai_models.json —— 换模型只改配置不改代码(docs/11 §17.3/§17.6)。

- ai_outfit_panel.py:_build_title_group 去掉标题模型下拉;apply_config 改记 title_model
  名字、_emit_config 不再写(UI 不覆盖);_selected_title_model_config → 纯函数
  _find_title_model(按 name 查 + 校验,返回 (config, error))+ UI 包装
  _resolve_title_model_config;找不到/未配置/图片接口时明确报错
- config_service:DEFAULT_CONFIG.title_model 默认改为 "GPT-5.5 文本"
- docs/ai_models.sample.json:补一条 name=「GPT-5.5 文本」的 chat 文本模型样板(占位 key)
- 测试:面板断言无标题模型下拉;_find_title_model 命中/缺失/无模型三例;全套 py37 通过
- 冒烟:seeded ai_models.json + apply_config → title_model 名字解析到 gpt-5.5(chat),缺失→报错

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:45:36 +08:00
adminandClaude Opus 4.8 2fa488bd21 feat(ai-outfit): 左栏新增「标题生成」(看图→提示词→AI 文字标题→写回A列) (§19.18)
AI 穿搭页左栏新增独立的「生成标题」流程:用户写标题提示词,AI 看该行
衣服图(目录行取首图)生成电商标题,逐行立即写回 Excel A 列并刷新明细表;
完成后重载 Excel,紧接「开始生成」跑图即用新标题。移除原「最终生成要求预览」
腾出版面(docs/11 §17)。

- ai_text_service.py:AiTextClient 复用图像服务 HTTP 管道做文本输出;
  chat/gemini 带图视觉,images/images_edits 明确报错;extract_text 取首条标题
- ai_title.py + TitleResult:单行编排,never raises
- excel_service.write_title_result:只写 A 列、不动 D/E/F
- config_service:title_model 默认 + load/save_title_prompt + 默认标题话术
- 面板:标题生成组(提示词+标题模型下拉+保存+生成标题)置于话术组上方;
  _TitleWorker 顺序逐行+立即回填+刷新;与「开始生成」互斥;删预览相关组件
- 测试:文本解析/payload、generate_title(单文件/目录首图/失败)、
  write_title_result、面板标题组存在且无预览;全套 py37 通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:04:38 +08:00
adminandClaude Sonnet 4.6 0362c7db89 feat(export): 批量导出生成 Excel 后弹窗提示用户 (§17.24)
Excel 成功写入后弹出 QMessageBox,显示文件路径并引导切换
到「AI 穿搭」页直接使用;无有效印花子目录时不弹框。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 09:37:40 +08:00
adminandClaude Sonnet 4.6 a3f4c39821 feat(export): 批量导出后生成 AI 穿搭 Excel,每印花子目录一行 (§17.23)
添加印花批量导出完成后,自动在输出目录生成与时间戳目录同名的 xlsx
文件(docs/02 §6.12);每个有效印花子目录一行,C 列指向该子目录路径,
AI 穿搭页直接选取即可触发目录扇出逻辑(§19.12),无需手工填写 Excel。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-23 09:33:28 +08:00
admin e83dd07dc0 feat: use image concurrency for outfit directories 2026-06-22 18:10:54 +08:00
admin 00ed41db14 docs: define outfit image concurrency 2026-06-22 18:05:26 +08:00
admin 5b5ee119ad feat: add outfit output directory 2026-06-22 17:49:59 +08:00
admin e19eef5b43 docs: set outfit output directory 2026-06-22 17:45:00 +08:00
admin 74338c383e fix: seed AI model config at runtime 2026-06-22 17:35:56 +08:00
adminandClaude Opus 4.8 9a61a432d9 fix(ai-outfit): 货号(B列)改为可选,空货号不再跳过整行 (§19.14)
用户表的「商品id(货号)」整列为空时,行有效性判定(标题+货号+图三者非空)
把每一行都判为不完整 → read_all_rows 与 load_outfit_tasks 都返回 0:预览下拉
只剩占位符,且「开始生成」也判定无待处理行。货号不进提示词、目录行输出名沿用
源图名,对该用法是多余约束。

- excel_service.py: read_all_rows / load_outfit_tasks 完整性判定改为只要求
  标题 + 衣服图路径 非空,货号可空(状态过滤不变)
- core/ai_outfit.py: 单文件行货号为空时输出名回退 Path(garment).stem
  (货号非空仍用 货号.jpg);目录行不变
- ai_outfit_panel.py: 样本下拉标签货号为空显示「(无货号)」
- tests: 空货号行被 read_all_rows/load_outfit_tasks 收录、单文件空货号按源图名命名
- docs/11 §4(表+规则)/§9(命名回退)、tasks.md §19.14

离屏冒烟:标题填/货号空/C=目录 → 预览下拉有行、生成产出到子目录、E=完成;
全套测试在 Python 3.7 全绿(test_config_service 的失败属并行的出厂模板工作,与本次无关)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 17:15:05 +08:00
adminandClaude Opus 4.8 abe46c4aeb feat(ai-outfit): C 列支持图片目录 → 多图扇出到同名子目录 (§19.12)
Excel C 列除单张图片外也可填一个目录(d:/images/a/):对目录内每张图各
生成一张穿搭图(N→N),全部存到 输出目录/<目录叶子名>/,文件名沿用源图名;
Excel 仍整行一个状态:D=子目录、E=全成功才「完成」、F=失败张数/原因。

- core/ai_outfit.py: list_directory_images(顶层、扩展名过滤、排序、忽略子目录)、
  make_outfit_subdir_path(不加 _n 后缀)、generate_outfit_image 目录分支
  (新参 request_interval/image_log;逐张跳过已存在→幂等重试、本地节流、发日志、
  聚合成单个 OutfitResult)
- core/models.py: OutfitResult 加 output_paths(目录行各 jpg,供缩略图)
- ai_outfit_panel.py: gen 闭包传 request_interval/image_log;缩略图逐张;
  明细「结果」列显示「子目录(N 张)」;_basename 处理目录末尾分隔符
- tests/test_ai_outfit.py: 扇出/幂等跳过/空目录/缺目录/部分失败/命名过滤等用例
- docs/11 §4.1+§9.1、tasks.md §19.12

离屏冒烟:3 图目录 + mock API → output/<dir>/ 3 jpg、D=子目录、E=完成、缩略图 3 张;
全套 12 测试文件在 Python 3.7 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 17:03:04 +08:00
adminandClaude Opus 4.8 c59a348d67 feat(ai-outfit): button palette matching the print module (§19.11)
Panel had no stylesheet -> all buttons rendered native gray. Add
setObjectName('aiOutfitPanel') + _apply_styles() with QSS scoped to
#aiOutfitPanel (so dialogs keep native buttons): primary blue for 开始生成
(aiStartBtn, mirrors 开始批量导出/打开文件夹), quiet red for 停止生成 and the 话术
删除 (aiStopBtn/aiPromptDeleteBtn, mirrors 删除), secondary gray for the rest.
docs/11 §10.5 + tasks §19.11. Offscreen grab verified; full suite (12) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:56:25 +08:00
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
adminandClaude Opus 4.8 2643b19686 docs(ai-outfit): empty-tasks prompt + keep preview on generate (§10.4 / §19.10)
After a whole sheet is 完成, clicking 开始生成 silently shows '0 待处理 /
完成 0' and also re-empties the preview sample dropdown. Document two fixes:
(1) a clear 'no pending rows' message (N done skipped / M failed retryable /
clear E column to redo), (2) _on_tasks_loaded must not reset the preview combo
(read_all_rows owns it). Add §10.4 + §19.10.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:14:57 +08:00
adminandClaude Opus 4.8 1c9ee36488 fix(ai-outfit): preview sample rows ignore Excel status (§19.9)
Add excel_service.read_all_rows (every valid data row, status-independent) and
use it in _reload_sample_rows for the preview dropdown, so previewing still
works after the whole sheet is 完成. Generation still uses load_outfit_tasks.
+1 excel test (read_all_rows includes 完成/失败, skips incomplete); offscreen
all-完成 sheet now yields sample rows; full suite (12) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:09:56 +08:00
adminandClaude Opus 4.8 cf1096ad79 docs(ai-outfit): preview sample rows ignore status (§10.3 / §19.9)
Bug: after a whole Excel finishes, re-selecting it shows an empty preview
sample-row dropdown because _reload_sample_rows uses load_outfit_tasks (which
skips 完成 rows). Preview should list all valid data rows regardless of status.
Document the cause + fix (new excel_service.read_all_rows for preview; generate
still uses load_outfit_tasks). Update §7 + add §10.3 / §19.9.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 15:05:01 +08:00
adminandClaude Opus 4.8 25b18ea342 feat(ai-outfit): multiple named prompt templates (§19.8)
- config_service: outfit_prompts.json load/save (utf-8-sig read, no-BOM write,
  corrupt/empty -> migrate legacy outfit_prompt.txt or seed 默认; always >=1;
  _normalize_prompts drops invalid). app_config gains outfit_prompt_name.
- ai_outfit_panel: 通用话术 group gets a template dropdown + 新建/另存为/重命名/
  删除 (keeps 插入标题/保存). Switching loads the template + refreshes preview;
  dirty edits prompt Save/Discard/Cancel before switching/new/save-as (cancel
  reverts the combo); unique names; can't delete the last one; selection
  persisted via config_changed; 开始生成 stores the editor into the template.
- tests: +6 config cases (14 total); offscreen CRUD/dirty/rename/delete verified;
  full suite (12 files) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:12:34 +08:00
adminandClaude Opus 4.8 072b546f5f docs(ai-outfit): spec prompt templates (multi, named) — §7.2 / §19.8
Upgrade the single prompt to multiple named templates (switch/new/save-as/
rename/delete/save, remember last). Decisions: all-custom (seed one 默认),
dirty-check confirm on switch, include rename. Storage: outfit_prompts.json +
outfit_prompt_name in app_config, migrate the old outfit_prompt.txt. Add §7.2
design + §19.8 task (not yet implemented).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:52:10 +08:00
adminandClaude Opus 4.8 d851978634 feat(ai-outfit): info popup when user switches resolution/model (§19.7)
Connect QComboBox.activated (user-only) on the 分辨率 and AI 模型 dropdowns to
an info-only QMessageBox: resolution shows the per-resolution timeout +
next-run-only; model shows its api_type + billing/effect caveat + next-run-only.
Track last value so re-selecting the same item doesn't pop; programmatic sets
(apply_config/_fill_sample_combo) never pop. Offscreen-verified; suite (12) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:31:56 +08:00
adminandClaude Opus 4.8 38bae8166c docs(ai-outfit): spec resolution/model switch info popup (§10.2 / §19.7)
User switching the 分辨率 or AI 模型 dropdown shows an info-only QMessageBox
(no block, no revert) about the impact: resolution -> per-resolution timeout +
next-run-only; model -> api_type + billing/effect may differ + next-run-only.
Trigger via QComboBox.activated (user-only) so programmatic sets don't pop.
Add §10.2 design + §19.7 task (not yet implemented).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:26:52 +08:00
adminandClaude Opus 4.8 d4082ec477 feat(ai-outfit): auto-append output requirements to prompt (§19.6)
- core/ai_outfit: OUTPUT_REQUIREMENTS constant + build_output_requirements();
  render_prompt(template, task, resolution=None) appends the block when a
  resolution is given; generate_outfit_image passes the current resolution.
- ai_outfit_panel: inline preview now uses render_prompt/build_output_requirements
  with the selected resolution and refreshes when the resolution combo changes,
  so the preview equals what is actually sent.
- tests: +3 cases (tail with resolution, none without, helper empty/filled);
  updated the success test for the appended tail. Full suite (12 files) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:40:28 +08:00
adminandClaude Opus 4.8 031c420f33 docs(ai-outfit): spec auto-appended output requirements (§7.1 / §19.6)
Final prompt = user 话术 + an auto-appended 「批量生成输出要求」block ported
from the old tool (参考解析度 dynamic = resolution setting; 固定 1:1 / 结合标题与
参考图 / 不可跑版 fixed). Simplified wording, always appended, preview must
include it. Add §7.1 design + §19.6 task (not yet implemented).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 10:35:11 +08:00
adminandClaude Opus 4.8 1526254486 feat(ai-outfit): AI 穿搭 UI tab (§19.3) + config plumbing (§19.4)
- main_window: wrap workflow in a QStackedWidget (page 0 = print, page 1 =
  AI outfit); enable tab 2「AI 穿搭」, switch pages on tab change.
- app/widgets/ai_outfit_panel.py: three-column page per docs/11 §10 —
  left settings (Excel/output/model/prompt editor+save+insert+preview
  dialog/batch options), center (recent-results thumbnails + detail
  table), right (progress/stats/start/stop/export failures/log).
- Threading: QThread + _OutfitWorker(QObject) wraps OutfitBatchRunner;
  queued signals refresh UI, each row written back to Excel on the worker
  thread; finish summary + failure-list CSV export.
- config_service: load_ai_models()/load_outfit_prompt()/save_outfit_prompt()
  + outfit_* keys in app_config; panel persists via config_changed signal.
- tests/test_config_service.py: 8 cases for the new helpers. Full suite
  (12 files) green on Python 3.7; offscreen MainWindow smoke test passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 08:52:37 +08:00
adminandClaude Opus 4.8 5ca615f7ae fix(ai-outfit): per-resolution timeout + _to_int default fallback
- §8 dynamic timeout: add resolution_timeout() (512/1K/2K/4K ->
  180/240/360/600s); generate() uses it for the POST and image download.
  timeout_seconds now defaults to 0 = auto-by-resolution; an explicit
  positive value overrides. Validation allows 0, rejects negatives.
- _to_int now returns the supplied default on parse failure (was 0), so a
  bad connect_timeout_seconds falls back to 30 instead of failing as 0.
- Tests: +5 (resolution map, auto vs override timeout, connect fallback).
- tasks.md §19.1/§19.2: tick unit-test item, note runner is pure-logic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:03:52 +08:00
admin 5b57a4d39a feat: add outfit batch runner 2026-06-18 17:52:05 +08:00
admin 28fcc018bc feat: add outfit generation core 2026-06-18 17:43:35 +08:00
admin fe1dc7778e feat: add ai image service 2026-06-18 17:40:56 +08:00
admin 10238d3da3 feat: add outfit excel service 2026-06-18 17:35:00 +08:00
admin 382797cfea feat: add outfit task models 2026-06-18 17:30:18 +08:00
admin 5fb2f1c8e0 chore: add ai outfit dependencies 2026-06-18 17:26:16 +08:00
adminandClaude Opus 4.8 a7db9444bd docs: track AI outfit module in tasks.md (§19)
Promote AI 穿搭 from the deferred list (§18) to an active §19: design
+ mockup done (19.0), implementation staged per docs/11 §14 (19.1-19.5).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 17:13:34 +08:00
adminandClaude Opus 4.8 421ae78035 feat: default export dir to <install root>\合并后的图片 when packaged
get_output_dir() now returns <install root>\合并后的图片 (parent of app\) for
frozen builds — next to Launcher.exe, easy to find, not replaced on update.
Falls back to <data_dir>/output if the install root is read-only; dev mode
unchanged. The export panel already uses get_output_dir(), so its default
follows automatically.

- tests/test_file_service.py: +3 (dev, frozen install-root, fallback)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:29:09 +08:00
adminandClaude Opus 4.8 3c4617a27b docs: default export dir to <install root>\合并后的图片
Default output moves from ~/.cmbot/output (buried in the user profile) to a
合并后的图片\ folder next to Launcher.exe — easy to find and not replaced on
update (NOT under app\, which is swapped each update). Falls back to
~/.cmbot/output if the install root is read-only; user can still pick any dir.
docs/10 §4/§5, docs/02 config, docs/07 §7.5; tasks 17.22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:26:04 +08:00
adminandClaude Opus 4.8 954914a8af docs: update indicator is a hint text, not a red button
Replace the red-dot-on-the-gear treatment with a low-key blue "发现新版本
vX.Y.Z" label to the left of the ⚙ 配置 button (red reads as error; the text
is clearer and shows the version). docs/07 §4.3, docs/10 §12 and related
wording updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:02:14 +08:00
adminandClaude Opus 4.8 dc2cd123fc docs: align update design with non-blocking two-phase model
Launcher only applies a staged update + launches (no download at startup);
downloading happens in the app's settings dialog. docs/10 §3/§6/§8/§12/§16/§18
rewritten for the two-phase flow (installer.download_and_stage / apply_staged);
docs/07 §4.3/§4.4 cover the gear dot + "检查并更新" button; tasks 17.21.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 12:03:23 +08:00
adminandClaude Opus 4.8 30814b6bd6 docs: reconcile tasks.md with actual state
Tick §17.1–17.6 (persistence, independent resets, button dedup, relocate/rename,
template delete, compact layout — all implemented earlier). Clean up §18: move
delivered online-update/distribution out of the backlog; add stage-4 (mandatory/
retention) as the remaining update work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:37:17 +08:00
adminandClaude Opus 4.8 2d2c6c839d feat: settings dialog for update config (gear entry)
A low-key "⚙ 配置" button at the right end of the flow-tab row opens a settings
dialog to edit update_source / update_user / update_pass, with a test-connection
button that distinguishes "reachable & up to date" from "cannot connect".

- settings_dialog.py: pure-UI QDialog; password mask + show; test runs off the
  UI thread via load_manifest and reports the result
- update_service: add load_manifest() (raises on failure, unlike check_for_update)
- main_window: gear button, _open_settings persists via save_config and re-runs
  the update check; _update_found connected once to avoid duplicate banners
- tests: +2 for load_manifest

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:09:29 +08:00
adminandClaude Opus 4.8 295dc803c6 chore: retire PowerShell update.ps1 and install_local.ps1
Superseded by src/launcher.py (Launcher.exe) + the portable layout from
build.ps1. The PS prototype validated the flow; removed to avoid confusion
with the old %LOCALAPPDATA% installer. docs/10 §16 and tasks 17.19 updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:05:06 +08:00
adminandClaude Opus 4.8 3327276c9a docs: settings dialog for update config (gear entry, not a numbered tab)
A low-key "⚙ 配置" button at the right end of the flow-tab row (separated by
stretch, not a numbered step) opens a settings dialog to edit update_source /
update_user / update_pass, with a test-connection check. Dialog is pure UI;
main_window owns save_config. docs/07 §3/§4.1/§4.3/§4.4; tasks 17.20.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 11:03:36 +08:00
adminandClaude Opus 4.8 ac428fb5ec feat: build.ps1 produces Launcher.exe + portable layout
- build a lean onefile Launcher.exe from src/launcher.py (stdlib-only, no
  PySide6/Pillow) alongside the onedir app build
- release dir is now the portable layout: Launcher.exe + app\ (CMBot.exe +
  config + version.txt)
- emit two zips: CMBot-<ver>.zip (self-update payload = app\ contents, what
  manifest.url points to) and CMBot-<ver>-portable.zip (initial install)
- launcher: harden logging setup for --windowed builds / read-only roots

Actual PyInstaller build must run on Windows; script is syntax-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 10:56:53 +08:00