Commit Graph
82 Commits
Author SHA1 Message Date
adminandClaude Opus 4.8 11dd062f45 feat(ai-outfit): two settings per row in the right column
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>
2026-06-22 09:53:15 +08:00
adminandClaude Opus 4.8 db28a39ebc feat(ai-outfit): inline prompt preview + move settings to right (layout A)
- 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>
2026-06-22 09:49:21 +08:00
adminandClaude Opus 4.8 d79f881ec7 docs(ai-outfit): inline prompt preview + settings on right (layout A)
- 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>
2026-06-22 09:35:52 +08:00
adminandClaude Opus 4.8 572c2cf6dd feat(ai-outfit): drop product_id from prompt UI + default template
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>
2026-06-22 09:17:28 +08:00
adminandClaude Opus 4.8 8a4ebe685b docs(ai-outfit): drop product_id from prompt, keep only insert-title
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>
2026-06-22 09:16:04 +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 2d408d9593 docs: replace 使用说明.docx with cmbot自动化工具.docx
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 08:38:05 +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 31733ed9c9 docs: add AI outfit UI mockup, align style with print module
- 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>
2026-06-18 17:11:56 +08:00
adminandClaude Opus 4.8 c82f870910 docs: add AI outfit module design (docs/11-ai-outfit.md)
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>
2026-06-18 16:30:26 +08:00
admin 0afceba79c feat: add vision prompt script 2026-06-18 15:21:49 +08:00
adminandClaude Opus 4.8 83d0f37d3e docs: add user manual (使用说明.docx) + generator script
scripts/gen_user_manual.py builds docs/使用说明.docx with python-docx (re-run to
regenerate). Covers install/launch, UI overview, workflow, materials, preview
fine-tuning, templates, batch modes/queue, export structure, settings + online
update, data locations, and FAQ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:39:29 +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 0795e270b4 feat: new-version hint text left of the gear (replaces red button)
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>
2026-06-18 14:11:30 +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 5a1a004daf feat: non-blocking updates — download in app, apply at launch
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>
2026-06-18 11:58:06 +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 56f2767ca0 chore: preset factory update source in default config (internal use)
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>
2026-06-18 11:12:30 +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
adminandClaude Opus 4.8 1d8e4fc240 feat: src/launcher.py — Python update launcher (HTTP, app/app.old swap)
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>
2026-06-18 10:49:45 +08:00
adminandClaude Opus 4.8 3d01bed98c feat: get_data_dir() three-tier fallback (env -> ~/.cmbot -> dev root)
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>
2026-06-18 10:39:52 +08:00
adminandClaude Opus 4.8 0c9415fbcd docs: pivot launcher to Launcher.exe + portable layout + ~/.cmbot data
- 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>
2026-06-18 10:37:48 +08:00
adminandClaude Opus 4.8 54e42670f7 fix: tolerate UTF-8 BOM in manifest; sync docs to app/app.old model
#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>
2026-06-18 10:19:03 +08:00
admin ba8daaccdd feat: add local launcher installer 2026-06-18 10:04:29 +08:00
admin 757056b462 docs: mark update release script complete 2026-06-18 09:45:35 +08:00
admin cdfd684894 feat: generate update release manifest 2026-06-18 09:45:08 +08:00
admin 8944676ad6 docs: update http update task status 2026-06-18 09:43:23 +08:00
admin 6aae8d1bd6 feat: update launcher over http 2026-06-18 09:42:45 +08:00
admin 813e569639 feat: support http update checks 2026-06-18 09:38:44 +08:00
admin 6bc27f5180 chore: add update auth config defaults 2026-06-18 09:36:19 +08:00
admin 622159badd docs: update online update strategy 2026-06-18 09:25:42 +08:00
adminandClaude Opus 4.8 51cefab2db docs: pivot update design from LAN/UNC to HTTP transport
- docs/10: HTTP source + Basic Auth; manifest url/sha256/size; download zip +
  verify SHA-256 + extract; HTTP security model; stage notes marked pending
