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>
- _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>
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>
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>
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>
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>
Add last_tab to DEFAULT_CONFIG; main_window restores it in _restore_preferences
(only enabled/implemented tabs 0/1; setCurrentIndex before connecting the persist
slot to avoid an echo write) and persists via _on_tab_persist on currentChanged.
Mirrors the existing last_template/last_batch_mode pattern. Document the key in
docs/02 §9. Offscreen-verified (restore 0/1, disabled 2 -> 0, persist); suite green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
- 数据源/输出 become inline label+path+browse rows (_inline_path_row);
removed the left group boxes to free vertical space.
- 通用话术 + 最终提示词预览 enlarged and stretch with window height.
- 生成设置 grid -> 3 columns (2 rows); AI 模型 dropdown moved under it in the
right column. Splitter sizes left360/center/right400; right minWidth 360.
- Verified offscreen against the mockup (real Qt grab); full suite (12) green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 生成设置 -> 3 cols x 2 rows; AI 模型 dropdown moved under it on the right.
- 数据源/输出 become inline label+path rows; bigger 通用话术 + 最终提示词预览
(preview now shows the appended output requirements). Right column widened
to ~400 (left ~360). Update §10 and re-render ui-ai-outfit.png.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
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>
Template covering all four api_type variants (chat/gemini/images/images_edits).
Copy to ~/.cmbot/config/ai_models.json and fill real values; the real file
(with keys) stays out of git.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Constrain the 样本行 and AI 模型 combos (AdjustToMinimumContentsLengthWithIcon
+ minimumContentsLength + Ignored size policy) so long items elide instead of
inflating the column's min width. Left scroll h-policy AlwaysOff -> AsNeeded as
a safety net; trim splitter initial sizes to fit the default window.
Measured: left inner minimumSizeHint 829px -> 200px (viewport ~398), no clip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Left column content is clipped on the right (looks covered by the center).
Measured offscreen: the left QScrollArea has h-scroll AlwaysOff while its
inner content demands 829px min width vs a ~428px viewport. The driver is the
样本行 QComboBox (minimumSizeHint ~743px from its long '第N行·货号·标题' item),
which inflates the preview group. Documented root cause + fix direction
(constrain combo size policy; left scroll AsNeeded).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the one-per-row QFormLayout with a 2-column QGridLayout (3x2:
并发/间隔 · 冷却/重试 · 分辨率/质量), matching the mockup's 2-col 生成设置.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Drop the prompt-preview dialog; add a live inline preview pinned at the
bottom of the left column: sample-row dropdown + read-only rendered text,
refreshing on prompt edit / sample change, with a missing-{title} hint.
- Sample rows load from the chosen Excel (on browse, on startup from the
remembered path, and from the run's loaded tasks).
- Move 生成设置 out of the left column into the right run column
(_create_settings_group), so left = 数据源 + 话术 + 预览.
- Matches docs d79f881. Full suite green; offscreen window smoke passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Replace the prompt-preview dialog with an inline, live preview pinned at
the bottom of the left column (sample-row dropdown, title substituted).
- Move 生成设置 from the left into the right run column (2-col), so left
becomes a focused 数据源 + 话术 + 预览 authoring column.
- Update §7 (inline preview) and §10 (left/right column contents) + re-render
the mockup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Match the doc change (8a4ebe6): remove the 插入货号 button (keep only
插入标题) and drop 货号 {product_id} from DEFAULT_OUTFIT_PROMPT. product_id
still drives Excel column B and the output filename, not the AI prompt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prompt offers only {title}; product_id is used for Excel column B and the
output filename (货号.jpg), not for AI generation. Update §7/§10 and the
mockup (remove the 插入货号 chip + {product_id} from the sample prompt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- 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>
- §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>
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>
- New docs/ui-ai-outfit.html + ui-ai-outfit.png: 「2 AI 穿搭」page
mockup, same chrome/palette/components as docs/ui-v1 (blue-only).
- Center = recent-results thumbnail strip (finished outputs only) +
detail table; dropped the live single-image preview to avoid the
concurrent "which row" ambiguity.
- §7: 保存话术 persists template via config_service; 预览最终提示词 is a
button opening a read-only QDialog rendered from a sample row.
- §8: concurrency is Python threads (ThreadPoolExecutor); Qt only
signals back to the main thread to refresh UI; default 1 for API limit.
- §10: three-column layout, results-strip rationale, settings 3-col.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Design for the "2 AI 穿搭" tab: Excel as the (fixed) data source → convert rows
to OutfitTask → garment image + prompt → AI image API → person-wearing-clothes
JPG → write result path back to Excel. Ports the old project's API client /
batch / retry into cmbot's app/core/services layering, reusing config/log/
~/.cmbot/output. Also fixes the 旧ai穿搭项目.md §6 recommendation (keep Excel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Show a low-key blue, clickable "发现新版本 vX.Y.Z" label just left of the
⚙ 配置 button when an update is found (hidden otherwise), instead of recoloring
the button red. Matches docs/07 §4.3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
Startup no longer blocks on a download. New split:
- services/installer.py: download_and_stage() (runs while the app is open →
staging\app.new, verified) and apply_staged() (launcher swaps it into app\
at next launch, when the exe isn't locked).
- launcher.py: slimmed to seed + apply_staged + launch; no network at startup.
- main_window: a new version lights a dot on the ⚙ 配置 button instead of a
blocking banner; the old open-folder banner is removed.
- settings_dialog: "检查并更新" downloads + stages, then "下次启动生效".
Verified end-to-end over a local HTTP server (download→stage→apply); 101 tests
pass (test_installer +11, test_launcher rewritten).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Ship update_source=http://cm.xiapi.com with the internal read account so a
fresh install is preconfigured (seeded into ~/.cmbot on first run). Plaintext
credentials are acceptable for internal LAN use per project decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
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>
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>
- 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>
Launcher core that reuses services/update_service: seed default config into
~/.cmbot on first run, check the manifest, download the release zip, verify
SHA-256, extract, validate version, swap into app/ (keeping app.old/ for
rollback), then launch app/CMBot.exe. Degrades to the local version on any
failure; checks install-root writability before updating.
- update_service: UpdateInfo gains sha256/size/min_supported; release URL
resolved to absolute; extract make_auth_header + add download() helper
- tests/test_launcher.py: 11 tests (seed, find-root, swap, sha mismatch,
no-update); also verified end-to-end against a local HTTP server
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Packaged builds now resolve the data root to ~/.cmbot (%USERPROFILE%\.cmbot)
instead of the program dir, so user config/templates/output stay writable and
per-user regardless of where the program is installed or whether it is launched
via Launcher.exe (docs/10-lan-update.md §5). CMBOT_DATA_DIR still overrides.
- tests/test_file_service.py: 5 tests covering env override, frozen->~/.cmbot,
dev->project root, blank-env handling
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Launcher is a compiled Python/PyInstaller Launcher.exe (retires update.ps1
and install_local.ps1)
- portable: extract to any writable dir; InstallRoot = Launcher.exe's folder,
no longer tied to %LOCALAPPDATA%
- user data moves to ~/.cmbot (%USERPROFILE%\.cmbot): always writable, per-user,
survives program updates; get_data_dir() three-tier (env -> ~/.cmbot -> dev root)
- writability check now applies to the program dir only; first-run seeds default
templates from app\config into ~/.cmbot
- docs/10 §3/§4/§5/§8/§9/§11/§16 updated; tasks 17.19 added (docs done, code TODO)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#1 BOM: PowerShell Set-Content -Encoding UTF8 writes a BOM that made the in-app
json.loads silently fail (banner never showed even with an update available).
- update_service: decode manifest with utf-8-sig (HTTP + local)
- build.ps1: write manifest.json without BOM (UTF8Encoding $false)
- tests: +2 covering BOM manifests (HTTP + local)
#2 docs: docs/10 §16 status synced to the implemented app/app.old/version.txt +
SHA-256 model (stages 2/3 done + e2e verified); tasks 17.18 updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/update.ps1: check manifest, stage+verify the new version, swap it in
alongside the running one (versions\<ver> + current.txt pointer), then launch
with CMBOT_DATA_DIR. Degrades to the local version on any failure; never
overwrites the running version. Rollback = edit current.txt.
Verified against a fake versioned layout: update, idempotent re-run, unreachable
source, no source, corrupt download (marker missing), malformed manifest.
Not yet wired into a real install layout (build.ps1 still ships flat onedir).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read manifest.json from the configured update_source and show a dismissable
banner when a newer version is available. Notify-only — no install yet.
- services/update_service.py: version compare + check_for_update (pure, tested)
- config: add update_source key (empty = no check)
- main_window: top banner, background-thread check; open folder via os.startfile
(QDesktopServices.openUrl mishandles file:// folder URLs — ShellExecute err 2)
- tests: +15 covering version compare and check_for_update branches
- docs 02/05/10 + tasks 17.16
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QueuePanel reused one pixel TransformState for all non-fine-tuned items,
mis-placing garments/prints of other sizes. Store the ratio-based template
instead and recompute each item from its own garment/print size.
- add core.composer.image_size() and resolve_transform() (pure, tested)
- queue: set_template() replaces set_transform(); per-item resolve + size cache
- template_panel: expose current_template(); main_window pushes it to the queue
- tests: +4 covering fine-tune precedence, per-size recompute, empty inputs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add get_data_dir() with CMBOT_DATA_DIR override, fallback to program dir
- point config/logs/output at the data root; resources stay on program root
- harden config/template writes with parents=True for a deep data root
- add docs/10-lan-update.md (LAN auto-update design); default install root
%LOCALAPPDATA%\CMBot for elevation-free self-update
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Output filename now includes the print: <garment>_<print>.<ext>, e.g.
1_TY030.png (folder grouping unchanged) (§17.12).
- Hide the single-export button (export via the queue) and remove the
orphaned static export hint label (§17.13).
- Fix: selecting a non-fine-tuned queue item now lays the print out per the
selected template (apply_current) instead of load_print's centred default;
still guarded by _loading_queue_item so it isn't marked 已微调 (§17.14).
Docs: PRD 6.6/6.8/9, UI design 7.6/7.7/10/12.4, tasks.md 17.12–17.14.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Auto-preview the first image after a folder loads (both panels), so the
canvas isn't empty (§17.8).
- Group exports as <output_dir>/<timestamp>/<print_stem>/<garment_stem>.<ext>;
one timestamp per export run, shared across the batch (§17.9).
- Remember the last garment/print folders and reopen the picker there;
persisted via config, wired through main_window (§17.10).
- Shrink the 3 box templates further and lower them (chest placement).
- Fix: batch export ignored the chosen format/quality/output dir because the
queue never received ExportOptions. Export panel now emits
export_options_changed → queue.set_export_options (+ initial sync) (§17.11).
Docs: PRD 6.1/6.8/9, architecture 4.12, UI design 5.1, tasks.md 17.8–17.11.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The in-content header strip showed the app name + version, duplicating the
OS window title (setWindowTitle). With the 设置 button already gone it had no
other purpose, so remove it (and its styles / unused _HEADER_HEIGHT). Name and
version stay visible via the OS title bar; the work area gains ~40px.
Docs: UI design 3 (layout) and 4.1 (title bar), tasks.md 17.7.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The 设置 button was a layout placeholder from the initial header build —
never wired to anything, so clicking it did nothing. Remove it (and its
style) rather than leave a dead control; current settings (output
dir/format/quality) are editable inline in the export panel.
Docs: UI design 4.1 records that no settings entry is provided for now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>