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>
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>
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>
- 数据源/输出 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>
- 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>
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>
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>
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>
- 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>
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>
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>
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>
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>
- 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>
#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>
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>
- Template delete: add a 删除 button (reuses template_service.delete_template)
with a confirm dialog; hidden for built-ins like 保存; reloads and re-selects
the first built-in after deletion, persisting the new selection.
- Whole reset: move the template-wide reset out of the template area to the
bottom of the transform panel and rename it to 位置尺寸旋转重置为当前模板, so it
sits with the params it resets, beside the three per-section resets.
- Compact layout: pair X/Y and 宽/高 onto single rows (short labels), and put
the export 格式/质量 selectors on one row. Sync logic unchanged.
Docs: PRD 6.6, UI design 7.1/7.2/7.3/7.4/7.5/10, tasks.md 17.4/17.5/17.6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>