- docs/02/05: add update_user/update_pass; update_source is now an HTTP(S) URL
- tasks 17.18: HTTP pivot task (docs done; code TODO)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 08:54:16 +08:00
adminandClaude Opus 4.8 275ce852c3 feat: LAN auto-update launcher script (stage 3)
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>
2026-06-17 17:29:56 +08:00
adminandClaude Opus 4.8 09a563aeec docs: mark §17.16 reachable-source open-folder GUI check verified
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:17:47 +08:00
adminandClaude Opus 4.8 7cd6948c76 docs: mark §17.16 unreachable-source GUI check verified
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:12:43 +08:00
adminandClaude Opus 4.8 0392e1c8a2 feat: LAN update check on startup with notify banner (stage 2)
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>
2026-06-17 17:05:46 +08:00
adminandClaude Opus 4.8 f2b08fecb3 fix: per-item transform in batch export (correct placement across sizes)
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>
2026-06-17 16:42:20 +08:00
adminandClaude Opus 4.8 76f2c6dd5b feat: separate writable data dir from program dir (LAN update groundwork)
- 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>
2026-06-17 16:29:47 +08:00
adminandClaude Opus 4.8 7a1b9d8330 feat: print name in output filename; export panel trim; queue template fix
- 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>
2026-06-17 15:51:52 +08:00
adminandClaude Opus 4.8 97341a5831 chore: rename app to 自动化工具 and exe/release to CMBot
Functional/build-level rename only:
- version.py APP_NAME -> 自动化工具 (window title)
- build.ps1 exe/release name AutoPrint -> CMBot
- docs packaging/structure examples AutoPrint -> CMBot

Descriptive product name in narrative docs is left unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 15:18:38 +08:00
adminandClaude Opus 4.8 edcd5d1e18 feat: auto-preview, timestamped/grouped output, folder memory; fix batch format
- 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>
2026-06-17 15:13:01 +08:00
adminandClaude Opus 4.8 0cb1544654 refactor: drop the duplicate in-app title bar; use the OS window title
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>
2026-06-17 09:05:47 +08:00
adminandClaude Opus 4.8 162c3fc788 refactor: remove the non-functional 设置 button from the header
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>
2026-06-17 08:53:34 +08:00
adminandClaude Opus 4.8 7fe770e80d feat: template delete, whole-reset relocation, and compact param/export rows
- 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>
2026-06-16 17:45:09 +08:00
adminandClaude Opus 4.8 f767ec2660 feat: persist template/batch-mode, per-aspect resets, de-dup template actions
§17.1 Preferences persistence:
- Wire the previously-unused config_service; remember and restore the
  last-selected template and last batch mode. main_window loads config,
  distributes initial values, then persists on change (save-on-change).
- template_panel: template_changed signal, select_template,
  current_template_state. queue_panel: set_batch_mode + batch_mode_changed.

§17.2 Independent resets:
- Replace the combined reset and 归零 with per-section 位置/尺寸/旋转 reset
  buttons (quiet ghost styling) that restore only their own aspect to the
  selected template. Size reset changes width/height only, leaving X/Y.

Button de-dup:
- Remove the export panel's 应用为模板 / 重置为模板 — literal duplicates of
  the template panel's 另存为 / 重置为模板. Template actions now live only
  in the template area; the export panel handles output only.

Docs: PRD (6.6/6.9/9/10), architecture (4.12), UI design (7.x/10),
development-rules (6, single-home actions + consistent naming), tasks.md (17).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:01:58 +08:00
adminandClaude Opus 4.8 69a6dc892a fix: keep print and re-apply selected template on preview switch
Switching the preview garment cleared the scene (print lost), and
re-loading a print used the canvas's hard-coded centred default instead
of the selected template.

- template_panel: add apply_current() to re-emit the selected template
  for the current garment/print sizes and sync _current_state.
- main_window: remember the current print path, restore it after a
  garment switch, and call apply_current() after garment/print changes
  so the selected template stays sticky. Fine-tuned items are unaffected.
- Also feed template_applied into export_panel.set_transform to keep the
  single-export transform in sync.

Docs: record the sticky-template rule (PRD 6.6).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:39:45 +08:00
adminandClaude Opus 4.8 8a34fe93ff feat: thumbnail grid, aspect-aware templates, and UI hierarchy polish
Material list:
- Switch asset list from one-per-row to a wrapping thumbnail grid
  (IconMode cards: thumbnail + corner checkbox + elided name, subfolder
  moved to tooltip).

Templates:
- Template.to_transform_state now contains-fits the print inside the
  target box by the print's native aspect ratio (no stretch); falls back
  to the box when print size is unknown.
- Feed print native size into template_panel for aspect-aware fitting.
- Shrink built-in template boxes to realistic print proportions.

UI hierarchy:
- Promote 打开文件夹 buttons to primary (filled accent).
- Add inline 归零 (reset angle) and a 重置位置/尺寸/角度 button to the
  transform panel; wire it to re-apply the selected template.
- Hide 保存 for built-in templates instead of leaving it greyed.

