1309 Commits
Author SHA1 Message Date
Will Miao 104d2675ae feat(ui): split global example image download into missing and re-process
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.
2026-11-12 08:42:27 +08:00
ghosttigerllc-bit 458f99ac23 feat(settings): optional fallback folder name for unclassified tags (#1200) 2026-11-11 18:31:52 +08:00
Will Miao 79470bd870 fix(settings): polish ComfyUI node settings navigation 2026-11-11 09:36:41 +08:00
Will Miao d81218f3f0 feat(settings): link directly to ComfyUI node settings 2026-11-11 08:25:09 +08:00
Will Miao 80ab8b65c7 fix(models): recover from stale auto-organize state
- reject metadata writes when the backing model file is missing
- use the REST result when the auto-organize progress socket fails
- add regression coverage and a progress fallback notification

Refs #1126
2026-11-10 23:12:59 +08:00
Will Miao fb4344c009 fix(settings): save exclusion textareas on blur and close (#1187)
The auto-organize exclusions, metadata refresh skip paths and both
send-to-workflow exclusion textareas only committed on Enter, so clicking
away or closing the modal silently discarded the edit while the rest of
the settings modal autosaves.

Commit on blur and keep Enter as an explicit commit; Enter now just blurs
the field so every path shares one commit handler, like the filename
template inputs. Also flush on Escape, because Chrome does not fire blur
when the focused field is hidden by the closing modal. Skip the post-save
value write-back while the field is focused so a quick re-entry is not
overwritten by the in-flight request.
2026-11-10 12:15:45 +08:00
Will Miao 802b82a806 feat(settings)!: remove multi-library support 2026-11-09 20:53:09 +08:00
Will Miao 1c652e76b7 feat(library): refine card preview cycling interaction and defaults
- Arm hover cycling only from the bare preview area; hovering the card
  header or footer disarms the timer, so action buttons and tooltips
  stay undisturbed
- Drop the activation delay from 1.5s to 0.8s now that chrome
  interactions are protected structurally
- Revise cycle interval options to Fast (1.5s) / Normal (2s, default) /
  Relaxed (3s), dropping the rarely useful 5s/8s choices
- Translate the updated cardPreview strings in all 9 locales
2026-11-09 20:09:43 +08:00
Will Miao f9cb3988aa fix(example-images): repair partial folders and surface failure reasons
- Add an atomic .lm-examples-complete marker; only a folder with media and
  the marker is skipped, so interrupted or legacy folders are re-checked by
  the per-image existence path. Clear the marker before each pass.
- Treat processed/failed folders without a marker as pending so the
  auto-download pre-check repairs them, and clear markers when provider
  metadata is refreshed or custom images are imported.
- Bound stale-metadata refreshes at 90s so one hung provider cannot park the
  serial run; count metadata-missing models as retryable.
- Record failure reasons in the progress file, expose failure_summary, and
  render a no-media/unavailable/retry breakdown in the progress panel with a
  warning completion toast.
- Add marker, partial-repair, timeout, failure-summary and UI tests.
2026-11-09 17:26:22 +08:00
Will Miao ededac14d3 feat(settings): add About section with environment details (#1194)
Move the version panel out of Help -> Documentation into a dedicated
Settings -> About section. It shows the LoRA Manager, ComfyUI, ComfyUI
Frontend, Python, OS and browser versions, copies the block as Markdown
for issue reports, and links to the issue tracker, wiki, GitHub and
Discord.

Read the values from a new GET /api/lm/environment endpoint instead of
ComfyUI's /system_stats: Python and OS stay available in standalone mode,
while ComfyUI-only versions are omitted there instead of failing the
whole panel. Model Paths is inserted before About so About remains the
last settings section in standalone mode as well.
2026-11-09 12:24:31 +08:00
Will Miao 87f2ed634e fix(help): avoid generic help-text class colliding with injected CSS (#1193)
The feature list in the help modal used the generic .help-text class. An
externally injected global .help-text rule (e.g. from a browser extension)
sets position: fixed and z-index: 10000, which pulled the list out of flow
and covered the modal tab buttons: the app's own rule never declared
position, so the injected declaration applied.

Rename the class to .help-features and scope its rules under .help-modal
so third-party global styles can no longer match it. Also close the
unterminated <a>/<li> pair in the documentation tab.
2026-11-09 11:01:49 +08:00
Will Miao 4db23e832f fix(example-images): stabilize ETA and persist download progress
- Blend a phase baseline with a 120s throughput window; reset the phase
  on pause and skip bursts so a resumed run is not estimated from the
  folders it skipped.
- Expose pause-aware elapsed_seconds and add a compact status response
  for the 2s progress poll.
- Save .download_progress.json from both download loops; the old save
  sat behind an early return on the common image path.
- Treat blank aiohttp errors and raised exceptions as transient, and
  bound gallery media at 25s (videos 180s) instead of the shared 300s
  socket read.
- Add tracker, manager, progress and timeout tests plus the full-run
  measurements.
2026-11-08 20:04:32 +08:00
Will Miao f4ea2551a8 feat(library): cycle example images on card hover
Cards with a static image preview now crossfade through the model's
example images when the pointer rests on the card, giving multi-concept
LoRAs a richer at-a-glance signature without giving up the single fixed
preview that anchors visual memory of the library.

- Cycling activates only after a 1.5s dwell so ordinary hover actions
  (action icons, tooltips) keep working; the card chrome fades out for
  an unobstructed view while cycling and returns on mouse leave
- Example metadata and the first frame start preloading at hover time,
  and each shown frame warms the next, so mode entry and interval ticks
  never wait on the network
- The cover image (usually the preview itself) opens the rotation as
  the anchor's stand-in; cycling starts from the second example and the
  cover rejoins when the rotation wraps
- Video previews keep their autoplay-on-hover behavior; video entries
  inside example lists are skipped, as are NSFW entries under SFW mode
  and blurred cards until unblurred
- Honors prefers-reduced-motion, pauses in hidden tabs, and cleans up
  when the virtual scroller recycles a card
- New settings: card_preview_cycle (default on) and
  card_preview_cycle_interval (2/3/5/8s, default 3s)

Implements #1182
2026-11-08 00:19:25 +08:00
Will Miao d06bfea02a fix(versions): stop the folder filter from hiding local versions
View all local versions answers "every local version of this model", but the
listing request still carried the active folder: versions moved to another
folder silently disappeared, and a group-by-model card's version count (counted
across the library) no longer matched the list it opened.

- suspend the folder filter when the view starts, remember it, and show the root
  selection so the sidebar and the grid agree
- restore the folder when the filter is cleared; keep it suspended across a
  page reload
- picking a folder while the filter is active now exits it instead of being
  silently ignored (the view is library-wide)
- hand a suspended folder back to its own page when another model page clears
  the stale filter
- backend contract unchanged: the client simply omits `folder` while the view
  is active

Tests cover the query params, folder suspend/restore/foreign handoff and the
sidebar folder handoff.
2026-11-07 12:46:58 +08:00
Will Miao a45d095277 Merge remote-tracking branch 'origin/feat/workflow-auto-organize' 2026-11-06 12:54:34 +08:00
Will Miao 05cdea8c59 Merge remote-tracking branch 'origin/feat/trigger-word-search' 2026-11-06 12:54:24 +08:00
Will Miao fa6712cff9 feat(settings): create folders from the directory picker
The settings folder picker could only select existing directories, so
creating a model root meant leaving the UI to create the folder first.

- Add POST /api/lm/create-directory, backed by create_directory() in
  py/utils/directory_browser.py: it creates one subfolder of the browsed
  directory and rejects names that are not a single safe path segment
  (separators, dot names, Windows-invalid characters and reserved names,
  over-long names) plus duplicates, missing/non-directory parents and
  permission errors, each with a stable error_code.
- Add a New Folder button and an inline name row to the picker; the
  created folder is entered immediately so it can be selected with one
  click. Esc dismisses the row before closing the modal, and the button
  is disabled on the Windows drive-list level where there is no
  directory to create in.
- Map the server error codes to localized picker messages.
- Cover the endpoint and the picker flow with backend and frontend tests.
2026-11-06 12:46:53 +08:00
Will Miao 0b46852f91 feat(search): search LoRAs by trigger word
Users see a trigger word in someone else's prompt and have no way to tell
whether the LoRA is already in their library, so they fall back to searching
the model sites again.

- Read trigger words from civitai.trainedWords through a small helper that
  tolerates the shapes older sidecars contain (None, a bare string, mixed
  lists). Provenance does not matter: CivitAI/CivArchive sync, HuggingFace
  or ModelScope model cards, the AI enrichment skill and manual edits all
  write the same field.
- Add an opt-in trigger_words search option to SearchStrategy, evaluated
  last before the always-on hash fallback. Matching is case-insensitive
  substring only: fuzzy would run the sequence matcher over every declared
  word of every entry and cost seconds on large libraries.
- Expose it as a "Trigger Words" chip on the LoRAs page only, plumb the
  option through page state and the list request, and parse the new
  search_trigger_words query param.
- Cover the strategy (including AI-enriched HuggingFace entries and
  malformed trainedWords), the param parsing, the chip-to-state wiring and
  the request query string. The English key is synced with a
  [TODO: Translate] placeholder in the other locales.
2026-11-05 20:24:18 +08:00
Will Miao 37cb01a1ac feat(workflows): auto-organize the workflow library (#1177)
Expose Auto-Organize on the Workflows page and give workflows their own
download path template, which both downloads and Auto-Organize use.

- Add a `workflow` default ({base_model}) to the backend and frontend path
  template constants. Local workflows rarely carry CivitAI tags, so the
  default stops at the base model instead of adding a "no tags" segment.
- Enable the bulk Auto-Organize action for workflows; the shared endpoint
  and scanner move already handled them, only the UI gate was missing.
- Add the workflow row to Settings > Organization, rendered hidden and
  toggled by the existing Workflow management master switch so the config
  only appears while the feature is enabled.
- Preview workflow templates with a .json artifact name instead of a
  weight-file name.
- Cover the default template, download routing, both auto-organize modes,
  sidecar moves, and the settings gate with tests.
2026-11-05 19:53:09 +08:00
Will Miao 3726a886af fix(metadata): surface the hash computed by a failed fetch
A failed CivitAI lookup used to drop the entry that the fetch had just
refreshed: the on-demand hash was already computed and stored, but the
client only applied the response entry on success, so the card kept the
stale empty sha256 until a page reload.

- Return the refreshed entry in the error payload when the lookup fails.
- Apply the entry on both outcomes in refreshSingleModelMetadata; the
  error toast is unchanged.
- Cover the failed-response path on the handler and the API client.
2026-11-04 11:38:12 +08:00
Will Miao 291d4feae4 feat(modal): calculate a pending sha256 from the model modal
Checkpoints and other large models are scanned lazily: the cache entry has
an empty sha256 with hash_status="pending", so the modal's hash footnote
rendered nothing and the hash could not be copied for external lookups.

- Expose hash_status in the model list responses and pass it to the modal
  through the card dataset.
- Render the pending / calculating / failed states in the hash footnote
  with a manual trigger, then swap in the calculated hash with its copy
  button; the underlying card and virtual-scroll entry stay in sync.
- Add POST /api/lm/{prefix}/calculate-hash, which reuses the scanner's
  singleflight hash calculation and persists the SQLite cache so the hash
  survives a restart without a rescan.
2026-11-04 10:54:36 +08:00
Will Miao 14e90006de fix(previews): detect video previews from the versioned preview URL
Preview URLs now carry a cache-busting `&v=<mtime_ns>` suffix, so the media
type could no longer be read from the end of the string. Model cards,
duplicates, recipe cards and modals, the showcase thumbnail and the ComfyUI
preview tooltip all fell into the image branch and rendered `<img>` for video
previews, which browsers cannot decode.

Move the query-aware video detection that already lived in the versions tab
into a shared mediaTypes helper and use it everywhere a server-provided
preview URL is inspected. The web UI and the ComfyUI widget bundle each keep a
copy because they are served from different mounts. Video previews that turn
out to be webm now also get the right `<source type>`.

Add regression coverage for versioned mp4/webm/image URLs.

Fixes #1168
2026-10-31 16:46:04 +08:00
Will Miao 1f0579a931 fix(recipes): register the R refresh shortcut on the recipes page
The R/F/D toolbar shortcuts were only wired in PageControls, but the
recipes page uses its own RecipePageControls in recipes.js. The shared
toolbar still rendered the R keycap and the shortcuts sheet listed it,
yet no keydown handler was registered, so pressing R did nothing.

Extract the shortcut handling into a shared ActionShortcuts module and
register it from both PageControls and RecipePageControls. Registration
is idempotent per event-manager source, so rebuilding controls cannot
fire the action twice. F/D still pass through on recipes because the
page has no fetch/download buttons.

Add regression coverage for the recipes shortcut.
2026-10-31 14:29:56 +08:00
Will Miao 91f955a277 fix(context-menu): keep only one context menu open at a time
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).
2026-10-31 11:27:48 +08:00
Will Miao 0ec714c122 fix(context-menu): keep popup menus inside the viewport
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
2026-10-31 11:09:50 +08:00
Will Miao 9daa9d7dd8 fix(workflows): remove copy filename from the card context menu 2026-10-31 08:31:39 +08:00
Will Miao e11c0e21e1 fix(ui): name each page's items instead of models in shared chrome
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.
2026-10-30 20:35:45 +08:00
Will Miao fed087eb8c fix(workflows): use workflow wording in card actions and dialogs
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.
2026-10-30 20:10:45 +08:00
Will Miao 874d0e96d8 feat(workflows): single library folder + create from ComfyUI
- Replace the multi-root folder_paths.workflows workflow library with a
  scalar workflow_path setting (Settings > Library); remove the
  extra_folder_paths.workflows path and the standalone Model Paths editor.
- Migrate pre-release settings.json files that still keep
  folder_paths.workflows / extra_folder_paths.workflows on startup, so an
  upgrade keeps the configured folder instead of silently losing it.
- Add "Create LM-managed workflow" to the ComfyUI Graph menu and the File
  menu/command palette: it writes the serialized graph into the configured
  folder, stamps extra.lm_source and registers the entry in the scanner
  cache without forcing a rescan.
- Attach the decode image of the client's last executed prompt as the
  workflow preview. The prompt id is sent with the create request and the
  backend only honors it while it is still the registry's current prompt,
  so a tab never borrows another tab's output; otherwise the card keeps the
  placeholder preview.
- Detect library entries deleted/renamed while a graph still carries the
  marker: a link-status endpoint clears the stale extra.lm_source (on graph
  load, menu open and before create/update), and the write-back route
  returns 404 instead of recreating the document.
- Collapse the workflows.noPaths standalone/ComfyUI split into a single
  description/hint; the folder is edited in Settings > Library in both
  modes, so the manual settings.json guidance and the standalone
  open-settings-folder fallback are gone.
- Add the preview tensor conversion as a shared utility (reused by the
  recipe widget save path) and cover create, preview gating, link status,
  migration and menu injection with tests.
2026-10-30 17:35:53 +08:00
Will Miao 7adfa799e9 fix(ui): use the shared field radius in the phone header
The phone search field was forced to border-radius:999px. That looked
out of place next to the 6px used everywhere else, and at that radius the
1px border vanished along both arcs - the element's overflow:hidden
rounded clip cuts the outline, so the field read as a broken frame.

Use var(--border-radius-sm, 6px), i.e. exactly the wide-screen value.
Verified at 320/390/520/600 and focused, light and dark.
2026-10-29 21:03:57 +08:00
Will Miao 06e230ee39 fix(ui): keep a full search field in the phone header
The <=600px header collapsed the search to a floating 32px circle, which
left ~250px of empty bar at 600px and hid the primary action behind a tap
even though the width supported a real field.

- The phone breakpoint moves to 520px: at 521px the icon nav and the
  200px search floor still coexist, and 480-600px keep their tabs.
- Below that the search is an always-visible pill that fills the row and
  the current page name sits next to the logo, so the bar still says
  where you are now that the tabs live in the menu.
- Drops the click-to-expand overlay and its Escape handler: no fixed
  positioning, no transparent placeholder, no breakpoint duplicated in JS.
- Active menu entry uses an accent tint; the hover token it used before
  has no dark-mode value.

Verified 320-1920px: no clipping or overlap, page title appears at
381-520px and the menu carries all seven tabs.
2026-10-29 20:50:42 +08:00
Will Miao 735c313cc7 feat(ui): phone header - tabs in the menu, search in a button
Below 600px seven tabs plus a usable search field no longer fit. The bar
now reads logo | search | menu:

- The nav entries are repeated in the hamburger dropdown (shown there
  only at <=600px) and the main nav is hidden.
- The search collapses to a round magnifier and expands into a fixed
  overlay covering the header row on focus; Escape or a click elsewhere
  returns it to the button.
- SettingsManager.setNavEntryVisible keeps both copies of the opt-in
  entries (Other, Workflows) in sync when the switches are flipped.
- The dropdown scrolls when its 12 entries do not fit the viewport.

Verified in a headless browser from 320px to 1920px: no clipping and no
overlap, and the menu keeps its five actions above 600px.
2026-10-29 20:33:53 +08:00
Will Miao 96f957b38f fix(ui): keep the header search usable as tabs grow
The opt-in Other Models and Workflows pages took the nav to seven tabs
(~783px with icons), which left the header search field 73px wide at
1280px - just its two inline toggles. Rebuild the responsive ladder
around the measured widths:

- <=1500px: drop nav icons, keep the labels; icons return above 1500px
- <=1366px: compact gaps/padding and drop the Ctrl+F cue
- <=1150px: hide the app title
- <=1100px: collapse the five round controls into the hamburger
- <=780px: labels collapse to icons (was 700px; the row overflowed and
  clipped the hamburger by 39px at 720px)
- <=520px: drop the search field's 200px floor (the row was clipped by
  93px at 390px)

Also give the nav links the a11y metadata the icon-only states need:
aria-label (the label span is display:none below 780px), title, and
aria-current on the active tab; decorative icons are aria-hidden.
2026-10-29 20:24:49 +08:00
Will Miao 53f7ff407a fix(workflows): restore uses the app confirmation modal and a loading overlay
- Replace the browser confirm() with a generic #confirmationModal
  (template + ModalManager registration + showConfirmationModal() helper
  in modalUtils.js, promise-based, danger-styled confirm button).
- Show the loadingManager overlay while the restore request is in flight;
  .json restores re-download from CivitAI and the wait previously looked
  like a dead click. The overlay hides in a finally block.
2026-10-29 12:23:22 +08:00
Will Miao f473678004 feat(workflows): restore a workflow to its downloaded state
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.
2026-10-29 12:12:52 +08:00
Will Miao e4369b0bd0 feat(workflows): show the Updated card cue after Update from ComfyUI
The write-back result carries the new document hash; the LM page now
refreshes the card in place via virtualScroller.updateSingleItem, which
re-renders it with the shared 'Updated' indicator.
2026-10-29 09:35:48 +08:00
Will Miao fa774de9ea feat(workflows): update managed workflows from the LM card context menu
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.
2026-10-29 08:29:26 +08:00
Will Miao 2d9a245ea8 fix(workflows): label the modal button Open in ComfyUI like the other entry points
Workflows load a whole graph document rather than inserting into the
current graph, so all workflow entry points (card icon, context menu,
modal button) now consistently read Open in ComfyUI; model types that
insert into the current graph keep Send to ComfyUI.
2026-10-28 20:17:01 +08:00
Will Miao adc8713c0b feat(workflows): wire card icons and modal send button for workflows
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
2026-10-28 20:09:10 +08:00
Will Miao 7efeb592fd feat(workflows): copy-to-clipboard flow and standalone-aware 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
2026-10-28 19:19:36 +08:00
Will Miao 0c55dff828 feat(workflows): use WF as the workflow card badge abbreviation
The fallback produced WORK (first four letters), which reads like a verb
and is wider than the other badges; WF matches community shorthand and
the two-letter style of CN/CV/TE. Also give the workflow sub-type its
full display name so tooltips show Workflow instead of the raw key.
2026-10-28 17:43:59 +08:00
Will Miao 7221f5632d fix(download): show brand-new model cards without a manual refresh
The in-place reconcile from #1078 no-oped whenever the downloaded model
had no card in the current view, which silently swallowed brand-new
downloads (e.g. pasting a CivitAI URL for a model never downloaded
before): the backend added the entry but the listing only caught up on
manual refresh.

Reconcile now takes wasLocallyOwned (derived from the existsLocally
flags of the version list the modal fetched before the download):
- owned model outside the current view: keep the #1078 no-op
- brand-new or unknown ownership with no matching card: fall back to
  the legacy reload so the new card appears
2026-10-28 16:59:05 +08:00
Will Miao 720440507c feat: add opt-in workflow library management
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.
2026-10-28 11:45:01 +08:00
Will Miao 493257bc3c feat(first-run): one-click bulk metadata fetch prompt after the first scan
A fresh install ends its first library scan with a page full of grey
placeholder cards. This adds the bridge from "scanned" to "illustrated
library":

- FirstRunManager.start_initial_scan arms a bulk_fetch_prompt_pending
  flag when the first scan actually launches. Upgrade shortcuts and the
  defer path skip it — those users already have metadata, or have no
  fresh scan to anchor the prompt to.
- New GET /api/lm/metadata-fetch-candidates reports how many cached
  models per page type qualify for a bulk fetch. It is read-only
  (in-memory cache only, never triggers a scan), excludes recipes, and
  shares the eligibility predicate with BulkMetadataRefreshUseCase
  (extracted as is_fetch_candidate/count_fetch_candidates), so the
  advertised number matches what a fetch would actually process.
- BannerService shows a one-shot banner with the candidate count and a
  Fetch Metadata action that runs the existing fetchCivitaiMetadata
  flow (progress bar + reload included). The flag is cleared once the
  banner displays, so it never nags twice; dismissal persists through
  the shared dismissed_banners setting.

Onboarding conflict is avoided structurally: the banner registers only
from the showPromos branch, which already requires the onboarding tour
to be finished or skipped and the first-run/init pages to be gone, so
the tour and this prompt never compete for the same moment.
2026-10-25 09:43:40 +08:00
Will Miao 8f9e7e54ad feat(init): structured byte-weighted scan progress broadcasts
The initialization page computed its ETA by regex-parsing the English
details strings ("Found N", "Processing x/y") and the progress bar
advanced per file, so one 7 GB LoRA and one 100 KB embedding moved it
by the same amount.

The scanner progress callbacks now carry the collected file sizes end
to end: process_models/count_models broadcasts include structured
processed/total/bytes_done/bytes_total fields, and the bar percentage
is weighted by bytes (falling back to file counts when sizes are
unavailable). initialization.js consumes the structured fields
directly - no more string parsing - and computes the remaining-time
estimate per byte, keeping the file-count path as a fallback.
2026-10-25 09:24:30 +08:00
Will Miao 86c0239b8c fix(init): honest error state and per-page completion for initialization
Two initialization-page defects made failures look like progress:

- A cache read exception while rendering a model/recipe page forced
  is_initializing=True, showing an endless spinner; worse, the polling
  fallback then reported the idle scanner as "complete", reloading the
  page straight back into the same exception. Page handlers now pass
  init_error to the initialization template, which renders the error
  state (with retry) server-side, and initialization.js stays on it
  instead of starting WebSocket/polling.

- The WebSocket path reloaded once the page's own scanner finished,
  while the polling fallback waited for every scanner. The polling
  fallback now mirrors the per-page semantics: /api/lm/init-status
  accepts ?page=<pageType> (aggregate behavior preserved without it),
  and ModelScanner records _init_error so a failed initial scan is
  reported as "error" instead of masquerading as "complete". Polling
  stops on error instead of reloading into the failure.
2026-10-25 09:02:57 +08:00
Will Miao 92820be4ad feat(first-run): gate the initial library scan behind a welcome page
Fresh installs no longer start a full library scan (with per-file SHA256
and sidecar writes) automatically at startup. Instead a welcome page
lists the configured model roots, estimates the scan cost from a quick
hash-free walk, and lets the user pick the sidecar storage layout and
UI language before confirming. Deferring shows an empty library; the
existing refresh button starts the scan later. Upgrading installs are
detected via a non-empty persisted cache snapshot (row-count check, so
the empty file created by scanner construction no longer masquerades
as an existing library) and keep the old behavior.

Bundled fixes surfaced by the new flow:

- broadcast an init-progress error when the startup scan fails instead
  of silently landing on an empty library; the initialization page now
  shows the error with a retry button
- skip the onboarding language modal/tutorial while the welcome or
  initialization page is up (its spotlight targets don't exist there,
  so the tour was silently auto-completed and never shown)
- promotional banners wait until onboarding is completed or skipped;
  alert banners (startup messages, cache health) are unaffected
- honor the hidden attribute in first-run/initialization CSS so the
  no-roots hint and error boxes don't render as empty frames
2026-10-25 09:02:57 +08:00
Will Miao 07fcaa137a fix(fetch): handle rate_limited status in metadata fetch UI
When a bulk metadata fetch hits a provider rate limit, the backend
aborts the batch and broadcasts {status: "rate_limited"} over the
fetch-progress WebSocket. The frontend switch in fetchCivitaiMetadata
never handled that status, so the completion promise never settled and
the loading overlay stayed up forever -- users had to cancel and
re-trigger, and the aborted batch silently never resumed.

- Add case 'rate_limited' to the fetch-progress switch: settle the
  promise so the overlay closes, and show an aborted status line.
- Show a warning toast with updated/failed/skipped counts and a hint to
  re-run Fetch from Civitai later.
- Reuse the failure-details modal with a rate-limited notice banner
  listing the models skipped due to rate limiting.

Backend behavior (abort after 3 consecutive rate limits, no automatic
resume) is the deliberate #1085 design and is unchanged.
2026-10-24 21:29:31 +08:00
Will Miao 0be3ea7d98 fix(download): accept mirror/www Civitai hosts and clarify linked-file errors
Follow-up to #1152:
- Accept civitai.green and www-prefixed hosts in isSupportedCivitaiPageHost,
  aligning with py/utils/civitai_utils.py and extensionReimportBridge.js, so
  download URLs on those hosts resolve too
- Drop the duplicate civitaiModelByVersion entry from the LoRA-specific
  endpoint map; the shared getApiEndpoints map now provides it for all types
- Report a distinct error when a linked fileId exists in the version but is
  not a downloadable model weight file (new linkedFileNotWeightFile locale key)
2026-10-21 21:53:59 +08:00
pixelpaws 8033292ad8 Merge pull request #1152 from ergs0204/fix/civitai-download-links
fix: accept Civitai download URLs and preserve file selection
2026-10-21 21:47:18 +08:00