Commit Graph
42 Commits
Author SHA1 Message Date
Vito Sansevero 2dbb21edc2 fix: clamp paging on tag listings and the page-derived recent offset
tags/stats, tags/{tag}/prompts and tags/filter parsed limit/offset with a
bare int(), so limit=999999 was passed straight to SQLite and a huge
offset raised OverflowError (500). They now share parse_page_params
(limit 1..500, offset 0..MAX_PAGE_OFFSET, 400 on non-integers), and
/recent clamps the offset it derives from ?page= the same way.
2026-10-08 18:01:34 -07:00
Vito Sansevero f9da6488d6 fix(autotag): contain batch rows, require media extensions, bound the scan
- The batch tagger runs every generated_images row through
  _resolve_allowed_image_path; rows that exist but lie outside the served
  output directories are skipped and counted in skipped_outside_roots on
  progress and complete events, and the engine receives the resolved path.
- _resolve_allowed_image_path requires a media extension (.png .jpg .jpeg
  .webp .gif .bmp .tiff .tif), so autotag/single returns 403 for other files.
- autotag/start holds a module-level lock; a concurrent start gets 409
  {"success": false, "error": "Auto-tagging is already running"}.
- scan_output_dir walks with a 50,000-file and 12-level cap, skips symlinked
  files and thumbnails, returns path through _public_path, relative_path in
  POSIX form, and a truncated flag.
- Autotag handlers report exceptions via the shared _public_error; the
  local copy is removed. Tests close the database before unlinking it.
2026-10-08 18:01:34 -07:00
Vito Sansevero d83383b9e0 fix(lora): confine LoraManager paths to custom_nodes and hide server paths
- is_civitai_url also refuses URLs with userinfo or a non-443 port, so
  https://evil.com\@civitai.com/x and https://civitai.com:8443/x are rejected.
- resolve_lora_manager_path / custom_nodes_directories: lora/enable and
  lora/scan only accept a LoraManager directory that is a direct child of
  ComfyUI's custom_nodes (folder_paths base_path or registered custom_nodes
  folders, else this package's parent) and whose name contains "lora",
  compared via realpath+normcase; relative public paths resolve against the
  API path anchors. Anything else is 400, so find_lora_directories can no
  longer widen the image allow-list with arbitrary trees.
- lora/enable persists through PromptManagerConfig.save_to_file(), which
  honours PROMPT_MANAGER_CONFIG_PATH, instead of a hard-coded config.json.
- detect/status/enable return path and detected_path through _public_path;
  every lora handler reports exceptions via _public_error, and lora/scan
  errors raised after the SSE stream opened become an error event.
- Tests close the database before unlinking it.
2026-10-08 18:01:34 -07:00
Vito Sansevero c8613bdb66 style: wrap long lines in the API layer, autotag and config
Split long f-string log messages, response messages, URL builders and
docstrings; DEFAULT_PROMPT and SYSTEM_PROMPT are rewritten as implicit
concatenations with identical values. Drop the unused shutil import in
admin and the unused HFCacheInfo import in autotag; keep the
hf_hub_download/snapshot_download availability probe with noqa: F401 and
mark the deliberately late imports in config with noqa: E402.
2026-10-08 18:01:34 -07:00
Vito Sansevero 1f422d2c43 fix: restore through SQLite's backup API, close_all for shared files, safer output scans
restore_from_file copied the uploaded file over the live database and deleted
its WAL while other PromptModel instances (nodes, image monitor, queue hook)
still held connections, which corrupts on POSIX and fails on Windows. It now
copies pages through the online backup API into the live connection so every
open handle sees the new content. PromptDatabase.close_all() closes the
connections of every thread, which tests need before deleting a database.
Output scans skip symlinked files, filesystem entries get a stable digest id
instead of a per-process hash(), and thumbnail streaming stops when the
client disconnects.
2026-10-08 18:01:34 -07:00
Vito Sansevero 0393d01746 fix: page image listings in SQL and drop three unused helpers
get_recent_images and search_images_by_prompt take limit/offset so the recent
and search image routes no longer over-fetch a 5000-row window and slice it.
generate_content_hash, PromptModel.migrate_database and find_duplicates had no
callers outside their own tests. The isolated logger tests re-apply their
thread filter after update_config rebuilds the handlers. Long lines in the
touched database and image modules are wrapped so the full flake8 hook passes.
2026-10-08 18:01:34 -07:00
Vito Sansevero 7128957d2c refactor: split diagnostics, maintenance and HTML serving into helpers
- run_diagnostics composes four module-level probes (_diagnose_database,
  _diagnose_dependencies, _diagnose_output_dirs, _diagnose_image_monitor).
