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.
- 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.
- 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.
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.
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.
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.
- 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.
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.
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.
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.
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.
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.
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.
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.
- /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.
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.
/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.
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.
* 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
* 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.
* 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>
* 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)
* 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
* 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
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.
- 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
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)
- 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
_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
- 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.
- 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
- 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
- Fix AttributeError: 'str' has no attribute 'as_posix' on thumbnail_rel_path
in get_output_images and find_duplicates (thumbnail_rel_path is already a
string from f-string construction, not a Path object)
- Fix NameError: total_images undefined in generate_thumbnails (was defined as
total_media, causing progress logging and response to fail)
- Add URL encoding for paths in scan_output_dir for filenames with spaces
- Exclude thumbnails directory from image monitor to prevent linking thumbnail
files to prompts via fallback mechanism
- Bump version to 3.0.31
Add ability to configure a custom image scan directory in Settings:
- New "Image Scan Directory" field in Settings modal
- "Currently Monitoring" display shows active directory path
- Settings API now returns/saves gallery_root_path and monitored_directories
- Image monitor checks GalleryConfig before auto-detecting
- Restart notification when gallery path changes
Closes#76
Resolve conflicts in py/api.py by keeping feature/button's thumbnail
path generation approach (using rel_path.with_suffix('') and string
formatting) which was tested and working correctly.