The global context menu's single "Download example images" entry always
posted force=true, i.e. the re-process-all path that ignores the previous
run's state and re-checks every model. The per-model and bulk menus
already expose both choices, so the global entry was the odd one out.
Turn it into a submenu with "Download Missing" (force=false, keeps the
progress-aware skip) and "Re-process All" (force=true), both still
library-wide. The labels reuse the per-model submenu keys, which are
already translated in every locale, so no new i18n keys or placeholders
are introduced.
The sidebar menus (folder tree, view options) and the page menus (card,
bulk selection, page-global) are independent components that only knew
how to close themselves, so right-clicking a folder left an open card
menu next to the folder menu, and right-clicking a card left the folder
menu behind.
Add a coordinator where every menu registers a closer; opening a menu
first closes all the others. BaseContextMenu takes part automatically,
the sidebar registers its two menus, and the global contextmenu handler
now uses the coordinator instead of hiding three specific menus by hand
(which also covers right-clicks outside the content area).
Menus are `position: fixed`, so only the window can clip them, but they
were placed at the raw cursor position with a naive flip: near the bottom
edge the menu opened below the window (nothing to scroll it into view),
and a tall menu that flipped up could land above the top edge. The
sidebar folder menu and the view-options menu did no viewport math at all,
so right-clicking a folder at the bottom of the tree showed no options.
Add a shared positionFixedMenu helper that picks the side with room,
clamps on both axes, and caps the height (scrolling internally) when the
menu is taller than the window. BaseContextMenu uses it and exposes a
prepareMenu hook so per-card entries are toggled before the menu is
measured; the Recipe/Lora/Checkpoint/Embedding/Other/Workflow menus used
to mutate the menu after positioning and so measured the previous
content. Submenus get the same treatment via positionSubmenu.
Fixes#1167
Shared chrome borrowed the model-library nouns on the Recipes and Workflows
pages:
- The header filter panel is titled per page (`recipes.filter.title`,
`workflows.filter.title`) instead of "Filter Models".
- Bulk guards and move failures use page-specific copy
(`recipes.bulk.noSelection`, `recipes.move.failed`,
`workflows.bulk.noSelection`) instead of "No models selected" / "Failed to
move model(s)", covering the bulk menu, the sidebar drag-and-drop path and
the recipe move client.
- Workflows got a bulk-action config, so model-only bulk entries (send to
workflow, auto-organize, re-import/rematch, copy syntax) stay hidden
instead of appearing unfiltered.
- The workflows page's global context menu no longer offers the model-only
example-image entries.
- MoveManager no longer assumes every API client exposes `modelType`: the
recipe client has none, which made the "use default path" preview throw
and stored the toggle under `use_default_path_undefined`.
Recipes card copy is translatable now too: the card tooltips (favorites,
share, send, delete, blur, NSFW levels) and the delete dialog go through
i18n instead of hard-coded English.
10 new keys (the move-only noSelection key folded into
`workflows.bulk.noSelection`), translated in all 9 locales; no
[TODO: Translate] placeholder remains.
The workflow card menu reused the model-library copy ("Exclude Model",
"Delete Model", "Copy Model Filename"), and the shared delete dialog
hard-coded "Model:" in its info box.
- Workflow-specific labels for copy-filename / move / delete, and for the
delete confirm dialog (title, message, info-box label, failure alert)
- Move dialog title, root label and hint now go through i18n instead of
hard-coded English; bulk delete dialog switches to workflow copy
- Failed move / nothing-selected toasts use workflow wording
- "Copy Workflow Filename" now copies the file name; it used to click the
card's copy icon, which copies the workflow document (duplicate of
"Copy Workflow")
- Fix "Link to CivitAI" posting to /api/lm/loras/relink-civitai: the
mixin's prefix map was keyed by singular type and missed the plural
page type used by WorkflowContextMenu
Also drops the "Exclude Workflow" action: workflows, like recipes, have
no exclusion concept. The file has no consumer outside LM, and the
excluded view is only reachable from the global context menu, so
excluding a workflow was a one-way hide. The entry is removed from the
card menu, the global menu and the excluded-view banner; the backend
route stays available.
New card context menu entry 'Restore Downloaded Version' discards local
edits made via the write-back flow:
- Container artifacts (.zip/images) were never modified — restore deletes
the derived .workflow.json sidecar and re-extracts from the pristine
archive, then re-syncs analysis metadata. Purely local, works for
self-hosted workflows too.
- Bare .json artifacts are rewritten in place by write-backs, so restore
re-downloads the recorded CivitAI version file (API-key auth, temp file
+ workflow validation + SHA256 check before the atomic replace).
The menu entry is disabled for local-only .json workflows that have no
restore source. On success the card is refreshed in place with the
shared 'Updated' cue.
Add an Update from ComfyUI entry to the workflow card context menu,
enabled only when the workflow is the active graph in a connected
ComfyUI tab and carries unsaved edits.
ComfyUI side (workflow_writeback.js): the first graphChanged after a
load/write-back flips a dirty flag inside the extra.lm_source marker
(ChangeTracker only dispatches on real state changes); the active
graph's link state (file_path, sha256, dirty) is reported to
POST /api/lm/workflows/comfyui-state on load, tab switch, dirty flip
and write-back. A new lm_collect_workflow WS handler answers LM-page
write-back requests: it reuses the regular PUT content flow and posts
the outcome to /api/lm/workflows/collect-result.
Backend: an in-memory WorkflowLinkRegistry holds per-client link states
(pruned against live websocket sids) and pending collect futures;
POST /api/lm/workflows/update-from-comfy targets the tab holding the
requested file (dirty holders win) and awaits the collect result,
mapping no_changes/cancelled to 200, timeout to 504.
The card send/copy icons and the model modal's Send to ComfyUI button
only had lora/checkpoint/embedding branches; workflows fell through to
a silent no-op (modal) or lora-ish behaviour with wrong tooltips (card).
- shared helpers sendWorkflowToComfy / copyWorkflowToClipboard in
utils/workflowActions.js, reused by the context menu, card icons and
the model modal
- card send icon opens the workflow in the running ComfyUI graph; the
copy icon copies the workflow document JSON for paste-to-load
- correct tooltips (Open in ComfyUI / Copy workflow to clipboard)
- send entry points hidden in standalone mode (no graph to push into):
card icon and modal button, matching the context menu
- GET /api/lm/workflows/content returns the extracted workflow document
(json direct, zip/png/webp via the derived sidecar); no ComfyUI needed,
so it also serves standalone mode where send-to-comfy is unavailable
- workflow cards gain a Copy Workflow context-menu action that puts the
document JSON on the clipboard for paste-into-ComfyUI loading
- the Open in ComfyUI item is hidden in standalone mode, where pushing
to a graph is impossible by definition
Add a Workflows page that manages ComfyUI workflow JSON files as a
first-class library alongside loras/checkpoints/embeddings:
- opt-in via the new 'Workflow Management' settings toggle (default off)
- dedicated workflow folder roots with sidecar/preview support, separate
from ComfyUI's own workflow storage; 'Send to ComfyUI' loads a workflow
straight into the graph
- workflow scanner parses the graph document (UI/API format sniffing,
node classes, model/civitai refs) and persists the analysis in the
SQLite model cache
- settings UI: workflow folder paths editor in the Library tab and the
standalone Model Paths page
Fixes from sandbox E2E verification:
- include the workflow scanner in startup initialization so the page no
longer hangs on 'initializing' when enabled without folders
- add the missing workflows entry to the frontend page-state map (page
init crashed and the sidebar fell back to the loras tree without it)
- project workflow analysis fields through the scanner cache entry and
persist them in the model cache (new columns with auto-migration)
- keep symlinked paths intact in the directory picker (abspath instead
of resolve), so stored library paths match what the user sees on disk
Also rebuilds the vue-widgets bundle to sync it with web/comfyui/utils.js.
A model file could only ever be linked to huggingface.co: `set_hf_url`
validated the URL with a huggingface-only regex, the agent fetched the card
from a hardcoded HF URL, and the readme processor built every relative image
path off `https://huggingface.co/{repo}/resolve/main`. ModelScope publishes the
same model-card convention (README.md + YAML frontmatter, often carrying
`base_model:` and `trigger_words:`) behind a public, key-less API, so the
enrichment pipeline could already serve it - it was the plumbing that was
HF-shaped, not the idea.
Make the external source a first-class, provider-driven concept:
- New `py/services/model_sources/` registry. A `ModelSource` owns URL
recognition (lenient for stored values, strict for user input), the
canonical page URL, model-card fetching, the asset base URL and the
capability flags. `HuggingFaceSource` is the previous logic relocated;
`ModelScopeSource` reads `/models/{o}/{n}/resolve/{master|main}/README.md`
and falls back to `/api/v1/models/{o}/{n}/repo`. `TensorArtSource` is
link-only on purpose: tensor.art answers plain HTTP clients with a
Cloudflare challenge and its internal API (ap-east-1.tensorart.cloud /
cn.tensorart.net) rejects every /v1/model/* route with "invalid
authorization header", so it declares supports_enrichment=False rather than
failing silently later.
- Metadata gains `source_platform` + `source_url`; `hf_url` stays as a
read/write alias, written only for Hugging Face, so existing sidecars,
cached rows and third-party consumers keep working. Normalisation runs at
the scanner, the persistent cache (both directions, plus two new columns
behind an ALTER migration) and the linking handler - which is what stops a
user who switches sources from leaving a stale `hf_url` on a ModelScope
model.
- The agent pipeline keys off the provider instead of `hf_url`: the fast-fail
gate now explains *why* a model is skipped (no source / unknown source /
source without a reachable card), the prompt context exposes
source_url/source_id/source_label/asset_base_url while still filling the
legacy hf_url/repo aliases, and the four README image extractors take a
base_url (defaulting to HF) so relative paths resolve against the right
site. Version grouping generalises to hf: / ms: / ta: keys.
- `POST /api/lm/set-hf-url` keeps its path and its legacy payload keys but
accepts `source_url`, validates against every provider and returns the
platform. `GET /api/lm/model-sources` lets the UI render the supported-site
list from the server.
- Frontend: a `modelSourceHelpers` mirror of the registry drives the link
dialog, the card/modal globe (branded "View on ModelScope/TensorArt"), the
version-group key and the enrichment gate; the versions tab no longer sends
ms:/ta: keys to the CivitAI API.
TensorArt stays in the list because provenance is worth keeping even when the
card is unreadable - the dialog says so plainly ("Sites that don't expose one
(currently TensorArt) can only be linked") and the context menu disables
enrichment with a matching tooltip, instead of the user getting
"Unsupported URL".
Verified against the real ModelScope API: jj3550945163/Krea-2-LORA returns a
1882-byte card whose frontmatter carries base_model/tags/trigger_words, and
relative images resolve to .../resolve/master/....
Tests: backend 2815 passed; frontend 1130 JS + 91 Vue passed; pytest
tests/i18n and a Jinja compile pass over templates/. The nine locales carry
[TODO: Translate] for the new strings, completed in the next commit.
A model could have CivitAI metadata and a HuggingFace link at the same time,
but only one of the two "View on ..." entries ever rendered, because both the
model modal and the card globe asked the `from_civitai` provenance flag which
source to show. `set_hf_url` wrote `false` and a CivitAI refresh wrote `true`,
so whichever ran last erased the other: linking HF hid "View on CivitAI" even
though the civitai payload was still in the sidecar, and (on the card) a later
refresh pointed the single globe icon back at CivitAI, hiding the HF entry.
Decide the links from the data itself instead:
- `set_hf_url` no longer touches `from_civitai`; it records where the metadata
came from, and HF provenance is already tracked by `hf_url`.
- Add `hasCivitaiSource(civitai)` in the shared card/modal utils and gate the
modal's CivitAI link, the card globe (title, enabled state, click target,
new `data-has_civitai`) and the context-menu `civitai` action on actual
CivitAI data (`modelId` / `model_id` / `id`). A dual-source model now shows
both links, and a CivitAI-only model with no `hf_url` stays as before.
- Agent HF enrichment (`PostProcessor.is_hf_model`) keyed off
`not from_civitai`, which stopped being a synonym for "has an HF source" once
both sources can coexist (and already broke after a CivitAI refresh flipped
the flag back to true). Key it off `hf_url` directly; the post-processor
tests move to that discriminator and gain a dual-source case.
Regression tests: the set-hf-url handler preserves civitai + `from_civitai`
and no longer forces the flag false, the modal renders both links (including
with `from_civitai: false`), and the card globe targets/opens the right source
and is disabled when neither is available.
Backend: 2749 passed. Frontend: 1098 JS + 91 Vue tests passed.
The option only existed in the LoRA page menu. Move updateEnrichMenuItem
and enrichWithAgent into ModelContextMenuMixin so both pages share the
implementation, and add the menu item to the checkpoints template.
Replace the post-run toast cascade and the standalone L4 results modal
with a summary modal modeled on the batch download summary: 3-state
header, stat cards (matched / needs review / unresolved / errors),
an L4 review table with per-entry undo, and a copyable report. Wired
into the global, bulk and single-recipe rematch entries; complete
no-op runs keep the lightweight toast. Obsolete results-modal code,
styles and i18n keys are removed.
- Snapshot pre-rematch entry state (reconnectSnapshot) so rematched
entries can be undone via the existing restore flow
- Bulk missing-LoRA downloads mark unresolvable failures hash-invalid,
flipping those entries from download to reconnect candidacy
- Recipe modal always offers a reconnect action next to download for
missing LoRA entries
- Rematch runs collect an opt-in relaxed-matching choice (also reconnect
missing models by file name) via a pre-run options dialog on the
global, bulk and single-recipe entries
- L4 (filename-level) matches are listed in a results dialog with
per-entry undo
Recipes imported from CivitAI image URLs can contain 0 LoRAs: the backend
only sees the REST image API + EXIF, while the complete generation data
lives in the image page's internal trpc payload (see
docs/recipe-civitai-image-no-metadata.md). When the companion
lm-civitai-extension is installed with a valid license, re-import (single
and bulk) of CivitAI-image-sourced recipes is now delegated to the
extension via DOM CustomEvents; the extension scrapes the image page with
the user's session and calls back into the reimport endpoint with the
full metadata payload. Without the extension (or with an invalid license)
the native path runs unchanged.
- POST /api/lm/recipe/{id}/reimport accepts optional payload params
(image_url/name/resources/gen_params/base_model/tags); the payload path
reuses the import-remote engine with reimport semantics (user-edit
carryover, delete-after-save), and malformed/failed payloads fall back
to the legacy URL import. Response gains loras_count.
- The endpoint also accepts GET: the extension is GET-only by convention
(documented in AGENTS.md).
- New static/js/utils/extensionReimportBridge.js (probeExtension /
delegateReimport / getCivitaiImageInfo) wired into RecipeContextMenu
and BulkManager with silent native fallback.
- i18n: toast.recipes.reimportingViaExtension added and translated in
all 9 locales.
The recipe "Repair Metadata" action has been marked Deprecated in the UI
for a while and cannot reliably recover recipes imported from CivitAI URLs
whose REST meta has no resources/hashes and whose image has no embedded
metadata (e.g. CivitAI-only generation data). Drop the feature end to end.
Backend:
- remove repair routes (repair, cancel-repair, recipe/{id}/repair,
repair-bulk, repair-progress) and their handler mappings/methods
- remove RecipeScanner repair_all_recipes / repair_recipe_by_id /
_repair_single_recipe and REPAIR_VERSION
- remove WebSocketManager recipe-repair progress channel
- drop repair_version column from the persistent recipe cache
- rematch mutual-exclusion now only checks rematch
Frontend:
- remove repair entries from per-recipe, bulk and global context menus
- remove repairRecipe / repairSelectedRecipes / repairRecipes + cancelRepair
and the repairBulk API client method/endpoint
- drop recipe-repair i18n keys (synced across locales; doctor keys kept)
Tests/docs: delete test_recipe_repair.py, update scaffolding/routes/ws/
persistent-cache/integration tests and i18n guideline examples.
The recipe card pill counted LoRAs deleted from the source (isDeleted) as
available, showing a green 'ready 2/2' for recipes that cannot be fully
reproduced. LoRAs with an unresolvable hash (hashInvalid) were counted as
missing/downloadable even though downloads always fail, and recipe syntax
generation emitted broken tokens for them.
- Four-state status on RecipeCard pill and RecipeTab badge: ready (all in
library), missing (downloadable, red, keeps the action cue), partial
(unobtainable entries skipped when used, amber, fa-circle-minus),
unavailable (nothing usable, gray, fa-ban)
- Pill numerator is now the real in-library count; tooltips spell out
missing vs unavailable (deleted from source or unresolvable hash)
- get_recipe_syntax_tokens skips hashInvalid entries like deleted ones
instead of emitting tokens pointing at nonexistent files
- Bulk missing-download manager and recipe context menu exclude
hashInvalid LoRAs, matching the modal's per-item download block
- New locale keys loraStatus.missingAndUnavailable/partial/noneUsable,
translated for all 9 non-en locales
- Recipe card: compact status pill with state icon + available/total
fraction (e.g. "2/3"), pinned to the footer bottom-right like model
card actions; status is encoded by icon + color, never color alone
- Recipe modal: "N missing" is now a real <button> with a persistent
border, leading download icon, focus-visible ring and aria-label;
clicking opens the download-missing flow
- Fix context menu missing-LoRA detection selector after badge refactor
- i18n: add recipes.status/loraStatus keys with translations for all
10 locales, and fill pending rate-limit translations
Downloads failed with "No suitable file found in metadata" for models whose
only file uses newer CivitAI file types (e.g. 'Enhancement LoRA' for
Anima/AIR image-editing LoRAs) because the primary-file allowlist only
covered legacy types.
- unify the weights-type allowlist as MODEL_WEIGHT_FILE_TYPES
(py/utils/constants.py) and apply it across download, recipe and
metadata-refresh lookups
- mirror CivitAI's getPrimaryFile() semantics: prefer weights-type primary,
fall back to weights files, then trust CivitAI's primary flag (excluding
non-downloadable artifacts like Config/Archive/Workflow)
- mirror the allowlist in the frontend via shared isModelWeightFile() helper
- add regression tests for the Enhancement LoRA primary-file download,
primary-flag fallback and weights-over-non-weights-primary preference
Move the 'Download Missing' / 'Re-process All' example image actions
under a single 'Download Example Images' submenu item in the single-model
and bulk context menus, matching the existing send-to-workflow submenu
pattern. Shorten the submenu labels and update all locale translations.
Split the single-model and bulk context menu actions into 'Download
Missing Example Images' (regular endpoint, skips already-processed
models) and 'Re-process Example Images' (force endpoint, retries
failed models).
- start_download accepts model_hashes so a selected subset can be
processed with the progress-aware skip logic; explicitly targeted
models bypass the failed/processed model-level guards so per-image
gaps are filled
- pre-download existence check in the processor skips network requests
for image files already on disk across all download paths
- force download retries previously failed models and clears their
failed status on success
- add i18n keys for the new menu items across all locales
When using full path lora syntax, the context menu (single/bulk)
and bulk copy actions were passing only the file basename to
buildLoraSyntax(), ignoring the folder prefix. This caused the
output to look like legacy A1111 format even when full path mode
was enabled.
Aligns all entry points with ModelCard.handleSendToWorkflow(),
which correctly includes the folder prefix.
Also fixes selectAllVisibleModels() to cache the folder field,
preventing missing prefix on select-all-then-send flows.
- Merge Relink to Civitai and new Link to HuggingFace into a single
'Link Model' submenu with sub-options for each source
- Add POST /api/lm/set-hf-url endpoint to associate a model with a
HuggingFace repo URL, saving hf_url to .metadata.json
- Add link_hf_modal.html for URL input, following relink-civitai pattern
- Use update_single_model_cache instead of add_model_to_cache to
prevent duplicate cache entries after linking
- Remove os.path.realpath usage for consistency with relink-civitai
- Raise errors instead of silently falling back to LoRA scanner when
model root cannot be determined
- Scope .input-group CSS rules to modal IDs to fix style conflicts
with download-modal.css
- Add i18n keys across all 10 locales with translations for
zh-CN, zh-TW, ja, ko, de, es, fr, he, ru
- PostProcessor returns updates dict from enrich_hf_metadata
- AgentService includes updated_data per model in WebSocket progress events
- Convert preview_url to HTTP URL via config.get_preview_static_url()
- LoraContextMenu: showEnhancedProgress + updateSingleItem per model
- BulkContextMenu: same pattern, remove window.location.reload()
- Guard empty updated_data and clean up callbacks on HTTP error
Introduce an agent skill framework for LLM-driven metadata enrichment:
- AgentCLI (py/agent_cli/): in-process wrappers around internal services
using standard relative imports, eliminating the need for sys.path hacks
- LLMService: centralized BYOK (bring-your-own-key) LLM client supporting
OpenAI, Ollama, and custom OpenAI-compatible endpoints
- PostProcessor: deterministic engine that applies LLM output via AgentCLI
(replaces old handler.py + _BASE_MODEL_ALIASES approach)
- SkillRegistry: filesystem-based skill discovery (skill.yaml + prompt.md)
- AgentService: orchestrates skill execution with WebSocket progress
- Frontend AgentManager: WebSocket listeners, skill execution, config UI
- Context menu entries (single + bulk) for "Enrich Metadata (Agent)"
- Settings UI for AI Provider configuration (BYOK)
- Full i18n support across 9 locales
Bug fixes found during review:
- aiohttp.web.json_response: status_code= -> status=
- settings_modal cancelEditApiKey: wrong argument position
- AgentManager.isLlmConfigured: allow Ollama without API key
- PostProcessor._merge_tags: lowercase all tags to match TagUpdateService
- group_by_model dedup now counts versions per group and attaches
version_count; respects update_flag_strategy (same_base) by
sub-grouping on base_model
- Card footer shows clickable 'x versions' link instead of version
name when grouped (hides HIGH/LOW badges); clicking triggers
View Local Versions without page reload
- Added 'Local Versions' sort option (versions_count), auto-hidden
when group_by_model is off
- Sort preference is saved/restored separately for normal and
grouped modes
- VLM flow (triggerVlmView, clearCustomFilter) uses resetAndReload()
via API instead of window.location.reload()
- Fixed cache mutation bug: version_count is now set on a shallow
copy, not the cached dict, preventing stale version_count leaking
into VLM responses
- i18n: all 9 locale files translated
Adds a 'Group by Model' toggle entry to the right-click global context
menu for quick access, complementing the existing setting in
Settings → Layout Settings. The menu item shows a checkmark indicator
reflecting the current state and immediately reloads the view on toggle.
Also fixes he.json translation that was mojibake (garbled characters).
Includes:
- Context menu HTML item with check-indicator
- JS toggle logic via settingsManager
- i18n for all 10 locales
- Hebrew translation fix
- Fix copy button on embedding cards to copy 'embedding:folder/name' format
- Add send-embedding-to-workflow for Prompt (LoraManager), Text (LoraManager),
and CLIPTextEncode nodes, appending embedding code to text content
- Extend workflow registry to register text-capable nodes by comfyClass
(not generic widget name 'text') to avoid false matches
- Add mode parameter to update_node_widget API/event for append support
- Fix single/bulk context menus: single shows plain 'Send to Workflow',
bulk collapses submenu into direct action for embeddings (append-only)
- Add local file reimport support via _do_reimport_from_local
- Validate source_path BEFORE deleting old recipe (prevent data loss)
- Move delete_recipe after save_recipe (safe ordering)
- Preserve folder location, NSFW level, and carry over user edits
- Remove old timestamp preservation (use current time)
- Add scrollTop reset in resetAndReloadWithVirtualScroll
- Only reload on successful bulk reimport (avoid empty grid)
- Disable preserveScroll for both single and bulk reimport
- Single recipe right-click menu: Re-import from Source
- Bulk context menu: Re-import Metadata for Selected
- Progress overlay with LoadingManager for single and bulk operations
- Virtual scroller data lookup (replaces fragile DOM querySelector)
- Fix dynamic import path for resetAndReload on recipe pages
- Add translation keys for all 9 supported languages
Bug: when scrolling down on recipes page, any operation with
preserveScroll: true would fetch only page 1 data then restore
scroll position to beyond the loaded items, leaving the grid empty.
Fix:
- Remove preserveScroll: true from all 7 must-refresh trigger
paths (filter, search, sort, import, settings reload, sync,
rebuild cache, sidebar folder nav)
- Replace full list refresh with updateSingleItem() for repair
and bulk missing-LoRA download operations
- Update tests to match new scroll-free behavior
Adds a new bulk operation in the recipes page that allows users to select
multiple recipes and repair their metadata in batch.
Backend:
- New POST /api/lm/recipes/repair-bulk endpoint accepting recipe_ids array
- repair_recipes_bulk handler iterates repair_recipe_by_id for each recipe
- Response includes per-recipe updated data for frontend card refresh
Frontend:
- Bulk context menu: new 'Repair Metadata for Selected' item in Metadata section
- BulkManager.repairSelectedRecipes() with loading/toast flow
- Uses VirtualScroller.updateSingleItem() per repaired recipe (no full reload)
- Visibility controlled via repairMetadata actionConfig flag
Locales:
- Added repairMetadata, repairBulkComplete, repairBulkSkipped, repairBulkFailed
- Translated across all 9 supported languages
Group 15 flat menu items into 5 logical sections (Workflow, Metadata,
Attributes, Organize, Download) with section headers to reduce cognitive
load. Nest the three workflow-related actions (Append, Replace, Copy
Syntax) into a single "Send to Workflow" hover-triggered submenu.
Add submenu infrastructure to BaseContextMenu with mouseover/mouseout
boundary detection, 250ms close delay, and viewport-aware positioning.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allows downloading example images only for selected models instead of
the entire library. Reuses the existing /api/lm/force-download-example-images
endpoint which already accepts an array of model hashes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces two separate menu items with a single smart item that dynamically
switches between 'Set as Favorite' and 'Remove from Favorites' based on
whether all selected items are already favorited. Shows a count badge
'(3/5)' when only some items are favorited in a mixed selection.
Supports all model types (LoRA, Checkpoint, Embedding) and recipes via
existing per-item save/update API — no backend changes needed.