From an outside audit:
- The scan wizard's offline mode still sent the greyed-out rename and
overwrite options, and scraper.py fetched Civitai model pages and preview
media for models whose .info held a Civitai id. Offline now implies no
media and no model page; the folder scan and scan_all refuse a missing or
non-object body instead of defaulting to an online scan.
- The Prompt Workshop's snap dock referenced leftSearch from the source deck
module (ReferenceError on click); the deck now exposes focusSearch().
- translate, delete_model, delete_gallery_image and the image-material
routes answer malformed bodies with 400 instead of 500.
- Removed six unused imports in ui_doctor.js and 16 duplicate locale keys
per language (the later, effective entry is kept, so nothing changes on
screen).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The F5-TTS voice library (input/F5-TTS scanning, voice upload with ffmpeg
conversion, romanization, Load Audio clip drags) and the engine switch are
removed. The audio page now works with one engine, Anomalous_TTS
(GPT-SoVITS): characters, drags onto its node, the Script Director with
direct generation, pronunciation and settings. The audio gallery and its
stream route stay (output and temp folders only). The root PATH hack that
existed for F5-TTS's ffmpeg lookup is gone. Recorded as AD-016.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Script Director gets a Generate section for GPT-SoVITS characters
(ui_script_run.js): it queues the script as an API prompt with this
page's client id (audio_tts_run.js), shows queued/running/progress,
plays the result and saves it to output/audio/<character>/. The canvas
is never touched. A line card's retake button adds [take:N] to that
line so only it is regenerated; "New take of all" changes the seed.
Cancel deletes a queued job or interrupts it by prompt id when running
(checked against /queue, since the start event can arrive first).
Events that arrive before /prompt answers are buffered and replayed.
Language/speed start from the character's `defaults` and can be saved
back (read-modify-write, unknown fields kept). Needs Anomalous_TTS
interface 11; older versions get a hint and keep push-to-node.
The Arona multi-voice template read a file only one machine has; the
button, route, helper (alignedVoiceValue) and strings are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sending a Prompt Note with a UNet main model chained its LoRAs through
LoraLoader, whose clip input nothing could feed. UNet LoRAs now use
LoraLoaderModelOnly and the editor says to connect a text encoder.
Models without a baseModel in their metadata matched no base model, so
they never appeared; base_models now reports them and
compatible_models serves them for "__unlabeled__". Both routes share
one walker and run it off the event loop.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving a workflow recipe failed for any workflow with a model loader:
recipe_images.py called _resolve_exact_model_reference and hashlib, which
stayed behind in recipe_schema.py when 6ad975c split the module. The
resolver moves to path_utils.py so both recipe modules can import it
without a cycle.
The same split left media_routes.py without time/tempfile (card
thumbnails silently fell back to full-size covers) and model_metadata.py
without _resolve_paths_to_model_info_sync (editing a moved model failed).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- audio_engines.js: F5-TTS and GPT-SoVITS (the separate Anomalous_TTS node
pack), detected through /object_info/<node class>. Both engines' voices are
normalised into one voice-group shape so cards, sidebar and Script Director
share one implementation.
- The audio page shows an engine switch; a missing engine stays listed as
"not installed" with install steps (no popups). The F5-TTS library still
works without the F5-TTS node; GPT-SoVITS is never scanned when missing.
- GPT-SoVITS cards come only from the node's HTTP contract: preview reference
clips, drag a character onto AnomalousTTS_CharacterSpeech, and edit the
emotion references ("Edit emotions"), saved through /anomalous_tts/settings
with unknown fields preserved and the node's validation messages shown.
- Drop targets and the Script Director are per engine: voices of one engine
are refused on the other engine's node with the reason.
- Audio gallery reads the character of GPT-SoVITS generations.
- Usage guide (in-app and docs/guides/audio-studio.md) covers both engines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeQEub5
Conflicts:
- api/__init__.py, web/styles.css: keep audio routes/styles and add the
version manager.
- README.md, CHANGELOG.md: take main's released text; this branch's
0922b27 was an earlier alignment of the same v1.57 notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeQEub5
- api/version_manager.py: installed version (local only), published
releases on request (GitHub API, git tag fallback), switch to a
published tag, return to the default branch (fast-forward only), undo
the last switch; refuses to overwrite modified tracked files
- header version chip and "Version & updates" panel; nothing goes online
until "Check for updates" is clicked; restart via ComfyUI Manager
- zh/en strings, styles, architecture notes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeQEub5
Conflicts resolved:
- entry trigger files, web/main.js, api/__init__.py: main carries the squashed
entry fix (09b06d1), identical to this branch before its later cleanup, so
this branch's versions are kept
- ARCHITECTURE.md, docs/architecture/backend.md, web/modules/ui_sidebar.js:
both sides' additions kept
Verified on the merged tree: 107 Python tests, all JS tests except the orphaned
material_parameter_cross_node.mjs, jsdom walkthroughs of the audio studio,
domain switching and the gallery search blocks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
Splitting the query on spaces hid how it was read and made phrases like
"cyberpunk city" impossible to search.
- ui_search_chips.js: Enter or a comma (also "," / "、" inserted by an IME)
turns the typed text into a block; blocks keep their spaces and match as a
phrase; hash-like and numeric blocks are labelled; × removes a block,
Backspace in an empty box removes the last one, one button clears all; text
still being typed joins the search after a short pause; IME composition
never commits a block
- gallery_images accepts repeated `term` params (q still works)
Verified: tests/test_image_search.py (phrase blocks vs split query, 107 Python
tests in total) and a jsdom walkthrough of blocks, IME commas, backspace,
remove, clear and the request parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
- api/image_search.py reads only the PNG text chunks before IDAT (ComfyUI
prompt/workflow, A1111 parameters), builds one small record per image and
caches it by mtime; all space-separated terms must match
- hex terms of 8+ characters also match SHA256 values recorded in the
workflow (extra.anomalous_hashes) and local model files with that hash, so
renamed models are still found; A1111 "Model hash" text matches as text
- gallery_images accepts q; the search box above the gallery debounces input,
shows the match count, keeps paging/infinite scroll and the detail viewer on
the filtered list, and lets a newer query supersede one still loading
On the local output (774 PNG) the first search reads ~10 MB of text chunks;
later searches use the cache.
Verified: tests/test_image_search.py (prompt, seed, AND terms, A1111, file
names, recorded and local-index hashes, cache, chunk reader), full Python suite
(106), jsdom walkthrough of the gallery search box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
Since 9cebdbd9 model_resolution.py used MODEL_EXTENSIONS without importing it.
/anomalous/all_hashes swallowed the NameError and always returned {}, and
resolve_hash / resolve_hash_batch (recipe model matching) failed while
collecting candidates. The all-hashes walk is now collect_model_hash_index()
so the output gallery search can reuse it; the endpoint behaves as before.
Verified: tests/test_image_search.py::ModelResolutionImportTests; on the local
library the index now returns 191 keys (176 with SHA256) instead of 0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
The gallery listed file names only, and audio from Preview Audio nodes (the
template used one) lives in ComfyUI's temp folder, so the gallery stayed empty.
- api/audio_metadata.py reads ComfyUI's `prompt` comment from FLAC and
Ogg/Opus files (read-only) and extracts the TTS node's speech, sample, seed
- gallery rows show the speech (emotion chips, expandable per-segment script),
voice and seed, with copy-speech; results are cached by mtime/size
- search (?q=) matches speech text and file names
- "unsaved previews" section lists temp audio with "save to gallery", which
copies into output/audio/ (O_EXCL, never overwrites; the preview is untouched)
- audio_stream serves input/output/temp only; .opus is recognized
- parseTaggedSpeech in audio_script.js reads speech back the way F5-TTS splits it
Also outside the repo: ComfyUI/arona_multivoice_workflow.json now ends in a
Save Audio node (prefix audio/F5-TTS/Arona) instead of Preview Audio.
Verified: 100 Python tests (5 new gallery tests with synthetic FLAC/Opus),
tests/audio_script_rules.mjs, jsdom walkthrough (speech rows, preview save,
search, delete, paging), and a read-only run on the two real temp previews.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
Keeps the planned All-in-One loader and cross-node prompt features but removes
the regressions they introduced:
- model references: one adapter table for verified layouts (easy a1111Loader,
easy fullLoader, Efficient Loader) shared by recipe_identity.js and
recipe_schema.py; native loaders keep their stored labels; no more guessing
categories from file names (a "...vae.safetensors" checkpoint was a VAE);
"None"/"Baked VAE" are never references
- recipe summary sync follows the extraction rules: only checkpoint/UNet
loaders or ckpt_name/unet_name set the base model (not an upscaler's
model_name), only sampler-like nodes set seed/steps/cfg, and LoRA edits
update the entry of the edited loader instead of falling back to loras[0]
- one LoRA entry per loader again (deduplication shifted entries)
- prompt injection never writes negative text into a positive slot or the
reverse; the block chooser follows the same rule
- material cards only accept cross-node drops that carry prompts; parameter
blocks stay same-type
- drop unused imports and the unused modelSpecs alias
Verified: regression cases added to tests/recipe_all_in_one_compatibility.mjs
and tests/material_prompt_cross_node.mjs (both fail on the previous commit),
new tests/test_recipe_model_references.py; all JS tests except the orphaned
material_parameter_cross_node.mjs and 95 Python tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
ComfyUI-F5-TTS resolves {happy} to <sample stem>.happy.wav beside the selected
sample, so `Arona_happy.wav` + `{Arona_happy}` could never run. Voices now use
Character.wav (main, {main}) and Character.<emotion>.wav ({emotion}); the
catalog groups a main voice with its variants and marks tags that F5-TTS
cannot resolve (missing main, missing .txt, different format).
- script director: one character per script, untagged lines pinned to {main},
pushes sample (main voice, node's own combo spelling) and speech together,
confirms before overwriting and re-checks the node after the dialog
- pure rules (splitting, composing, planning, combo spelling) in audio_script.js
- one module instance per file: drop ?v= imports that duplicated
ui_audio_gallery/ui_audio_studio state, so studio playback stops the gallery
- all user/file-derived text goes through textContent (no innerHTML)
- studio/sidebar/gallery renders ignore stale responses; errors are shown
- gallery: count badge no longer overwrites the title, paging via "load more",
deletion errors surfaced, timeupdate instead of a 100ms timer
- sidebar: filter selection updates in place instead of refetching; the header
tabs, sidebar and domain switch share browser.switchAudioTab
- uploader: createViewScope lifecycle (Esc/overlay/close release listeners and
object URLs, Esc no longer reaches the parent), 409 -> confirm -> overwrite,
sends the pre-romanization text as original_text, no hard-coded character
- workflow template: confirm before replacing the canvas, clear message when
the template file is missing, align sample paths to the node's combo values
- static styles moved from inline JS into 11-audio-studio.css; strings localized
Verified: tests/audio_script_rules.mjs, tests/test_audio_catalog.py (15), a
jsdom walkthrough of studio/gallery/director/uploader with a stubbed app
(outside the repo), JS syntax and import/export link check. Not yet checked in
a live ComfyUI session.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5
- upload: validate target folder with resolve_within, refuse to overwrite an
existing voice unless overwrite=1, stage audio/transcripts and swap them in
together so conversion or disk failures keep the previous voice, drop stale
.txt/.orig.txt and other-format siblings, write .orig.txt for the native script
- ffmpeg is resolved explicitly (PATH, then portable python dir) with a timeout
- voice/gallery scans run in a worker thread; stems are unique across scan
folders; audio URLs are percent-encoded and versioned by mtime
- stream and delete only accept audio extensions; paging and template names
are validated; errors are JSON
- romanizer no longer reads Han-only (Chinese) text as Japanese and reports
missing pykakasi / hangul-romanize instead of silently succeeding
- extension static files use Cache-Control: no-cache (revalidate) instead of
no-store; document the deliberate PATH exception in __init__.py
Verified: new local tests/test_audio_catalog.py (14 tests) plus the existing
Python suite (78 tests) pass in a Linux venv; not yet exercised in ComfyUI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W411Z69FC3em9CzpeqEub5