- 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
27 KiB
Anomalous Model Browser Architecture
This is the short entry point for maintainers and AI agents. It describes the system map, ownership boundaries, and rules that apply across subsystems. Read only the linked topic document relevant to the task; do not load every document by default.
Reading map
Before implementation, follow AGENTS.md, the canonical development and maintenance rules. GEMINI.md is the Gemini reading entry point.
The prompt studio reads material prompt payloads through web/modules/material_prompt_data.js;
list summaries do not contain prompt bodies. See the Material Library contract below.
| When changing... | Read... |
|---|---|
| Python routes, storage, paths, metadata, covers, or scan state | docs/architecture/backend.md |
| Browser lifecycle, UI state, localization, media, or graph edits | docs/architecture/frontend.md |
| Update-guide content/versioning or sidebar hover labels | docs/architecture/update-guide.md |
| Workflow Recipes, packages, galleries, Parameter Notebooks, or prompt roles | docs/architecture/recipes.md |
| Material Library snapshots, image parameter details, or reusable node blocks | docs/architecture/material-library.md |
| Model Doctor, provenance hashes, missing-model recovery, or deep scanning | docs/architecture/model-resolution.md |
| Browser audits, E2E functional bug reports, or verification sign-offs | docs/audits/README.md |
| Why a current product boundary exists | docs/decisions/README.md |
| Recurring implementation mistakes and post-mortems | .agents/logs/ai_lessons.md |
README.md is user-facing documentation and CHANGELOG.md is user-facing
release history. Neither is the source of truth for internal architecture.
System shape
Anomalous Model Browser is a UI-only ComfyUI extension. It registers no custom
nodes (NODE_CLASS_MAPPINGS is empty). ComfyUI loads the Vanilla JavaScript/CSS
frontend from web/; the Python package registers /anomalous/ aiohttp
routes and performs local filesystem, metadata, recipe, and scan operations.
ComfyUI frontend
web/main.js
-> web/modules/* UI, graph integration, localization
-> /anomalous/* JSON and bounded media requests
ComfyUI Python server
__init__.py
-> api/__init__.py route registration
-> api/*.py storage and domain operations
-> scraper.py explicit metadata/hash enrichment
User-owned data
ComfyUI model folders models and sidecars
user/.../anomalous_recipes workflow recipes and recipe assets
user/.../anomalous_parameters immutable parameter snapshots
user/.../anomalous_materials curated image/workflow material bundles
user/.../anomalous_notebooks prompt notes, with legacy originals preserved
The frontend and backend communicate through narrow JSON contracts. The frontend must not infer filesystem authority, and the backend must not depend on DOM or live LiteGraph state.
Ownership map
Backend
-
api/config.pyowns configured paths and active model-folder types. -
api/path_utils.pyowns containment, filename validation, and atomic JSON writes;api/utils.pyis a compatibility export surface. -
api/metadata.pyowns sidecar and safetensors metadata extraction. -
api/model_catalog.py,api/model_resolution.py,api/model_metadata.py, andapi/model_media.pyown model listing, identity recovery, mutation, and covers;api/models.pyis a compatibility facade. -
api/scanner.pyandscraper.pyown scan orchestration and enrichment. -
api/workflow_schema.py,api/recipe_schema.py,api/recipe_images.py, andapi/recipe_store.pyown recipe validation/shaping, images, CRUD, history, and integrity receipts;api/recipes.pyis the HTTP facade. -
api/recipe_packages.pyowns bounded inspect-stage-commit package handling. -
api/parameters.pyowns Parameter Notebook persistence and lookup. -
api/notebooks.pyowns Prompt Note persistence and recoverable legacy copying. -
api/material_schema.py,api/material_assets.py, andapi/material_store.pyown curated material shaping, private assets, persistence/cache, search and lifecycle;api/materials.pyowns HTTP mapping and compatibility entry points. -
api/media_routes.py,api/gallery_routes.py,api/translation_routes.py, andapi/folder_types.pyown the formerly mixed utility route families. -
model_policies.pyowns shared backend rename and protected-category policy. -
model_identity.pyowns file SHA-256 evidence shared with the standalone scanner. -
api/audio_catalog.pyowns voice scanning, streaming, output-audio history and voice ingestion. Voices follow the ComfyUI-F5-TTS multi-voice layout:Character.wav+.txtis the main sample,Character.<voice>.wav+.txtare variants addressed as{<voice>};<stem>.orig.txtkeeps a native-script transcript for display. Ingestion validates the target folder, refuses to overwrite withoutoverwrite=1, and stages all files before swapping them in. -
api/audio_metadata.pyreads the ComfyUIpromptcomment from FLAC and Ogg/Opus files (read-only) and extracts the TTS node's speech, sample and seed for the audio gallery. -
api/romanizer.pyconverts Japanese (text containing kana) and Korean to Latin script for transcripts; Han-only text is left unchanged, and missing optional dependencies (pykakasi, hangul-romanize) are reported, not hidden. -
api/image_search.pypowers the output gallery search (gallery_images?q=): it reads only the PNG text chunks before the pixel data (ComfyUI prompt and workflow, A1111parameters), caches one record per image by mtime, and matches all query terms; hex terms of 8+ characters also match recorded model SHA256 values and, viacollect_model_hash_indexinmodel_resolution.py, local model files with that hash. Terms arrive as repeatedtermparams, one phrase each.ui_search_chips.jsis the reusable search-block input (Enter or a comma commits a block);ui_gallery.jsplaces it above the gallery. -
api/version_manager.pyowns the plugin's own version: the installed tag/branch (/anomalous/version, local only), published releases fetched only on request (GitHub releases API, falling back togit ls-remotetags; drafts skipped, pre-releases never counted as latest), switching to a published tag (detached checkout), returning to the default branch (fast-forward only), and undoing the last switch (recorded in the ignored.anomalous_version.local.json). Every switch refuses to run over modified tracked files.ui_version_manager.jsis the version line (shown in the update guide opened by the header "!" button) and panel; nothing goes online until "check for updates" is clicked, and restarts go through ComfyUI Manager's reboot route when present. -
web/main.jscoordinates extension registration (with?v=...versioned module imports busting aggressive browser ES Module caching and unconditional legacy storage key purging).browser.jsowns the shared browser class and extracted-method wiring;browser_entry.jsowns the single browser instance plus floating/topbar/menu entry behavior (pointer-capture drag, pre-mount positioning to avoid a flash,anomalous_trigger_pos_v3persistence); the unsaved default position is defined in CSS;entry_controls.jsowns entry mode, trigger size/style normalization, viewport clamping that keeps the button out of the sidebar dock, and saved-position storage (isValidSavedTriggerPosition);api/__init__.pysendsCache-Control: no-cachefor this plugin's static files so a normal refresh revalidates them (modules are imported without?v=query strings, which would create second module instances); andinterface_settings.jsowns language and theme preferences. -
ui_domain_switcher.jsowns the visual/audio domain toggle and its stored choice;browser.switchAudioTabis the single entry for audio navigation (header tabs, sidebar entries, domain switch) andhideAllPanelsstops audio playback. -
audio_engines.jslists the speech engines the audio page can manage (F5-TTS, GPT-SoVITS via the separate Anomalous_TTS node pack) and detects them at runtime through/object_info/<node class>. Every engine's voices are normalised into one voice-group shape (node_value= what the node's voice widget takes), so cards, sidebar and Script Director share one implementation. A missing engine stays in the switch marked "not installed" with install steps; nothing else depends on it. GPT-SoVITS data comes only from the node's HTTP contract (/anomalous_tts/characters,/audio,/settings; see the project docanomalous-tts-interface.md); Anomalous never reads or writes its model folders.ui_audio_tts_editor.jsedits a character's emotion references through that API, keeping settings fields it does not know. -
audio_node_targets.jsis the single table of canvas nodes the audio studio writes into and what each takes: F5-TTS (F5TTSAudio,F5TTSAudioAdvanced,F5TTSAudioFromModel) takes a whole character (main voice insample, emotions via{tags}inspeech); ComfyUI'sLoadAudiotakes one exact clip.planVoiceDropdecides every drop and names the reason for each refusal; unlisted nodes are refused, never matched by widget name. Supporting a node means adding one entry plus a test. -
ui_audio_studio.jsowns the voice cards, preview playback, tag copying, drags (card header = character, row = clip) through the sharedbindMaterialDragwithtargetHint/rejectHinttelling the user what releasing does, and the template workflow loader (confirms before replacing the canvas).audio_script.jsholds the pure F5-TTS rules: script splitting, bundling (buildScriptPackage) and combo value matching.ui_script_director.jsowns the script drawer: one character, an emotion chip row per line card, and a bundle that is pushed to the selected/only script-capable target node or dragged onto one; it writes the voice widget (main voice) and the speech widget together. The studio feeds it the voice groups after each fetch. -
In the audio domain the header ! opens
AUDIO_USAGE_GUIDE(a how-to, seedocs/guides/audio-studio.md) instead of the visual update guide. -
ui_audio_uploader.jsowns the voice ingestion modal (createViewScope lifecycle, overwrite confirmation, optional romanization keeping the original script). -
ui_audio_sidebar.jsowns the audio navigation and the active audio filter. -
ui_audio_gallery.jsowns the generated-audio history (output folder): playback, seeking, search, paging, download and deletion, showing the speech/voice/seed recorded in each file, plus a section for unsaved temp previews that can be copied intooutput/audio/. -
ui_sidebar.jscreates the browser shell, domain-aware header tabs, and folder navigation.ui_settings_hub.jsowns settings and model-card preferences;ui_toolbox.jsowns the tool catalog, fixed shortcut bar, and tool dispatch;ui_browser_navigation.jsowns shared panel hiding/cleanup and workspace return. Scan-wizard launch, single-model precision scans (triggerDirectModelScanwith strictly factual Civitai vs non-Civitai feedback reporting inferred base-model or match status directly within the bottom-right progress panel and toasts without blocking browser alerts), modal lifecycle ergonomics (backdrop click and Escape key dismissal with listener detachment, scrollable content area with sticky footer actions), post-scan frontend hash and native combo refreshes (app.refreshComboInNodes(),window.anomalous_reload_hashes()), and polling live inui_scan_wizard.js; folder visibility/order lives inui_folder_manager.js; and help content lives inui_help.js. -
ui_model_sources.jsowns the Model Sources Hub, managing workflow-model and global-library source detection, Civitai/HuggingFace URL attribution, sidecar persistence, and resilient scope switching between active workflow and full local library (with cached library state preservation and reliable re-rendering). -
ui_materials.js,ui_material_cards.js, andui_material_application.jsown the Material Library UI, category navigation, card presentation (with grab cursor affordances, explicit drag tooltips, and polymorphic card dragging viabindPolymorphicMaterialCardDrag), context-aware drag guidance, relaxed third-party node prompt widget sniffing and injection, and the structured empty state onboarding blueprint guiding users through collection, canvas drag, and prompt studio mixing. Drag precedence prioritizes node hits over blank canvas drops; blank canvas drops auto-instantiateCLIPTextEncodenodes for prompt materials with standard colors or open full workflows.node_material_actions.jsowns prompt envelope extraction (extractMaterialPromptEnvelope) shared by detail views, cards and canvas actions. -
ui_update_guide.jsandupdate_guide_data.jsown the non-intrusive update guide modal (accessible via header button#anomalous-update-notice-btnand Help modal; version ID2026-09-recipes-and-studios), presenting a 4-step milestone walkthrough (Workflow Recipe Studio, Material Library & Prompt Studio, Model Sources Hub, and Precision Direct Scan with canvas addition) with full bilingual localization.ui_spotlight_tour.jsprovides the interactive spotlight mask tour (startSpotlightTour), gliding smooth focal box highlights across topbar workspaces and bottom dock actions with directional tooltip cards and keyboard navigation. -
sidebar_actions.jsowns the sidebar bottom action hover-reveal short labels (100ms), singleton dynamic DOM tooltip bubbles (#anomalous-sidebar-tooltip-bubble, 600ms), click/pointerdown instant text/tooltip suppression guards,isBottomModalOpentooltip occlusion guards, and anti-flicker pointer stability. -
tool_registry.jscentralizes metadata, SVG icons (enlarged 20px crisp vector outlines with 2px stroke, #cbd5e1 contrast), and stable IDs for the 9 catalog tools (including Prompt Notes / 提示词笔记) and 2 fixed anchors (Toolbox and Settings). -
shortcut_layout.jsprovides tool layout utilities and fallbacks. The bottom shortcut bar maintains the clean fixed 4-tool setup (scan,doctor,assistant,materials) plus two anchors (toolbox,settings) housed in prominent 36px buttons with full click/active text suppression and.is-activetoggled styling. -
ui_toolbox.js's Toolbox modal strictly filters out all tools already present on the bottom bar, presenting a sleek 216px 3-row utility catalog with compact, frameless 44px tiles (providing an elevated silhouette with breathing room for catalog discovery), downward anchor caret pointing to the toolbox trigger button, 0.18s smooth spring pop-in animation, clean click action execution, and zero obstructive text or beta footers. -
ui_grid.jsand model-detail modules own model presentation:ui_grid.jsmanages chunked card rendering, card placeholder ergonomics (eliminating misleading unclickable text in favor of pure centered icon and status), card action buttons (one-click canvas addition with plus icon, model metadata editor, direct precision scanner without wizard modal popups) with absolute positioning cascades immune to tooltip target conflicts, vibrant hover affordance, safe docked sidebar preservation upon node addition, and multi-type node dispatch;ui_detail.jscoordinates detail display,ui_model_editor.jsowns metadata editing, andui_model_selector.jsowns advanced selection.ui_gallery.jsandui_gallery_detail.jsown generated-image browsing and workbench lifecycle, with stage interaction inui_image_stage.jsand metadata tabs inui_image_inspector.js. -
ui_recipes.js/ui_recipe_detail.js,ui_notebooks.js, andui_materials.jsown their respective workspace surfaces and persistence flows.ui_recipes.jsowns the Workflow Recipe studio catalog workspace with search/filter tags, grid/list layout toggle, dedicated top-right modal close anchor (permanently decoupled from the tool button row to prevent wrapping displacement), streamlined action header (preserving active workflow saving while pruning unfinished package import entrypoints), and card browsing.ui_notebooks.jsowns Prompt Note catalog, sidebar dual-group management (note list + floor quick jump anchor navigation with scrollspy active tracking and tooltip hints), and persistence;ui_notebook_editor.jsowns unfolded card editing (modularized into single-responsibility sub-functions adhering to the 50-line rule: sticky top action toolbar with floating More popover dropdown and timed two-step delete safety guard, unfolded companion models card with unconstrained multi-column tile flow eliminating nested gallery scrollbars, prompt composer with dynamic field-sizing and compact inline find & replace toolbar, flat material library archiving card with clean single-icon feedback, and unified dark slim scrollbar ergonomics with complete bilingual dictionary coverage inlocales.js), andnotebook_canvas.jsowns LiteGraph creation. Prompt Notes are integrated as a standard tool in the Toolbox with defensive workspace return state restoration, TDZ-safe summary initialization, and responsive empty-state fallback rendering.ui_recipe_detail.jscoordinates the Workflow Recipe detail session and model composition.ui_recipe_overview.jsowns the Overview prompt showcase (withentry.textfallback, guarded non-shrinking primary action CTA, and floating Popover More dropdown menu), andui_recipe_parameters.jsowns the responsive Parameter Presets workspace (featuring default-expanded raw node parameter inspection, aclamp(230px, 24vw, 290px)sidebar with guarded card actions, uncluttered console action bars with deferred status feedback,minmax(130px, 1fr)Bento Grid with universal click-to-copy, LoRA cards with flexbox truncation guards, and sticky editor headers).ui_materials.jsowns Material Library discovery and pagination,ui_material_cards.jsowns catalog cards and drag-and-drop (same-type blocks, or prompt-bearing materials onto nodes with prompt slots),ui_material_detail.jsowns the full detail surface, andui_material_application.jsowns selected-node tracking and explicit material application (same-type block application first, then cross-node prompt injection).node_material_actions.jsinspects prompt slots (inspectNodePromptSlots), extracts prompt envelopes without model file paths, and plans injection (planPromptInjection): both texts into two-slot nodes, otherwise the matching role or a role-neutral slot. Text never crosses roles; writes are one undo step. Within recipe detail,ui_recipe_versions.jsowns history comparison/restore,ui_recipe_gallery.jsowns result cards and direct Image Detail Workbench handoff,ui_recipe_model_matching.jsowns preview resolution and explicit local replacement,ui_recipe_metadata.jsowns inline persistence, andui_recipe_detail_dom.jsowns the DOM/copy helpers shared by detail subviews.recipe_identity.jsderives model references from native loaders plus a table of verified all-in-one loader layouts (ALL_IN_ONE_LOADER_SPECS, mirrored inapi/recipe_schema.py); other third-party widgets stay parameters. References are keyed by(node_id, widget_index, category, saved_value).recipe_parser.jssummarizes models, LoRAs, samplers and prompts (linked prompt nodes before embedded loader prompts) and keeps the summary in step with widget edits using the same node rules.ui_recipe_catalog.jsowns recipe filters, navigation, dismissible topbar drag guidance strip with localStorage persistence, the 3-step empty-state onboarding blueprint (renderRecipeEmptyGuide), and background catalog-wide model readiness resolution (resolveCatalogRecipeReadiness),ui_recipe_cards.jsowns cards and card actions (includinggrabdrag affordance, cover可拖拽badge, harmonized multi-state model readiness pill withgetRecipeReadinesssynchronizing available, missing, and pending matches with detail overview, and direct canvas drag-and-drop),ui_recipe_dialogs.jsowns save/edit dialogs, andui_recipe_media.jsowns shared cover helpers. Detail sessions synchronize detected model availability back toowner.recipeRecordsviasyncRecipeReferencesToCatalog. -
ui_prompt_composer.jsowns the standalone Prompt Studio drawer. Its child views areui_prompt_source_deck.js,ui_prompt_workbench.js, andui_prompt_inspector.js. Assembly plan data, track-vs-role separation, and cross-role tail smart-sorting are owned byprompt_composition.jsandprompt_studio_data.js.ui_prompt_source_deck.jsowns the card preview popover with narrow bridging corridors, differentiated hide timers, and fast dismissal when hovering or clicking library blank space. -
ui_prompt_translator.jsowns the standalone Prompt Translator, featuring robust multilingual/Chinese node prompt extraction (extractPromptFromNode), real-time canvas selection synchronization (app.canvas.onNodeSelected), automatic prompt injection on open/docked mode, on-demand read/sync controls, guarded selection writeback across single and multi-tab workflows, compact streamlined button ergonomics preventing multi-row wrapping, elastic vertical flex textareas maximizing canvas-side vertical space, and an expanded 460px default sidebar width with automatic backward-compatible width migration. Both translator and studio useui_lifecycle.jsfor global listeners, request cancellation and resize cleanup. Translation requests go throughtranslation_service.js. -
ui_dom.jsprovides small DOM/JSON helpers;material_inspector.jsowns material-specific metadata and parameter rendering. -
ui_doctor.jsowns diagnostics and global scans;ui_node_assistant.jsowns selected-node assistant history,ui_node_model_picker.jsowns native combo replacement, andui_node_presets.jsowns parameter preset rendering and application.model_picker.js,node_material_actions.js, andgraph_splice.jsown the remaining explicit graph changes. -
ui_model_sources.jsowns the Model Source Hub (模型来源统一中控中心), providing dual-scope (Workflow and Library) source inspection, external platform jumping, canvasNotenode generation,workflow.extra.anomalous_model_sourcesmetadata synchronization, automated asynchronous model metadata resolution (resolveWorkflowModelsMetadata) via/anomalous/resolve_paths_to_previewswith local sidecar priority detection, and protected read-only link display with deliberate edit-mode unlocking and dirty-state dynamic local persistence (hiding redundant[Save Local]buttons until links are modified). -
locales.jsis the shared runtime string catalog. Existing inline bilingual UI strings remain migration debt; new strings belong in the catalog. -
styles.cssis the ordered import manifest forweb/styles/*.css, which own presentation and theme overrides. Color values, dimensions and visual design descriptions are not duplicated as architectural contracts.
Cross-system invariants
These rules are intentionally summarized here and specified in the linked topic documents.
- Identity is provenance, not naming. Model Doctor may use a cryptographic hash, exact byte size under the allowed category policy, and target category. Paths, filenames, display names, previews, and fuzzy similarity are never identity evidence.
- Filesystem input is untrusted. Backend request paths must pass the shared
containment and filename helpers. Checking only for
..is insufficient on Windows and in the presence of alternate separators, UNC paths, or symlinks. - User files are changed transactionally and conservatively. Recipe writes, imports, graph mutations, and sidecar operations validate before mutation and either complete coherently or restore the prior state.
- The event loop stays responsive. Recursive walks, hashing, metadata parsing, and other potentially large disk operations run off the aiohttp event loop. UI rendering and media loading are bounded and cancellable.
- Host state is preserved. Browser panels are mutually exclusive, Workspace/model-detail transitions are recoverable, and graph edits use ComfyUI's change/callback contracts.
- Runtime strings are localized safely. User-visible copy comes from
locales.js; dynamic values stay outside dictionaries and enter the DOM as text. Only allowlisted rich content goes throughsafe_dom.js. - Optional integrations fail locally. Missing graph APIs, metadata, network availability, or an optional resolver may disable that capability but must not make the main extension disappear.
- Presentation data is not authority. Covers, thumbnails, summaries, cached names, and workflow fingerprints never replace the authoritative serialized workflow or model provenance record.
Data and compatibility boundaries
- Runtime settings and newly saved API keys live in
api/config.json.scraper.pymay read the legacy rootconfig.jsononly as a compatibility fallback. API keys are not stored in browserlocalStorage. - Recipes, Parameter Notebooks, and Material Library records/assets are user data outside the extension directory. They are never bundled with or silently migrated into the plugin source.
- The extension may integrate with Civitai and optional translation services only through explicit product behavior. External content and dependencies keep their own terms.
- Project code and documentation use the MIT license.
TRADEMARKS.mdseparately defines the project-name and official-branding boundary. - Internal property names and established routes may remain stable when a user-facing surface is renamed. Do not churn compatibility contracts merely to match presentation wording.
Change and snapshot protocol
Every product-code change should end as one coherent local Git snapshot:
- Run checks proportional to the changed behavior.
- Update architecture documentation only when the change modifies a module owner, data flow, public/internal interface contract, persistence format, security boundary, or critical invariant.
- When architecture changes, update the narrowest relevant topic document. Update this entry point only if the system map, cross-system invariants, or reading map changed.
- Do not add an architecture entry merely to say that existing boundaries were
unchanged. Ordinary fixes belong in code, tests, Git history, and—when useful
to users—
CHANGELOG.md. - Record a durable lesson in
.agents/logs/ai_lessons.mdonly for a recurring trap or a critical failure mode, not as a turn-by-turn work log. - Create a local commit after verification. Keep unrelated work out of the snapshot and do not push without explicit user authorization.
- Leave a clean worktree, or identify every intentional uncommitted file in the handoff.
Decision records explain enduring choices; they are not a chronological diary. Git history is the authoritative record of implementation changes. Planning-only documents must be clearly labeled as proposals and must not describe unshipped behavior as current architecture.