Docs: record template box/contain behavior (PRD) and the grid layout,
button hierarchy, and reset/save-visibility rules (UI design).
Tests: add 3 aspect-fit cases for to_transform_state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:11:56 +08:00
admin df9145abdb chore: add packaging build script 2026-06-16 11:49:25 +08:00
admin c94b600bb6 feat: add low contrast visibility analysis 2026-06-16 11:21:21 +08:00
admin 43a096366f docs: add low contrast screening task 2026-06-16 10:53:19 +08:00
admin 511fcb1fc5 docs: add low contrast screening requirement 2026-06-16 10:39:31 +08:00
adminandClaude Sonnet 4.6 d02da738c6 test: add composer and template test suites (43 tests, all passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:38:06 +08:00
adminandClaude Sonnet 4.6 597da6744d feat: implement fine-tune state and complete inter-panel signal wiring
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:31:16 +08:00
adminandClaude Sonnet 4.6 aeb6b59ca1 feat: implement queue panel with table, batch modes, stats, and export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:25:45 +08:00
adminandClaude Sonnet 4.6 1054ab6236 feat: implement export panel UI with output settings and single export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:18:39 +08:00
adminandClaude Sonnet 4.6 abc92cbdcb feat: implement template panel UI with picker, save, save-as, reset
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:15:33 +08:00
adminandClaude Sonnet 4.6 8bb6a8e53a feat: implement transform panel UI with position, size, rotation controls
TransformPanel provides the right-panel parameter editing section:

- Position: X/Y QDoubleSpinBox (range ±99999 px, 0.1 step)
- Size: Width/Height QDoubleSpinBox (min 10 px) + 锁定宽高比例 checkbox
  - When locked and width changes, height is auto-updated to maintain ratio
  - When locked and height changes, width is auto-updated to maintain ratio
  - Locking captures the current ratio at that moment
- Rotation: angle QDoubleSpinBox + 左旋 90° / 右旋 90° buttons

Signal-loop prevention:
- set_transform() sets self._updating = True and blockSignals on all inputs
  before updating values, preventing valueChanged from re-firing _emit()
- _on_width_changed/_on_height_changed also blockSignals on the companion
  spinbox when adjusting for aspect ratio to avoid double emission

Public API:
- set_transform(TransformState): programmatic update, no signal emitted
- get_transform() -> TransformState: read current inputs
- transform_changed signal: emitted whenever user edits any input

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:06:24 +08:00
adminandClaude Sonnet 4.6 3e3e06b7a1 feat: implement image canvas with drag/scale/rotate and preview zoom
Replace placeholder ImageCanvas with full QGraphicsView/QGraphicsScene editor:

Layout: ImageCanvas(QWidget) = top bar + _CanvasView(QGraphicsView) + hint bar

Top bar: garment/print filenames, mode buttons (移动/缩放/旋转, context-aware),
zoom controls (−/% label/+, scroll wheel).

_CanvasView:
- drawBackground: checkerboard in garment area, gray outside
- wheelEvent: zoom in/out anchored at cursor
- mousePressEvent/mouseMoveEvent/mouseReleaseEvent: delegate to ImageCanvas

Scene (1:1 with garment pixel coords):
- garment QGraphicsPixmapItem at (0,0), not interactive
- print QGraphicsPixmapItem positioned/rotated from TransformState
- cosmetic selection rect (blue, 1.5px)
- 4 corner handles (white circle, blue border) for scaling
- rotation arm (dashed) + handle (blue circle) above top-center

Interaction:
- Hit test: rotation handle > corner handles > print body (priority order)
- Move: delta in scene coords added to TransformState.x/y
- Scale: _scene_to_local_xy() maps mouse to item local coords, computes
  new size keeping opposite corner fixed (_do_scale); supports aspect
  ratio lock via Shift or keep_aspect_ratio flag; correct with rotation
- Rotate: atan2(mouse - center) + angle offset at press

Coordinate helpers (static):
- _local_to_scene_xy(lx, ly, state): item-local → scene
- _scene_to_local_xy(sx, sy, state): scene → item-local (inverse rotation)
Both use TransformState snapshot, not live item state, to avoid stale-pixmap issues.

transform_changed signal fires on mouse release with final TransformState.
Preview zoom changes only QGraphicsView transform; TransformState is unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 09:01:15 +08:00
adminandClaude Sonnet 4.6 94d30ab183 feat: implement image list panel UI for garment and print assets
Implement ImageListPanel with two _AssetPanel sub-widgets (garment/print)
separated by a vertical QSplitter:

- _AssetItemWidget: checkbox (batch select) + 52px thumbnail + subfolder
  label + filename; WA_StyledBackground enables preview highlight (#cce4f7)
- _AssetPanel: open-folder button → scan_image_folder(); stats row with
  tristate 全选 checkbox + "含子文件夹·共N 已选M" label + sort selector
  (文件夹+名称 / 名称); QListWidget with NoSelection mode so preview
  highlight and batch checkbox are visually distinct
- ImageListPanel: exposes garment_preview_changed, print_preview_changed,
  garments_changed, prints_changed signals for downstream panels

File scanning delegates to services/file_service.scan_image_folder();
no scanning logic duplicated in UI layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 08:44:17 +08:00
adminandClaude Sonnet 4.6 0887cb35aa feat: implement main window layout with panels and style
Expand MainWindow from placeholder to full structural layout:
- Header bar: app name, version label, settings button
- Flow tab bar (QTabBar): tabs 2/3 disabled, click redirects to status bar message
- Work area (QSplitter): ImageListPanel (left 374px) | ImageCanvas (center, stretch) | right scroll with TemplatePanel/TransformPanel/ExportPanel (318px)
- Queue panel (188px, collapsible ▲/▼ toggle)
- QStatusBar with Windows-style styling

Applies color/font spec from docs/07-ui-design.md §10.
No business logic in main_window.py; panel stubs remain for tasks 8–13.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 18:04:29 +08:00
admin 29523f9ac2 feat: implement batch composition core 2026-06-15 17:55:55 +08:00
adminandClaude Sonnet 4.6 4b008a0dc8 feat: implement image composer with Pillow
- compose(): scales print to TransformState.width/height, rotates
  clockwise by TransformState.rotation (negated for Pillow), pastes
  onto a transparent layer via alpha_composite, saves PNG or JPG
- Rotation centre fixed at print bounding-box centre; paste_x/y
  computed from centre - rotated_size/2 per design doc §13.3
- JPG: flattens RGBA onto white RGB background before saving
- Out-of-bounds print clips silently via PIL paste; width/height<=0
  returns ComposeResult(success=False); file errors caught, never raise

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:28:02 +08:00
admin a6a8504ea8 fix: align resource and runtime paths 2026-06-15 17:13:36 +08:00
adminandClaude Sonnet 4.6 519bb9a6d2 feat: implement template service with builtin and custom templates
- BUILTIN_TEMPLATES: 4 presets (正方形/纵向/横向长方形/左胸小号)
- load_custom_templates(): reads templates.json, skips corrupt entries
  individually so one bad record never blocks the rest
- get_all_templates(): builtin first, then custom
- add_template(): upsert by name, forces type="custom"
- rename_template() / delete_template(): raise ValueError if not found
- _save_custom_templates(): auto-creates config/ dir, logs on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 17:01:40 +08:00
adminandClaude Sonnet 4.6 2d9121e3e0 feat: add image folder scanning and safe output filename
- scan_image_folder(): recursively scans a directory for supported
  images (png/jpg/jpeg/webp, case-insensitive), returns List[ImageAsset],
  skips and logs unsupported files, raises OSError on bad path
- make_safe_output_path(): builds <garment>_<print>.<ext> filename,
  appends _1/_2/... counter if destination already exists

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 16:58:44 +08:00
adminandClaude Sonnet 4.6 b692e3aa42 feat: complete core data models
Add Template, ExportOptions, BatchMode, BatchOptions, ComposeResult,
BatchResult to models.py. Keep existing ImageAsset and TransformState,
adding center() helper to TransformState.

Template uses ratio-based fields with to_transform_state() to convert
to pixel coordinates for any garment size.
BatchMode enum covers all four batch modes from PRD.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 16:56:36 +08:00
adminandClaude Sonnet 4.6 134896fcd2 feat: implement config service with JSON read/write and safe fallback
- DEFAULT_CONFIG defines output_format, output_quality, output_dir,
  last_garment_dir, last_print_dir
- load_config(): merges file values with defaults; never raises on
  missing, damaged, or non-object JSON root
- save_config(): creates config/ dir as needed; logs error on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 16:51:46 +08:00
adminandClaude Sonnet 4.6 f7d52e8cb0 feat: add path helper functions to file_service
- get_app_dir(): project root in dev, exe dir in PyInstaller bundle
- get_resource_path/get_config_path: paths under app dir
- get_log_dir/get_output_dir: auto-create and return logs/ and output/
- Refactor log_service to use get_log_dir() instead of own __file__ calc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 16:48:37 +08:00
admin b8801ec024 fix: support isolated Python startup 2026-06-15 16:44:33 +08:00
adminandClaude Sonnet 4.6 24d43db434 feat: implement logging service with file output
- setup_logging() creates logs/ dir and writes timestamped log file
- format: asctime name levelname message
- falls back to console-only if log dir is not writable
- main.py now calls setup_logging() at startup and logs app name/version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 16:33:33 +08:00