- run_maintenance is table-driven: _maintenance_runners maps each
  operation name to a callable and _run_maintenance_operations reports
  per-operation success or a public error message.
- _read_json answers 400 for malformed or non-object JSON bodies in
  settings, duplicate delete, test-link and maintenance instead of 500.
- test_image_link delegates to _link_test_image.
- The four HTML page routes share _serve_html_page.
2026-10-08 18:01:34 -07:00
Vito Sansevero e282f6ea50 fix: clamp the logs limit and reject non-integers
GET /prompt_manager/logs accepted any limit up to 1000 and answered 500
when the value was not an integer. The limit is now clamped to [1, 500]
and a non-integer value is a 400.
2026-10-08 18:01:34 -07:00
Vito Sansevero 879247f1ef refactor: settings reads the image monitor through its module
get_settings located the ImageMonitor singleton by scanning sys.modules
for any module with 'image_monitor' in its name, a workaround for the
package being importable under two identities. It now uses the same
try-package / except-top-level import as the rest of the API
(_current_image_monitor) and falls back to the configured roots when no
monitor is running.
2026-10-08 18:01:34 -07:00
Vito Sansevero 38f4c0af43 perf: run duplicate and output scans off the event loop
The duplicate scan walked the output tree and hashed every file on the
aiohttp event loop, and the duplicate delete removed files inline; the
output scan ran its per-file database work one executor hop at a time.

- _find_duplicate_images_sync / _describe_media_file do the walk and
  hashing; find_duplicate_images() runs them via _run_in_executor.
- _delete_duplicate_images_sync / _delete_one_duplicate do the removal
  (absolute or output-relative paths) off the loop.
- The output scan is split into _collect_output_media_sync,
  _extract_batch_metadata_sync and _ingest_scan_batch_sync (one
  executor call per batch for all DB work) with a small SSE generator.
- Media collection is shared and de-duplicates by normcase'd path.
2026-10-08 18:01:34 -07:00
Vito Sansevero b0e60ec4b8 fix: no absolute server paths in API responses
Settings, diagnostics, duplicate/output scans and the logging endpoints
returned absolute filesystem paths, and several error envelopes echoed
str(e) which for OSError carries the full path.

- _public_path() renders a path relative to the ComfyUI base directory
  (folder_paths.base_path, else the output dir's parent) or to the
  parent of an opted-in extra gallery root, falling back to the
  basename; _public_error() reduces OSError to strerror + basename.
- GET /settings returns gallery roots and monitored directories in that
  form; POST /settings resolves relative roots against the same anchors
  (GalleryConfig.resolve_gallery_root) so the GET output round-trips.
- Diagnostics output_dirs, monitored_directories and the missing-DB
  message no longer carry absolute paths; the image monitor is reached
  through the package-or-top-level import fallback instead of a
  three-dot relative import that failed outside the package.
- Duplicate scan 'path' is now relative to the output root and the
  delete endpoint accepts relative paths.
- /logs/files 'path' and /logs/stats 'log_directory' use the public
  form; unexpected errors answer with a generic message.
2026-10-08 18:01:34 -07:00
Vito Sansevero e7fa993a42 fix: log config accepts only known keys
POST /prompt_manager/logs/config forwarded the whole request body into
PromptManagerLogger.update_config(), so any key could be injected into
the logging configuration. The handler now accepts only LOG_CONFIG_KEYS
(level, max_file_size, backup_count, console_logging, file_logging,
buffer_size), type-checks each value (level name, non-negative int,
bool) and answers 400 listing unknown keys. Malformed or non-object
bodies are 400 instead of 500.
2026-10-08 18:01:34 -07:00
Vito Sansevero c855a1e23b fix: gallery roots are restricted to ComfyUI directories
A gallery root could be set to '/' (or 'C:\') through POST
/prompt_manager/settings because the deny list only matched a few
system prefixes. The root was persisted to config.json and fed into the
image-serving containment check, which then allowed any file on disk to
be read through /prompt_manager/images/serve/.

- GalleryConfig.validate_gallery_root() accepts a directory only when its
  realpath lies inside a ComfyUI directory (folder_paths output/input/
  temp/user) or a directory listed in PROMPT_MANAGER_EXTRA_GALLERY_ROOTS;
  filesystem roots, the home directory, files and missing paths are
  rejected.
- save_settings returns 400 and persists nothing when any root fails.
- _get_all_output_dirs() filters configured roots through the validator
  and never falls back to a rejected root.
- PROMPT_MANAGER_CONFIG_PATH relocates config.json; load/save default
  to it.
2026-10-08 18:01:34 -07:00
Vito Sansevero b5720904cd fix: backups include WAL data and restores are integrity-checked
The database runs in WAL mode, so a shutil.copy2 of prompts.db silently
dropped every committed row still in prompts.db-wal; the backup
download and the pre-restore safety copy both did that. Restore also
overwrote the live file with connections open, read the whole upload
into memory before checking the 100 MB cap and never ran
PRAGMA integrity_check.

PromptModel.backup_database now uses the SQLite online backup API,
verify_database_file opens a candidate read-only and checks header,
integrity and the prompts table, and restore_from_file backs up the
live database, closes every thread's connection, copies the file,
removes stale -wal/-shm and re-runs migrations. The admin handlers
stream the upload to a temp file with an early size cutoff, reject
anything verify_database_file refuses with a 400 and keep the
{success: ...} JSON shape. The schema-init connection is now closed
instead of leaking.
2026-10-08 18:01:34 -07:00
Vito Sansevero 2cf6bdd393 refactor: share the internal error message in autotag routes 2026-10-08 18:01:34 -07:00
Vito Sansevero 5c592e4a87 fix: saving LoRA settings without a key keeps the stored CivitAI key 2026-10-08 18:01:34 -07:00
Vito Sansevero a6d4facfbf refactor: autotag model loading is injectable 2026-10-08 18:01:34 -07:00
Vito Sansevero b836e7cef9 fix: autotag/single checks real paths and DB rows against output dirs 2026-10-08 18:01:34 -07:00
Vito Sansevero 22d23020fe fix: CivitAI key is never returned or sent off-host 2026-10-08 18:01:34 -07:00
Vito Sansevero bcb2713baa fix: autotag endpoints no longer read arbitrary paths or run on GET 2026-10-08 18:01:34 -07:00
Vito Sansevero 027fdc09bb fix: cap every request-driven resource in the image routes
- /images/recent, /images/all and /images/output parse limit/offset
  through parse_page_params on every path (absent, 0, negative, float
  strings), and the clamped values are what reach the DB. /images/all
  gets its own MAX_BULK_LIMIT ceiling instead of an unbounded query;
  /images/recent caps its fetch window at MAX_RECENT_IMAGES_WINDOW.
- parse_page_params also clamps offset to MAX_PAGE_OFFSET so values past
  int64 no longer surface as a 500 from SQLite.
- Gallery and thumbnail scans share _iter_media_files: no symlink
  following, MAX_SCAN_DEPTH, and MAX_GALLERY_FILES / MAX_THUMBNAIL_FILES
  caps. Thumbnail size comes only from named presets.
- JSON bodies on /images/link and /images/generate-thumbnails are read
  in bounded chunks; over MAX_JSON_BODY_BYTES answers 413, malformed or
  non-object JSON answers 400.
- /images/link only accepts media paths inside the allowed directories
  (403 otherwise), closing a path-existence oracle.
2026-10-08 18:01:34 -07:00
Vito Sansevero 5fddf56eee perf: thumbnail generation no longer blocks the event loop
generate_thumbnails_with_progress is split into _thumbnail_targets
(scan, executor), _generate_one (one PIL/ffmpeg job, executor) and an
async driver that awaits each file via _run_in_executor, emits one
progress frame per file and yields between files. Per-file failures
are reported as file_error events (error stays fatal for the UI) and
the loop continues. The wildcard Access-Control-Allow-Origin header is
gone. The blocking POST variant reuses the same helpers. The scan never
follows symlinks and is capped by MAX_SCAN_DEPTH / MAX_THUMBNAIL_FILES.
2026-10-08 18:01:34 -07:00
Vito Sansevero 3228824161 fix: bound page sizes on list endpoints
/search, /recent and /images/recent share parse_page_params: limit is
clamped to [1, MAX_PAGE_LIMIT=500], offset to >= 0, and non-integer
values answer 400 instead of 500. /search now honours offset and all
three return a pagination block with the effective limit and offset.
2026-10-08 18:01:34 -07:00
Vito Sansevero 191d02eb6c fix: image routes serve only media files inside allowed directories
serve_image and serve_output_image now reject anything whose extension
is not in IMAGE_EXTENSIONS, fail closed when no output directory is
configured, and answer 403 (not 404) for dot-dot, absolute and
symlink-escaping paths. Containment is checked after realpath+normcase
so it behaves the same on Windows, macOS and Linux. 500 responses use
safe_error_message so OSError text no longer leaks absolute paths.
2026-10-08 18:01:34 -07:00
Vito 6c7e4d5b35 feat: release 3.2.4 — recently used sorting, run counts, positive-prompt image linking (#152)
* chore: start 3.2.4

* feat: track prompt usage (run_count, last_used_at) with server-side sorting

- Migration adds last_used_at and run_count; existing prompts are estimated
  from linked images (count, newest image time, else created_at). The
  backfill only touches NULL rows, so it self-heals rows written after a
  downgrade or an interrupted upgrade.
- Every node run records a use, including re-runs of an existing prompt;
  a tracking failure is logged and never blocks generation.
- search_prompts/get_recent_prompts accept a whitelisted sort key
  (last_used, created, run_count, rating, text); default stays newest first.

Refs #90

* feat: sort param on /recent and /search; microsecond last_used_at

- /prompt_manager/recent and /search accept ?sort= (whitelisted in the DB
  layer; unknown values fall back to newest first).
- last_used_at is written with microseconds from Python so a re-run in the
  same millisecond as another save still sorts first; ordering stays correct
  against millisecond values from the backfill.

Refs #90

* feat: server-side sort in admin UI with Recently Used / Most Used and run badges

- New web/js/prompt-list-sort.js owns the sort options, validation and
  request URLs; admin.js fills the dropdown from it and re-fetches from the
  server on change, so every sort spans all pages (the old client-side sort
  only reordered the current page).
- Default sort is Recently Used; prompt cards show a run-count badge with
  the last-used time on hover.
- The node's Recent button now lists recently used prompts.
- Contract test keeps the UI sort keys and the node's key in sync with the
  server whitelist.

Refs #90

* fix: link images and count runs by the positive prompt, not node execution

With a negative-prompt PromptManager node in the workflow, every executed
PromptManager node pushed onto the image-linking queue, so images could be
linked to the negative prompt or to a previous run. ComfyUI also skips
unchanged nodes, so re-runs of the same prompt were never counted.

- utils/prompt_graph.py finds the PromptManager nodes feeding a sampler's
  positive input (KSampler, CFG/Basic guiders, ControlNet and combine
  pass-throughs, PromptManagerText into encoders), falling back to all
  PromptManager nodes when none can be traced.
- Runs are counted by a ComfyUI on-prompt hook when a workflow is queued;
  prompts not yet saved are counted when the node first saves them.
- Nodes receive the queued graph (hidden PROMPT/UNIQUE_ID). Only the run's
  positive prompt becomes the current prompt; only batch items with linked
  text are pushed onto the FIFO queue.
- The image monitor links from the image's own metadata first, using the
  positive node, and falls back to the queue for batch items.

Refs #90

* fix: address review — count cached runs of linked text, keep repeated pending runs

- Text from known pure string nodes (core PrimitiveString/StringConcatenate,
  WAS Text Concatenate/Multiline, pysssss ShowText) is resolved at queue
  time, so cached re-runs of such prompts are counted and their images link
  from metadata. Unknown nodes are never guessed; the node compares its real
  text with the queue-time resolution and falls back to counting and
  queueing itself on any mismatch.
- A new prompt queued several times before its first run keeps every
  pending run; the first save records all of them.
- Registration flag is a threading.Event; consistent unittest imports;
  logging instead of print in the init fallback.

* refactor: unquote PendingFirstUse annotation so Tuple usage is visible
2026-10-07 13:52:09 -07:00
Vito aa768f57dd fix: release 3.2.3 — metadata parsing, null-safe saves, auto-hide, tag search (#151)
* fix: release 3.2.3 — metadata parsing, null-safe saves, auto-hide, tag search

- fix(#75): stop SaveImage patch from writing the last-run prompt into every
  PromptManager node (negative prompts were saved as copies of the positive)
- fix(#75): replace four duplicated metadata parsers with a shared
  web/js/comfy-metadata.js that traces sampler links (KSampler, CFGGuider /
  SamplerCustomAdvanced, LoRA chains, ControlNet pass-through, any checkpoint
  loader) and recovers per-node text for images saved by older versions
- fix(#143): treat null JSON body fields as empty in prompt API handlers
- fix(#149): keep auto-hide timeout 0 (disabled) instead of falling back to
  the default; validate result_timeout on save
- feat(#150): support tag:name filters in the node search box
- docs(#142): scan dialog mentions A1111/Forge metadata support
- docs(#144): fix ComfyUI-Lora-Manager links
- ci: run JS tests with node --test

* fix: bound metadata graph traversal to prevent tab freeze on crafted PNGs

Link resolution tried every linked input recursively with only a depth cap,
so a PNG whose prompt graph has cycles and fan-out could force ~inputs^25
paths and hang the gallery/metadata page. Each lookup now shares a visited
set (each node expanded once) with a hard cap of 500 visits.

* fix: read the matching output of multi-output settings nodes

KSampler settings linked to a node like SamplerCombo all resolved to its
first value, so steps, cfg and sampler showed the sampler name. Scalar
resolution now prefers the linked node's input with the same field name.

* fix: address review — trust API text, restore workflow-only parsing

- Workflow widget text now only replaces API text for metadata showing the
  pre-3.2.3 corruption signature (2+ PromptManager nodes with identical API
  text but different widgets). Otherwise the API graph wins, so expanded
  dynamic prompts and linked text display what actually ran.
- Images with only a UI workflow are converted to the API graph shape and
  traced the same way: prompts, KSamplerAdvanced, UNET/checkpoint loaders,
  KJNodes Set/Get, Reroute, bypassed and muted nodes.
2026-10-07 11:27:40 -07:00
c01d8e3c12 feat: recursive subfolder filtering + gallery tree navigation (#140)
* feat: recursive subfolder filtering + gallery tree navigation + thumbnail sizing

- Recursive subfolder filtering: selecting an intermediate folder shows all child images
- Gallery tree navigation panel with collapsible folder hierarchy (sticky, resizable)
- Thumbnail size controls (S/M/L) for adjustable grid density
- Fix path collision when multiple gallery roots share overlapping relative paths
- Fix auto-detect mode collapsing nested folders to ambiguous basenames
- Security test suite for path traversal edge cases

Addresses feedback from #126. Fixes code review items P1/P2 from #135.

Co-Authored-By: Kai Bowie <kaigtb@proton.me>

* style: fix Black formatting in test_path_security.py

* feat(gallery): add folder tree sidebar with hierarchical navigation (#126)

- Add collapsible folder tree sidebar to gallery view
- Support hierarchical year/month/date folder navigation
- Include ancestor path expansion for proper tree structure
- Add subfolder filtering to gallery image queries
- Update admin subfolder dropdown with ancestor support
- Fix trailing whitespace in SQL queries

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kai <noreply@gtb.ai>
Co-authored-by: Kai Bowie <kaigtb@proton.me>
Co-authored-by: Kai <kai@tachi-02.localdomain>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 07:58:59 -07:00
Vito 00284d92da feat: LoraManager integration — trigger words & example import (#52) (#136)
* feat: add LoRA utility module for LoraManager integration (#52)

Detection, metadata reading, trigger word cache, and prompt injection
for ComfyUI-Lora-Manager. All functions return empty results when
LoraManager is not installed.

* feat: add IntegrationConfig for opt-in third-party integrations (#52)

* feat(api): add LoraManager integration endpoints (#52)

Detection, enable/disable, scan/import, trigger word lookup,
and cache refresh endpoints under /prompt_manager/lora/*.

* feat: inject LoRA trigger words into prompts at encoding time (#52)

When enabled, scans for <lora:NAME:WEIGHT> tags in prompt text and
appends trigger words from LoraManager metadata. Behind config toggle.

* feat(ui): add LoraManager integration settings and import UI (#52)

Integrations section in settings with auto-detection badge, enable
toggle, trigger word toggle, and Import LoRA Data button.

* fix: detect LoraManager with case-insensitive directory scan (#52)

* fix: simplify LoraManager heuristic to match real install structure (#52)

* fix: find ComfyUI root via folder_paths and handle symlinked installs (#52)

* fix(ui): add pointer-events-none to toggle switch overlays (#52)

The styled div was intercepting clicks meant for the sr-only checkbox
input, preventing toggle switches from being clickable.

* fix: use lora-manager category and tag for imported LoRA data (#52)

* fix: use rglob to find metadata in lora subdirectories (#52)

* feat: discover LoRA dirs from extra_model_paths.yaml and folder_paths (#52)

Only models/loras under the ComfyUI root was checked. Now also parses
extra_model_paths.yaml and uses folder_paths.get_folder_paths('loras')
at runtime to find all configured LoRA directories.

* fix: use example prompts instead of trigger words, serve LoRA preview images (#52)

- Prompt text now uses civitai example prompts (images[].meta.prompt)
  when available, falling back to model name instead of trigger words
- Image serving now allows paths within LoRA directories when the
  integration is enabled, fixing 403 errors on preview images

* fix: handle null civitai field, link all preview images (#52)

- Guard against civitai: null in metadata (was crashing the scan)
- Link all local preview images per LoRA, not just the first
- Add get_civitai_image_urls() for future remote image support

* feat: reimport clears previous lora-manager data first (#52)

Clicking Import LoRA Data now deletes all existing lora-manager
category prompts before scanning, ensuring a clean reimport.

* feat: download civitai example images during LoRA import (#52)

Images from civitai.images[] are downloaded to data/lora_images/ cache
and linked to prompts alongside local preview files. Cached files are
reused on reimport. Image serving allows the cache directory.

* feat: add CivitAI API key setting for authenticated image downloads (#52)

Most civitai example images (especially NSFW) require authentication.
Adds API key field to the Integrations settings panel, passed as
Bearer token when downloading example images.

* feat(ui): add progress modal for LoRA import (#52)

Closes settings modal and shows a dedicated progress modal with
progress bar, status text, and processed/imported counts during
LoRA import. Auto-closes after completion.

* fix: reduce download timeout to 5s, update progress per LoRA (#52)

5K images at 15s timeout was painfully slow. Reduced to 5s fail-fast.
Progress now updates for every LoRA with image count, not every 5th.

* perf: use 512px thumbnails and parallel downloads for LoRA images (#52)

Full-size civitai images averaged 5.6MB each (27GB total for 5K images).
Now requests /width=512/ thumbnails (~50-100KB) and downloads 8 in
parallel per LoRA. Expected speedup: ~100x smaller + 8x parallel.

* fix: resize downloaded images to 512px thumbnails locally (#52)

Civitai CDN returns 401 for /width=N/ thumbnail URLs with API key auth.
Instead, download the original and resize to 512px via PIL before saving.
Reduces disk usage from ~5MB to ~30-50KB per image.

* docs: update README for v3.2.1 LoRA Manager integration (#52)

- Add LoRA Manager integration section with setup guide and CivitAI key docs
- Add folder filter section with rescan note for existing libraries
- Add v3.2.1 changelog entry, split from v3.2.0
- Add WIP notice for LoRA Manager feature
- Update AutoTag section to include WD14 models
- Fix stale references (KikoTextEncode, outdated file structure)
- Remove dated v2 development note
- Add screenshots for settings, integration, and filtered results
- Fix code review items: remove unused constant, add comments to empty excepts
- Bump version to 3.2.1

* test: add unit tests for LoRA integration and folder filter (#52)

- test_lora_utils.py: 32 tests covering trigger word extraction, example
  prompts, image URLs, metadata parsing, cache dir, TriggerWordCache
- test_lora_database.py: 17 tests covering delete_prompts_by_category,
  folder filter search, get_prompt_subfolders, LoRA import workflow
- test_config.py: 7 new IntegrationConfig tests for structure, enable,
  partial update, reset, and roundtrip

* fix: remove unused MagicMock import in test_lora_utils (#52)
2026-04-03 18:26:23 -07:00
Vito 6fc6e683eb feat: multi-directory gallery scan with folder filter (#126) (#135)
* refactor(api): add multi-root output dir infrastructure (#126)

- Change _gallery_cache from None to {} (dict keyed by path string)
- Update invalidate_gallery_cache() to clear dict instead of setting None
- Add _get_all_output_dirs() helper as single source of truth for
  resolving configured + fallback output directories
- Update _enrich_prompt_images() to iterate all output roots when
  computing relative paths and thumbnail URLs

* feat(api): support multiple gallery scan paths in settings endpoint (#126)

* feat(ui): dynamic multi-path settings with add/remove buttons (#126)

* cleanup: remove dead galleryRootPath assignment in loadSettings

* feat(api): multi-directory gallery scan with subfolder filter and multi-root serve (#126)

* feat(db): add folder filter to search_prompts and get_prompt_subfolders (#126)

* feat(api): add subfolders endpoint and folder filter to prompt search (#126)

* feat(ui): add folder dropdown filter to search bar (#126)

* fix: escape LIKE wildcards in folder filter and remove dead _gallery_cache_time field

* feat(scan): scan all media types across all output dirs with batched extraction (#126)

* chore: gitignore SQLite WAL-mode temp files

* fix: enrich prompt images with URLs and check all output dirs for serving

- Extract _enrich_images() for flat image lists, used by get_prompt_images
- get_prompt_images now adds url/thumbnail_url (fixes stuck "Loading images...")
- serve_image checks all output dirs not just first (fixes 403 on multi-path)
- Fix diagnostics monitor check to read current module attribute, not stale import

* feat(ui): add version-scoped update notice popup for rescan (#126)

* chore: bump version to 3.2.0
2026-04-03 06:46:01 -07:00
Vito 2cf9f42e73 fix: diagnostics and monitor respect config, singleton lifecycle (#132) (#133)
* fix: diagnostics, backup, and restore now respect config.json db path (#132)

* test: add regression tests for config-aware diagnostics and backup (#132)

* fix(test): use fresh event loop to avoid aiohttp test pollution

* fix: don't kill singleton image monitor when node instance is garbage collected

The __del__ method was calling stop_monitoring() on the shared singleton
ImageMonitor, killing it for ALL nodes whenever any single node instance
was garbage collected between workflow runs. This caused the monitor to
restart on every execution and miss image save events due to race conditions.

* test: add regression tests for image monitor singleton lifecycle

Ensures cleanup/del never stops the shared monitor, singleton is
thread-safe, and observer survives across node instance lifecycles.

* style: address PR review — log suppressed exceptions, remove unused imports
2026-04-03 06:22:55 -07:00
Vito 37962e30eb fix: validate WD14 threshold params to return 400 on bad input (#115) (#116)
Wrap float() casts for general_threshold and character_threshold in
try/except so malformed values return a structured 400 error instead
of an uncaught ValueError causing a 500.

Addresses review feedback on PR #114.
2026-02-11 05:55:52 -08:00
Vito Sansevero fdda1c4ad5 chore: bump version to 3.1.0, update changelog, fix tag styling and filters
- Add append: to autotag skip-tagged filter alongside prepend:
- Update tag chip styling to dark fill with white text
- Add v3.1.0 changelog entry covering all changes since v3.0.26
2026-02-10 17:36:33 -08:00
Vito Sansevero 42f9c344f8 feat: add WD14 tagger support (SwinV2 + ViT) with threshold controls
Add two WD14 classifier models as alternatives to JoyCaption LLM taggers.
WD14 models are ~400MB ONNX classifiers that output Danbooru tags with
confidence scores — much faster inference, no prompt needed.

Backend:
- Add wd14-swinv2 and wd14-vit entries to MODELS dict
- Add check/download/load/generate methods for ONNX-based WD14 inference
- Add general_threshold and character_threshold params to generate_tags()
- Filter "prepend:*" tags from exclude prefixes and skip-tagged logic

Frontend:
- Add WD14 radio buttons with download status in both admin and gallery
- Toggle prompt textarea vs threshold sliders based on model selection
- Pass thresholds to API for batch and single-image tagging
- Extract _setModelStatus() helper using safe DOM methods

Dependencies: onnxruntime (optional, with clear error message)
2026-02-10 16:27:40 -08:00
Vito Sansevero 30f83f2401 fix: address code review — unused import, silent except, gallery cache
- Remove unused `patch` import from test_output_dir.py
- Add debug log message to ImportError handler instead of bare pass
- Invalidate gallery file listing cache alongside output dir cache
  when the user changes gallery_root_path in settings
2026-02-09 05:46:38 -08:00
Vito Sansevero 6a5834e243 fix: use configured directory for image scan instead of always auto-detecting
_find_comfyui_output_dir() now checks GalleryConfig.MONITORING_DIRECTORIES
first before falling back to filesystem auto-detection. Also invalidates
the cached directory when the user changes the setting via save_settings().

Closes #108
2026-02-09 04:52:03 -08:00
Vito Sansevero 9e8864e453 fix: resolve broken imports and argument mismatch in API handlers
- Move utils imports to module-level with try/except for relative
  (ComfyUI) and absolute (test runner) import contexts in prompts.py.
  Fixes 500 errors on rating update, prompt save, and prompt edit.
- Add missing None for rating parameter in image scan's save_prompt
  call, which was passing the notes string as rating causing a type
  comparison error during image scanning.
2026-02-08 08:48:17 -08:00
Vito Sansevero 7f92c4d5b8 fix: preserve full tracebacks in server-side error logging
Use logger.exception() and exc_info=True to retain stack traces
for debugging, addressing Codex review feedback on PR #110.
2026-02-08 05:33:06 -08:00
Vito Sansevero be205f3dcc feat: add pre-commit hooks for local CI checks
- Black (auto-fix formatting on commit)
- Flake8 (block on syntax errors and undefined names)
- Bandit (security scan, non-blocking to match CI)
- Tests (run on pre-push only)
- Apply Black formatting fixes caught by the new hooks
2026-02-08 05:27:03 -08:00
Vito Sansevero ed40be066c fix: prevent stack trace exposure in SSE error responses
Log exception details server-side only; send generic error messages
to clients via SSE streams to resolve code scanning alerts.
2026-02-08 05:23:04 -08:00
Vito Sansevero 113d663fce fix: resolve 15 GitHub code scanning security alerts
- Fix XSS in showFullPrompt() across admin.js, gallery.js, metadata.html
  by replacing HTML string interpolation with DOM manipulation (createElement
  + textContent), eliminating unsafe user content injection
- Fix stack trace exposure in py/api error responses by replacing str(e)
  with generic messages and adding logger.exception() for server-side
  traceability
2026-02-07 13:04:52 -08:00
Vito Sansevero 2737a4b1ef style: apply Black formatter (line-length=88) to all Python files
Automated formatting pass across 30 files to establish consistent code
style enforced by CI. No logic changes.
2026-02-07 08:30:09 -08:00
Vito Sansevero 4548beb723 refactor: split api.py into domain modules and extract frontend JS
Phase 4 structural refactoring:
- Split monolithic py/api.py (5.3k lines) into domain mixins:
  prompts.py, images.py, admin.py, logging_routes.py, autotag_routes.py
- Extract inline JS from admin.html into web/js/admin.js
- Extract inline JS from gallery.html into web/js/gallery.js
- Add gzip compression middleware to API responses (Phase 5.4)
- Standardize API error envelope with success: false (Phase 5.3)
- Add filmstrip image enrichment to prompt list responses (Phase 5.2)
2026-02-07 07:02:25 -08:00