Compare commits

...

7 Commits

Author SHA1 Message Date
Will Miao 121d8d5cea fix(ui): lighten toolbar shortcut keycaps and fix contrast on active buttons 2026-09-05 00:21:11 +08:00
Will Miao ec147bd677 feat(ui): add setting to keep the action bar visible while scrolling (#1095)
Adds a 'Keep Action Bar Visible' toggle (default off) under
Settings > Interface > Layout Settings. When enabled, the controls bar
(Refresh, Download, etc.) and the breadcrumb nav are wrapped in a shared
sticky container (.sticky-topbar) so both stay pinned as one unit; when
disabled, the wrapper is display: contents and the original behavior
(only the breadcrumb stays visible) is preserved.
2026-09-04 23:53:27 +08:00
Will Miao 93fc28b499 chore(ui): rebuild vue-widgets bundle
Rebuilt from the preceding three commits' sources:

- active-filters chip and its dead settings-toggled broadcast removed
  (the built bundle also no longer embeds the scripts/app.js test shim
  that the chip's settings.js import used to pull in)
- scrollbar inset re-measured on programmatic value changes
- app/api bindings canonicalized to "../../../scripts/*" externals and
  settings.js no longer inlined (bound at runtime via "../settings.js"
  to the vanilla module instance), per the new build guard
2026-09-04 19:02:36 +08:00
Will Miao 7afed1a14b fix(ui): cross-link right-click menu in active-filters settings tooltip
The loramanager.lora_active_filters_autocomplete tooltip only mentioned
the /activefilters and /noactivefilters commands, while the prompt-node
tag-autocomplete tooltip cross-links every toggle entry point (typing in
the node and the node's right-click menu). 6ba64ebb added the right-click
menu entry without extending the tooltip to match; align the wording with
the established pattern.
2026-09-04 19:02:31 +08:00
Will Miao e6f5142e48 fix(ui): re-measure autocomplete scrollbar inset on programmatic value changes
The --lm-vscrollbar-width inset from 634ea7f2 was only refreshed on input
events, mount and mode changes. Programmatic value updates (widget.setValue
from "send lora to workflow", external value-change events) change the
textarea content without an input event, leaving the corner clear (x)
button overlapping a freshly appeared classic scrollbar until the next
keystroke. Mount-time pending value replay was already covered.

- onExternalValueChange and widget.onSetValue now call
  updateVScrollbarWidth() alongside the hasText update
- tests: cover both paths by overriding textarea metrics to an overflowing
  state and asserting the 15px gutter lands in the CSS var
2026-09-04 19:02:31 +08:00
Will Miao 87f05fb66c build(vue-widgets): keep shared runtime modules external in the widget bundle
Guard against the inlined-shim bug class that broke the removed
active-filters chip: importing web/comfyui/* modules from widget source
inlines them into lora-manager-widgets.js, and their own relative imports
then resolve against the repo filesystem at build time instead of the
vanilla files' runtime URL layout.

A resolveId plugin (enforce: pre) now returns explicit external markers:

- scripts/app.js and scripts/api.js imported at any "../../scripts/*"
  depth are rewritten to the canonical "../../../scripts/*" specifier so
  every app/api binding in the bundle is the real ComfyUI module. The
  repo-root scripts/app.js is a unit-test shim (in-memory settings store)
  and must never be bundled; the canonical depth is the only one that
  resolves from the emitted bundle's served location.
- web/comfyui/settings.js is externalized to "../settings.js" so the
  bundle binds to the SAME vanilla module instance the ComfyUI extension
  loader already runs - real settings store, registerExtension side
  effect executed exactly once, no duplicated module state.

A companion plugin warns on any web/comfyui/* import from widget source,
since an inlined copy still duplicates module-level side effects.

Notes from validating the mechanism: rollup output.paths resolves
returned paths to absolute filesystem locations (rejected), and a depth
regex inside rollupOptions.external matches raw specifiers before
resolveId hooks run and would emit the shim-relative depth verbatim
(rejected) - hence explicit { id, external: true } returns.
2026-09-04 19:02:25 +08:00
Will Miao cf64e5baa8 fix(ui): remove per-node active-filters chip from loras widgets
The indicator chip added for /activefilters discoverability was broken by
design of its import path: AutocompleteTextWidget.vue imported
web/comfyui/settings.js into the vue-widgets bundle, and settings.js's
"../../scripts/app.js" import resolved at build time to the repo-root test
shim (scripts/app.js, an in-memory settings store). The chip therefore read
and wrote an orphaned in-memory Map: clicking it flipped only its own
visual state and never touched the real ComfyUI setting that
autocomplete.js consults (use_active_filters query param).

Beyond the defect, a persistent per-node control for a global persisted
setting misleads users and needs cross-instance sync machinery, which the
footer hint, slash commands, right-click menu entry and settings dialog
already cover.

- AutocompleteTextWidget.vue: remove the chip button, its state/handlers,
  the settings.js import (the shim-inlining pathway) and all chip styles
- AutocompleteTextWidget.test.ts: drop the chip indicator describe block
  and the settings.js module mock; beforeEach import no longer needed
- settings.js: drop the lora-manager:setting-toggled window broadcast and
  its export — the chip was its only consumer, so every
  setLoraManagerSettingValue write no longer dispatches a dead event
- autocomplete.activeFilters.test.js: drop the broadcast assertion test
- loraLoader.activeFiltersMenu.test.js: drop SETTING_TOGGLED_EVENT_NAME
  from the settings.js mock

Discoverability of /activefilters // /noactivefilters is unchanged:
command-list footer, first-run hint, node context menu, settings dialog.
2026-09-04 19:02:16 +08:00
27 changed files with 307 additions and 539 deletions
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "Nach Modell gruppieren", "groupByModel": "Nach Modell gruppieren",
"groupByModelHelp": "Wenn aktiviert, wird nur die neueste Version jedes CivitAI-Modells als einzelne Karte angezeigt. Ältere Versionen werden ausgeblendet.", "groupByModelHelp": "Wenn aktiviert, wird nur die neueste Version jedes CivitAI-Modells als einzelne Karte angezeigt. Ältere Versionen werden ausgeblendet.",
"stickyControls": "Aktionsleiste sichtbar halten",
"stickyControlsHelp": "Wenn aktiviert, bleibt die Aktionsleiste (Aktualisieren, Herunterladen usw.) beim Scrollen zusammen mit der Breadcrumb-Navigation oben angeheftet.",
"displayDensity": "Anzeige-Dichte", "displayDensity": "Anzeige-Dichte",
"displayDensityOptions": { "displayDensityOptions": {
"default": "Standard", "default": "Standard",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "Group by Model", "groupByModel": "Group by Model",
"groupByModelHelp": "When enabled, only the latest version of each CivitAI model is shown as a single card. Older versions are hidden.", "groupByModelHelp": "When enabled, only the latest version of each CivitAI model is shown as a single card. Older versions are hidden.",
"stickyControls": "Keep Action Bar Visible",
"stickyControlsHelp": "When enabled, the action bar (Refresh, Download, etc.) stays pinned at the top while scrolling, together with the breadcrumb navigation.",
"displayDensity": "Display Density", "displayDensity": "Display Density",
"displayDensityOptions": { "displayDensityOptions": {
"default": "Default", "default": "Default",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "Agrupar por modelo", "groupByModel": "Agrupar por modelo",
"groupByModelHelp": "Cuando está activado, solo se muestra la versión más reciente de cada modelo de CivitAI como una tarjeta única. Las versiones anteriores están ocultas.", "groupByModelHelp": "Cuando está activado, solo se muestra la versión más reciente de cada modelo de CivitAI como una tarjeta única. Las versiones anteriores están ocultas.",
"stickyControls": "Mantener visible la barra de acciones",
"stickyControlsHelp": "Cuando está activado, la barra de acciones (Actualizar, Descargar, etc.) permanece fijada en la parte superior al desplazarse, junto con la navegación por rutas.",
"displayDensity": "Densidad de visualización", "displayDensity": "Densidad de visualización",
"displayDensityOptions": { "displayDensityOptions": {
"default": "Predeterminado", "default": "Predeterminado",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "Grouper par modèle", "groupByModel": "Grouper par modèle",
"groupByModelHelp": "Lorsque activé, seule la version la plus récente de chaque modèle CivitAI s'affiche sous forme de carte unique. Les versions plus anciennes sont masquées.", "groupByModelHelp": "Lorsque activé, seule la version la plus récente de chaque modèle CivitAI s'affiche sous forme de carte unique. Les versions plus anciennes sont masquées.",
"stickyControls": "Garder la barre d'actions visible",
"stickyControlsHelp": "Lorsque activé, la barre d'actions (Actualiser, Télécharger, etc.) reste épinglée en haut lors du défilement, avec la navigation par fil d'Ariane.",
"displayDensity": "Densité d'affichage", "displayDensity": "Densité d'affichage",
"displayDensityOptions": { "displayDensityOptions": {
"default": "Par défaut", "default": "Par défaut",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "קיבוץ לפי מודל", "groupByModel": "קיבוץ לפי מודל",
"groupByModelHelp": "כאשר מופעל, רק הגרסה העדכנית ביותר של כל מודל CivitAI מוצגת ככרטיס בודד. גרסאות ישנות יותר מוסתרות.", "groupByModelHelp": "כאשר מופעל, רק הגרסה העדכנית ביותר של כל מודל CivitAI מוצגת ככרטיס בודד. גרסאות ישנות יותר מוסתרות.",
"stickyControls": "השארת סרגל הפעולות גלוי",
"stickyControlsHelp": "כאשר מופעל, סרגל הפעולות (רענון, הורדה וכו') נשאר מוצמד לחלק העליון בעת גלילה, יחד עם ניווט פירורי הלחם.",
"displayDensity": "צפיפות תצוגה", "displayDensity": "צפיפות תצוגה",
"displayDensityOptions": { "displayDensityOptions": {
"default": "ברירת מחדל", "default": "ברירת מחדל",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "モデルでグループ化", "groupByModel": "モデルでグループ化",
"groupByModelHelp": "有効にすると、各CivitAIモデルの最新バージョンのみが1枚のカードとして表示され、古いバージョンは非表示になります。", "groupByModelHelp": "有効にすると、各CivitAIモデルの最新バージョンのみが1枚のカードとして表示され、古いバージョンは非表示になります。",
"stickyControls": "アクションバーを常に表示",
"stickyControlsHelp": "有効にすると、アクションバー(更新、ダウンロードなど)がスクロール時にパンくずナビゲーションと一緒に画面上部に固定されます。",
"displayDensity": "表示密度", "displayDensity": "表示密度",
"displayDensityOptions": { "displayDensityOptions": {
"default": "デフォルト", "default": "デフォルト",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "모델별 그룹화", "groupByModel": "모델별 그룹화",
"groupByModelHelp": "활성화하면 각 CivitAI 모델의 최신 버전만 단일 카드로 표시되며, 이전 버전은 숨겨집니다.", "groupByModelHelp": "활성화하면 각 CivitAI 모델의 최신 버전만 단일 카드로 표시되며, 이전 버전은 숨겨집니다.",
"stickyControls": "작업 표시줄 항상 표시",
"stickyControlsHelp": "활성화하면 작업 표시줄(새로고침, 다운로드 등)이 스크롤 시 브레드크럼 내비게이션과 함께 상단에 고정됩니다.",
"displayDensity": "표시 밀도", "displayDensity": "표시 밀도",
"displayDensityOptions": { "displayDensityOptions": {
"default": "기본", "default": "기본",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "Группировать по модели", "groupByModel": "Группировать по модели",
"groupByModelHelp": "При включении отображается только последняя версия каждой модели CivitAI в виде одной карточки. Старые версии скрыты.", "groupByModelHelp": "При включении отображается только последняя версия каждой модели CivitAI в виде одной карточки. Старые версии скрыты.",
"stickyControls": "Держать панель действий видимой",
"stickyControlsHelp": "При включении панель действий (Обновить, Загрузить и т. д.) остаётся закреплённой вверху при прокрутке вместе с навигацией по папкам.",
"displayDensity": "Плотность отображения", "displayDensity": "Плотность отображения",
"displayDensityOptions": { "displayDensityOptions": {
"default": "По умолчанию", "default": "По умолчанию",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "按模型分组", "groupByModel": "按模型分组",
"groupByModelHelp": "开启后,每个 CivitAI 模型仅显示最新版本的单张卡片,旧版本将被隐藏。", "groupByModelHelp": "开启后,每个 CivitAI 模型仅显示最新版本的单张卡片,旧版本将被隐藏。",
"stickyControls": "保持操作栏可见",
"stickyControlsHelp": "开启后,操作栏(刷新、下载等)会在滚动时与路径导航一起固定在页面顶部。",
"displayDensity": "显示密度", "displayDensity": "显示密度",
"displayDensityOptions": { "displayDensityOptions": {
"default": "默认", "default": "默认",
+2
View File
@@ -484,6 +484,8 @@
"layoutSettings": { "layoutSettings": {
"groupByModel": "按模型分組", "groupByModel": "按模型分組",
"groupByModelHelp": "啟用後,每個 CivitAI 模型僅顯示最新版本的單張卡片,舊版本將被隱藏。", "groupByModelHelp": "啟用後,每個 CivitAI 模型僅顯示最新版本的單張卡片,舊版本將被隱藏。",
"stickyControls": "保持操作列可見",
"stickyControlsHelp": "啟用後,操作列(重新整理、下載等)會在捲動時與麵包屑導覽一起固定在頁面頂端。",
"displayDensity": "顯示密度", "displayDensity": "顯示密度",
"displayDensityOptions": { "displayDensityOptions": {
"default": "預設", "default": "預設",
+1
View File
@@ -116,6 +116,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
"backup_retention_count": 5, "backup_retention_count": 5,
"use_new_license_icons": True, "use_new_license_icons": True,
"group_by_model": False, "group_by_model": False,
"sticky_controls": False,
# AI / LLM provider configuration (BYOK) # AI / LLM provider configuration (BYOK)
"llm_provider": "openai", # "openai" | "ollama" | "custom" "llm_provider": "openai", # "openai" | "ollama" | "custom"
"llm_api_key": "", "llm_api_key": "",
+33 -8
View File
@@ -25,6 +25,23 @@
box-shadow: var(--shadow-xs); box-shadow: var(--shadow-xs);
} }
/* Wrapper around the controls bar and breadcrumb nav. With the sticky-controls
setting off it is transparent to layout (display: contents), preserving the
original behavior (only the breadcrumb stays visible). When enabled, the whole
wrapper sticks as one unit so the two bars can never drift apart. */
.sticky-topbar {
display: contents;
}
body.sticky-controls .sticky-topbar {
display: block;
position: sticky;
top: 0;
z-index: calc(var(--z-header) - 1);
background: var(--bg-color);
box-shadow: var(--shadow-xs);
}
/* Responsive container for larger screens */ /* Responsive container for larger screens */
@media (min-width: 2150px) { @media (min-width: 2150px) {
.container { .container {
@@ -201,20 +218,17 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-left: 6px; min-width: 16px;
min-width: 18px; height: 16px;
height: 18px; padding: 0 4px;
padding: 0 5px; font-size: 10px;
font-size: 11px; font-weight: 500;
font-weight: 600;
line-height: 1; line-height: 1;
text-transform: uppercase; text-transform: uppercase;
border-radius: var(--border-radius-xs); border-radius: var(--border-radius-xs);
background-color: var(--shortcut-bg); background-color: var(--shortcut-bg);
border: 1px solid var(--shortcut-border); border: 1px solid var(--shortcut-border);
box-shadow: var(--shortcut-shadow);
color: var(--shortcut-text); color: var(--shortcut-text);
vertical-align: middle;
opacity: 0.8; opacity: 0.8;
transition: var(--transition-base); transition: var(--transition-base);
} }
@@ -225,10 +239,21 @@
border-color: var(--shortcut-border-hover); border-color: var(--shortcut-border-hover);
} }
/* Invert the keycap on active (accent-filled) buttons for contrast.
Must come after the hover rule above so it wins on active+hover. */
.control-group button.active .shortcut-key,
.control-group button.active:hover .shortcut-key {
color: var(--lora-accent);
background: rgba(255, 255, 255, 0.92);
border-color: transparent;
opacity: 1;
}
/* Ensure correct vertical alignment for text+shortcut */ /* Ensure correct vertical alignment for text+shortcut */
.control-group button span { .control-group button span {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 6px;
} }
/* Select dropdown styling */ /* Select dropdown styling */
+10
View File
@@ -1047,6 +1047,12 @@ export class SettingsManager {
groupByModelCheckbox.checked = !!state.global.settings.group_by_model; groupByModelCheckbox.checked = !!state.global.settings.group_by_model;
} }
// Set sticky controls
const stickyControlsCheckbox = document.getElementById('stickyControls');
if (stickyControlsCheckbox) {
stickyControlsCheckbox.checked = !!state.global.settings.sticky_controls;
}
// Set model name display setting // Set model name display setting
const modelNameDisplaySelect = document.getElementById('modelNameDisplay'); const modelNameDisplaySelect = document.getElementById('modelNameDisplay');
if (modelNameDisplaySelect) { if (modelNameDisplaySelect) {
@@ -3396,6 +3402,10 @@ export class SettingsManager {
const groupByModel = !!state.global.settings.group_by_model; const groupByModel = !!state.global.settings.group_by_model;
document.body.classList.toggle('group-by-model', groupByModel); document.body.classList.toggle('group-by-model', groupByModel);
// Apply sticky controls mode (keeps the action bar visible while scrolling)
const stickyControls = !!state.global.settings.sticky_controls;
document.body.classList.toggle('sticky-controls', stickyControls);
} }
} }
+1
View File
@@ -59,6 +59,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
strip_lora_on_copy: false, strip_lora_on_copy: false,
use_new_license_icons: true, use_new_license_icons: true,
group_by_model: false, group_by_model: false,
sticky_controls: false,
llm_provider: 'openai', llm_provider: 'openai',
llm_api_key: '', llm_api_key: '',
llm_api_base: '', llm_api_base: '',
+2
View File
@@ -54,8 +54,10 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="sticky-topbar">
{% include 'components/controls.html' %} {% include 'components/controls.html' %}
{% include 'components/breadcrumb.html' %} {% include 'components/breadcrumb.html' %}
</div>
{% include 'components/duplicates_banner.html' %} {% include 'components/duplicates_banner.html' %}
{% include 'components/folder_sidebar.html' %} {% include 'components/folder_sidebar.html' %}
@@ -79,6 +79,9 @@
<!-- Group by model toggle --> <!-- Group by model toggle -->
{{ sm.setting_toggle('groupByModel', 'group_by_model', 'settings.layoutSettings.groupByModel', 'settings.layoutSettings.groupByModelHelp') }} {{ sm.setting_toggle('groupByModel', 'group_by_model', 'settings.layoutSettings.groupByModel', 'settings.layoutSettings.groupByModelHelp') }}
<!-- Sticky controls toggle -->
{{ sm.setting_toggle('stickyControls', 'sticky_controls', 'settings.layoutSettings.stickyControls', 'settings.layoutSettings.stickyControlsHelp') }}
{{ sm.setting_select('cardInfoDisplay', 'card_info_display', 'settings.layoutSettings.cardInfoDisplay', [ {{ sm.setting_select('cardInfoDisplay', 'card_info_display', 'settings.layoutSettings.cardInfoDisplay', [
('always', 'settings.layoutSettings.cardInfoDisplayOptions.always'), ('always', 'settings.layoutSettings.cardInfoDisplayOptions.always'),
('hover', 'settings.layoutSettings.cardInfoDisplayOptions.hover'), ('hover', 'settings.layoutSettings.cardInfoDisplayOptions.hover'),
+2
View File
@@ -53,8 +53,10 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="sticky-topbar">
{% include 'components/controls.html' %} {% include 'components/controls.html' %}
{% include 'components/breadcrumb.html' %} {% include 'components/breadcrumb.html' %}
</div>
{% include 'components/duplicates_banner.html' %} {% include 'components/duplicates_banner.html' %}
{% include 'components/folder_sidebar.html' %} {% include 'components/folder_sidebar.html' %}
+2
View File
@@ -8,8 +8,10 @@
{% block init_check_url %}/api/loras/list?page=1&page_size=1{% endblock %} {% block init_check_url %}/api/loras/list?page=1&page_size=1{% endblock %}
{% block content %} {% block content %}
<div class="sticky-topbar">
{% include 'components/controls.html' %} {% include 'components/controls.html' %}
{% include 'components/breadcrumb.html' %} {% include 'components/breadcrumb.html' %}
</div>
{% include 'components/duplicates_banner.html' %} {% include 'components/duplicates_banner.html' %}
{% include 'components/folder_sidebar.html' %} {% include 'components/folder_sidebar.html' %}
+3 -1
View File
@@ -64,10 +64,12 @@
{% block init_check_url %}/api/recipes?page=1&page_size=1{% endblock %} {% block init_check_url %}/api/recipes?page=1&page_size=1{% endblock %}
{% block content %} {% block content %}
<!-- Recipe controls --> <!-- Sticky topbar: controls + breadcrumb -->
<div class="sticky-topbar">
{% include 'components/controls.html' %} {% include 'components/controls.html' %}
<!-- Breadcrumb Navigation --> <!-- Breadcrumb Navigation -->
{% include 'components/breadcrumb.html' %} {% include 'components/breadcrumb.html' %}
</div>
<!-- Duplicates banner (hidden by default) --> <!-- Duplicates banner (hidden by default) -->
<div id="duplicatesBanner" class="duplicates-banner" style="display: none;"> <div id="duplicatesBanner" class="duplicates-banner" style="display: none;">
@@ -253,37 +253,4 @@ describe('AutoComplete active-filters flag', () => {
expect(autoComplete.dropdown.querySelector('.lm-autocomplete-first-run-hint')).toBeNull(); expect(autoComplete.dropdown.querySelector('.lm-autocomplete-first-run-hint')).toBeNull();
}); });
it('broadcasts a setting-toggled window event when /activefilters is accepted', async () => {
const events = [];
const listener = (event) => events.push(event.detail);
window.addEventListener('lora-manager:setting-toggled', listener);
try {
const input = document.createElement('textarea');
input.value = '/activefilters';
input.selectionStart = input.value.length;
input.focus = vi.fn();
input.setSelectionRange = vi.fn();
document.body.append(input);
caretHelperInstance.getBeforeCursor.mockReturnValue('/activefilters');
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
const autoComplete = new AutoComplete(input, 'loras', { showPreview: false, minChars: 1 });
input.dispatchEvent(new Event('input', { bubbles: true }));
// The command token is cleared after acceptance; simulate the caret
// helper seeing the cleared input so the synthetic input event does
// not re-trigger command parsing (same pattern as behavior tests).
caretHelperInstance.getBeforeCursor.mockReturnValue('');
await Promise.resolve();
await Promise.resolve();
expect(events).toContainEqual({
settingId: 'loramanager.lora_active_filters_autocomplete',
value: true,
});
} finally {
window.removeEventListener('lora-manager:setting-toggled', listener);
}
});
}); });
@@ -54,7 +54,6 @@ const setSettingValueMock = vi.fn();
vi.mock(SETTINGS_MODULE, () => ({ vi.mock(SETTINGS_MODULE, () => ({
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID: LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID:
"loramanager.lora_active_filters_autocomplete", "loramanager.lora_active_filters_autocomplete",
SETTING_TOGGLED_EVENT_NAME: "lora-manager:setting-toggled",
getLoraActiveFiltersAutocompletePreference: getActiveFiltersPreferenceMock, getLoraActiveFiltersAutocompletePreference: getActiveFiltersPreferenceMock,
setLoraManagerSettingValue: setSettingValueMock, setLoraManagerSettingValue: setSettingValueMock,
})); }));
@@ -27,18 +27,6 @@
<line x1="6" y1="6" x2="18" y2="18" /> <line x1="6" y1="6" x2="18" y2="18" />
</svg> </svg>
</button> </button>
<button
v-if="isLorasMode"
type="button"
class="active-filters-toggle"
:class="{ 'is-active': activeFiltersEnabled }"
:title="activeFiltersToggleTitle"
@click="toggleActiveFiltersSearch"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" />
</svg>
</button>
</div> </div>
</div> </div>
</template> </template>
@@ -46,8 +34,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, computed } from 'vue' import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useAutocomplete } from '@/composables/useAutocomplete' import { useAutocomplete } from '@/composables/useAutocomplete'
// @ts-ignore - ComfyUI external module
import { LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID, SETTING_TOGGLED_EVENT_NAME, getLoraActiveFiltersAutocompletePreference, setLoraManagerSettingValue } from '../../../web/comfyui/settings.js'
// Access LiteGraph global for initial mode detection // Access LiteGraph global for initial mode detection
declare const LiteGraph: { vueNodesMode?: boolean } | undefined declare const LiteGraph: { vueNodesMode?: boolean } | undefined
@@ -87,10 +73,10 @@ const inputWrapperRef = ref<HTMLElement | null>(null)
// Width of the textarea's own vertical scrollbar gutter. When the content // Width of the textarea's own vertical scrollbar gutter. When the content
// overflows and a classic (non-overlay) scrollbar is shown, the scrollbar // overflows and a classic (non-overlay) scrollbar is shown, the scrollbar
// occupies the textarea's rightmost pixels and the absolutely-positioned // occupies the textarea's rightmost pixels and the absolutely-positioned
// corner buttons (clear x / active-filters filter) would overlap it. We // corner clear (x) button would overlap it. We expose this width as a CSS
// expose this width as a CSS var so those buttons can shift left of the // var so the button can shift left of the scrollbar; it is 0 when there is
// scrollbar; it is 0 when there is no scrollbar (content fits, or platform // no scrollbar (content fits, or platform overlay scrollbars that float
// overlay scrollbars that float over the content). // over the content).
const vScrollbarWidth = ref(0) const vScrollbarWidth = ref(0)
let scrollbarResizeObserver: ResizeObserver | null = null let scrollbarResizeObserver: ResizeObserver | null = null
@@ -125,48 +111,6 @@ const hasText = ref(false)
// Show clear button when there is text // Show clear button when there is text
const showClearButton = computed(() => hasText.value) const showClearButton = computed(() => hasText.value)
// Active-filters search indicator (loras nodes only). Mirrors the
// loramanager.lora_active_filters_autocomplete setting so users can
// discover and toggle the /activefilters mode without opening the
// dropdown or the settings dialog.
const isLorasMode = (props.modelType ?? 'loras') === 'loras'
const activeFiltersEnabled = ref(false)
const refreshActiveFiltersState = () => {
if (isLorasMode) {
activeFiltersEnabled.value = getLoraActiveFiltersAutocompletePreference()
}
}
const onSettingToggled = (event: Event) => {
const detail = (event as CustomEvent<{ settingId?: string; value?: unknown }>).detail
if (detail?.settingId === LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID) {
activeFiltersEnabled.value = detail.value === true
}
}
const activeFiltersToggleTitle = computed(() =>
activeFiltersEnabled.value
? 'Active Filters Search is ON: suggestions respect the LoRA Manager page filters. Click to disable, or type /noactivefilters.'
: 'Active Filters Search is OFF: suggestions search the full library. Click to enable, or type /activefilters.'
)
const toggleActiveFiltersSearch = async () => {
const newValue = !activeFiltersEnabled.value
try {
const success = await setLoraManagerSettingValue(
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID,
newValue
)
if (!success) {
throw new Error('settings API unavailable')
}
activeFiltersEnabled.value = newValue
} catch (error) {
console.error('[Lora Manager] Failed to toggle active filters search:', error)
}
}
// Initialize autocomplete with direct ref access // Initialize autocomplete with direct ref access
useAutocomplete( useAutocomplete(
textareaRef, textareaRef,
@@ -256,6 +200,9 @@ const onWheel = (event: WheelEvent) => {
// Handle external value changes (e.g., from "send lora to workflow") // Handle external value changes (e.g., from "send lora to workflow")
const onExternalValueChange = () => { const onExternalValueChange = () => {
// The DOM value was set synchronously by the caller; the new content may
// have toggled the vertical scrollbar on/off.
updateVScrollbarWidth()
updateHasTextState() updateHasTextState()
} }
@@ -265,6 +212,9 @@ const setupWidgetOnSetValue = () => {
props.widget.onSetValue = (value: string) => { props.widget.onSetValue = (value: string) => {
// The DOM value is already set by setValue, just update our state // The DOM value is already set by setValue, just update our state
hasText.value = value.length > 0 hasText.value = value.length > 0
// Programmatic sets can toggle the scrollbar; re-measure so the
// corner clear button stays clear of it.
updateVScrollbarWidth()
} }
} }
} }
@@ -355,11 +305,6 @@ onMounted(() => {
// Setup widget.onSetValue callback // Setup widget.onSetValue callback
setupWidgetOnSetValue() setupWidgetOnSetValue()
// Active-filters indicator: read initial state and stay in sync with
// slash-command / context-menu toggles dispatched via settings.js
refreshActiveFiltersState()
window.addEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled)
// Keep the corner buttons clear of the textarea's vertical scrollbar. // Keep the corner buttons clear of the textarea's vertical scrollbar.
updateVScrollbarWidth() updateVScrollbarWidth()
observeScrollbarWidth() observeScrollbarWidth()
@@ -391,7 +336,6 @@ onUnmounted(() => {
// Remove event listener // Remove event listener
document.removeEventListener('lora-manager:vue-mode-change', onModeChange) document.removeEventListener('lora-manager:vue-mode-change', onModeChange)
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled)
}) })
</script> </script>
@@ -484,58 +428,6 @@ onUnmounted(() => {
height: 12px; height: 12px;
} }
/* Active-filters search indicator (loras nodes only) */
.active-filters-toggle {
position: absolute;
top: 3px;
right: calc(3px + var(--lm-vscrollbar-width, 0px));
width: 16px;
height: 16px;
padding: 2px;
margin: 0;
border: none;
border-radius: 4px;
background: rgba(128, 128, 128, 0.25);
color: rgba(255, 255, 255, 0.5);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease;
z-index: 10;
}
.active-filters-toggle:hover {
opacity: 1;
background: rgba(128, 128, 128, 0.45);
color: rgba(255, 255, 255, 0.85);
}
.active-filters-toggle.is-active {
background: rgba(59, 130, 246, 0.35);
color: #7db8ff;
opacity: 1;
}
.active-filters-toggle svg {
width: 11px;
height: 11px;
}
/* Vue DOM mode adjustments for the indicator */
.text-input.vue-dom-mode ~ .active-filters-toggle {
top: 8px;
right: calc(8px + var(--lm-vscrollbar-width, 0px));
width: 20px;
height: 20px;
}
.text-input.vue-dom-mode ~ .active-filters-toggle svg {
width: 13px;
height: 13px;
}
/* Vue DOM mode adjustments for clear button */ /* Vue DOM mode adjustments for clear button */
.text-input.vue-dom-mode ~ .clear-button { .text-input.vue-dom-mode ~ .clear-button {
right: calc(8px + var(--lm-vscrollbar-width, 0px)); right: calc(8px + var(--lm-vscrollbar-width, 0px));
@@ -10,13 +10,13 @@
import { nextTick } from 'vue' import { nextTick } from 'vue'
import { shallowMount } from '@vue/test-utils' import { shallowMount } from '@vue/test-utils'
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' import { describe, expect, it, vi, afterEach } from 'vitest'
import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue' import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue'
function createMockWidget() { function createMockWidget() {
return { return {
callback: vi.fn(), callback: vi.fn(),
onSetValue: undefined, onSetValue: undefined as ((v: string) => void) | undefined,
inputEl: undefined, inputEl: undefined,
metadataWidget: undefined, metadataWidget: undefined,
name: 'text', name: 'text',
@@ -135,121 +135,15 @@ describe('AutocompleteTextWidget clear button', () => {
}) })
}) })
/**
* Tests for the active-filters search indicator (loras mode only).
*
* The small filter chip in the textarea corner mirrors the
* loramanager.lora_active_filters_autocomplete setting: it reflects the
* current state, can toggle it, and stays in sync with slash-command /
* context-menu toggles via the lora-manager:setting-toggled window event.
*/
const settingsMocks = vi.hoisted(() => ({
getPreference: vi.fn(),
setValue: vi.fn(),
}))
vi.mock('../../../web/comfyui/settings.js', () => ({
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID:
'loramanager.lora_active_filters_autocomplete',
SETTING_TOGGLED_EVENT_NAME: 'lora-manager:setting-toggled',
getLoraActiveFiltersAutocompletePreference: settingsMocks.getPreference,
setLoraManagerSettingValue: settingsMocks.setValue,
}))
const getActiveFiltersPreferenceMock = settingsMocks.getPreference
const setSettingValueMock = settingsMocks.setValue
function mountLorasWidget() {
const widget = createMockWidget()
const node = { id: 1 }
const wrapper = shallowMount(AutocompleteTextWidget, {
props: { widget, node, modelType: 'loras' },
attachTo: document.body,
})
return { wrapper, widget }
}
describe('AutocompleteTextWidget active-filters indicator', () => {
beforeEach(() => {
getActiveFiltersPreferenceMock.mockReset()
getActiveFiltersPreferenceMock.mockReturnValue(false)
setSettingValueMock.mockReset()
setSettingValueMock.mockResolvedValue(true)
})
it('renders only in loras mode', () => {
const loras = mountLorasWidget()
expect(loras.wrapper.find('.active-filters-toggle').exists()).toBe(true)
const widget = createMockWidget()
const prompt = shallowMount(AutocompleteTextWidget, {
props: { widget, node: { id: 2 }, modelType: 'prompt' },
attachTo: document.body,
})
expect(prompt.find('.active-filters-toggle').exists()).toBe(false)
})
it('reflects the current setting state', async () => {
const { wrapper } = mountLorasWidget()
await nextTick()
expect(wrapper.find('.active-filters-toggle').classes()).not.toContain('is-active')
getActiveFiltersPreferenceMock.mockReturnValue(true)
const wrapper2 = mountLorasWidget().wrapper
await nextTick()
expect(wrapper2.find('.active-filters-toggle').classes()).toContain('is-active')
})
it('toggles the setting when clicked', async () => {
const { wrapper } = mountLorasWidget()
await nextTick()
await wrapper.find('.active-filters-toggle').trigger('click')
expect(setSettingValueMock).toHaveBeenCalledWith(
'loramanager.lora_active_filters_autocomplete',
true
)
await nextTick()
expect(wrapper.find('.active-filters-toggle').classes()).toContain('is-active')
})
it('stays in sync with setting-toggled window events', async () => {
const { wrapper } = mountLorasWidget()
await nextTick()
expect(wrapper.find('.active-filters-toggle').classes()).not.toContain('is-active')
window.dispatchEvent(
new CustomEvent('lora-manager:setting-toggled', {
detail: {
settingId: 'loramanager.lora_active_filters_autocomplete',
value: true,
},
})
)
await nextTick()
expect(wrapper.find('.active-filters-toggle').classes()).toContain('is-active')
window.dispatchEvent(
new CustomEvent('lora-manager:setting-toggled', {
detail: { settingId: 'loramanager.some_other_setting', value: true },
})
)
await nextTick()
// Unrelated settings must not flip the indicator
expect(wrapper.find('.active-filters-toggle').classes()).toContain('is-active')
})
})
/** /**
* Tests for the vertical-scrollbar inset. * Tests for the vertical-scrollbar inset.
* *
* When the textarea content overflows and a classic (non-overlay) scrollbar * When the textarea content overflows and a classic (non-overlay) scrollbar
* is shown, the absolutely-positioned corner buttons (clear x, active-filters * is shown, the absolutely-positioned corner clear (x) button would sit on
* filter chip) would sit on top of the scrollbar. The component measures the * top of the scrollbar. The component measures the scrollbar gutter and
* scrollbar gutter and exposes it as the --lm-vscrollbar-width CSS var on * exposes it as the --lm-vscrollbar-width CSS var on .input-wrapper so the
* .input-wrapper so the buttons shift left of the scrollbar. jsdom does no * button shifts left of the scrollbar. jsdom does no layout, so overflow is
* layout, so overflow is simulated by overriding the scroll/dimension props. * simulated by overriding the scroll/dimension props.
*/ */
describe('AutocompleteTextWidget vertical scrollbar inset', () => { describe('AutocompleteTextWidget vertical scrollbar inset', () => {
function overrideTextareaMetrics( function overrideTextareaMetrics(
@@ -322,4 +216,66 @@ describe('AutocompleteTextWidget vertical scrollbar inset', () => {
await nextTick() await nextTick()
expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('0px') expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('0px')
}) })
it('re-measures the inset when the value is set programmatically via onSetValue', async () => {
const { wrapper, widget } = mountWidget()
const textarea = wrapper.find('textarea').element as HTMLTextAreaElement
// Start with no overflow
overrideTextareaMetrics(textarea, {
scrollHeight: 100,
clientHeight: 100,
offsetWidth: 320,
clientWidth: 320,
})
await nextTick()
// Simulate an external setValue (e.g. "send lora to workflow"): the DOM
// value is set by the caller and widget.onSetValue fires without an
// input event. Content now overflows → inset must be re-measured.
overrideTextareaMetrics(textarea, {
scrollHeight: 200,
clientHeight: 100,
offsetWidth: 320,
clientWidth: 305, // 15px scrollbar gutter
})
if (!widget.onSetValue) throw new Error('onSetValue not installed by component')
widget.onSetValue('long content')
await nextTick()
const wrapperEl = wrapper.find('.input-wrapper').element as HTMLElement
expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('15px')
})
it('re-measures the inset on external value-change events', async () => {
const { wrapper } = mountWidget()
const textarea = wrapper.find('textarea').element as HTMLTextAreaElement
// Start with no overflow
overrideTextareaMetrics(textarea, {
scrollHeight: 100,
clientHeight: 100,
offsetWidth: 320,
clientWidth: 320,
})
await nextTick()
// The lora-manager:autocomplete-value-changed event fires when the
// widget value is set externally; content now overflows → re-measure.
overrideTextareaMetrics(textarea, {
scrollHeight: 200,
clientHeight: 100,
offsetWidth: 320,
clientWidth: 305, // 15px scrollbar gutter
})
textarea.dispatchEvent(
new CustomEvent('lora-manager:autocomplete-value-changed', {
detail: { value: 'long content' },
})
)
await nextTick()
const wrapperEl = wrapper.find('.input-wrapper').element as HTMLElement
expect(wrapperEl.style.getPropertyValue('--lm-vscrollbar-width')).toBe('15px')
})
}) })
+78 -9
View File
@@ -1,12 +1,86 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
import { resolve } from 'path' import { dirname, resolve } from 'path'
// Specifiers that must stay external. The bundle is emitted to
// web/comfyui/vue-widgets/, and ComfyUI serves that directory's parent
// (web/comfyui) at /extensions/ComfyUI-Lora-Manager/, so one "../" from the
// bundle reaches web/comfyui modules and three "../../.." reach ComfyUI's
// own runtime scripts at runtime.
//
// scripts/app.js and scripts/api.js are intentionally NOT listed here: they
// are externalized by the keep-runtime-modules-external plugin below, which
// also rewrites the shallower "../../scripts/*" specifiers used by modules
// inlined from web/comfyui/ so every binding dedupes into a single import.
const EXTERNAL_SPECIFIERS = [
'../loras_widget.js',
'../autocomplete.js',
'../preview_tooltip.js'
]
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
cssInjectedByJsPlugin() // Inject CSS into JS for ComfyUI compatibility cssInjectedByJsPlugin(), // Inject CSS into JS for ComfyUI compatibility
// Keep shared runtime modules external instead of inlining them into
// the bundle. This guards against the inlined-shim bug class (the
// removed active-filters chip ended up writing settings to a dead
// in-memory store this way):
//
// 1. Modules under web/comfyui/ import the repo-root scripts/app.js
// TEST SHIM as "../../scripts/app.js" — a depth that resolves to
// the shim on the build filesystem but 404s at the bundle's
// runtime location. Rewrite to the canonical bundle-depth
// specifier so every app/api binding in the bundle is the REAL
// ComfyUI module.
// 2. web/comfyui/settings.js must never be duplicated into the
// bundle: it registers settings via a module-level side effect
// and owns module state. Externalize it to "../settings.js" so
// the bundle binds to the SAME vanilla module instance that the
// ComfyUI extension loader already loaded.
{
name: 'lora-manager:keep-runtime-modules-external',
enforce: 'pre',
resolveId(source, importer) {
const scriptsMatch = source.match(/^(\.\.\/)+scripts\/(app|api)\.js$/)
if (scriptsMatch) {
return { id: `../../../scripts/${scriptsMatch[2]}.js`, external: true }
}
if (
importer &&
/[\\/]web[\\/]comfyui[\\/]settings\.js$/.test(
resolve(dirname(importer), source)
)
) {
return { id: '../settings.js', external: true }
}
return null
},
},
// Warning twin of the rewrite above: importing web/comfyui/* from
// widget source inlines that module into the bundle, duplicating any
// module-level side effects/state it owns. The settings.js and
// scripts/app|api.js imports are made safe by the plugin above, but
// review any further such import deliberately.
{
name: 'lora-manager:warn-web-comfyui-imports',
enforce: 'pre',
resolveId(source, importer) {
if (
importer &&
/[\\/]vue-widgets[\\/]src[\\/]/.test(importer) &&
/[\\/]web[\\/]comfyui[\\/]/.test(source)
) {
this.warn(
`[vue-widgets] Inlining web/comfyui module "${source}" into the bundle. ` +
'settings.js and scripts/app|api.js imports are externalized by this config, ' +
'but the inlined copy still duplicates module-level side effects — verify that is intended.'
)
}
return null
},
},
], ],
resolve: { resolve: {
alias: { alias: {
@@ -20,13 +94,7 @@ export default defineConfig({
fileName: 'lora-manager-widgets' fileName: 'lora-manager-widgets'
}, },
rollupOptions: { rollupOptions: {
external: [ external: EXTERNAL_SPECIFIERS,
'../../../scripts/app.js',
'../../../scripts/api.js',
'../loras_widget.js',
'../autocomplete.js',
'../preview_tooltip.js'
],
output: { output: {
dir: '../web/comfyui/vue-widgets', dir: '../web/comfyui/vue-widgets',
entryFileNames: 'lora-manager-widgets.js', entryFileNames: 'lora-manager-widgets.js',
@@ -41,3 +109,4 @@ export default defineConfig({
'process.env.NODE_ENV': JSON.stringify('production') 'process.env.NODE_ENV': JSON.stringify('production')
} }
}) })
+1 -21
View File
@@ -175,42 +175,23 @@ const getPromptTagAutocompletePreference = (() => {
/** /**
* Persist a LoRA Manager setting through ComfyUI's setting API. * Persist a LoRA Manager setting through ComfyUI's setting API.
* Returns true when the setting was written successfully. * Returns true when the setting was written successfully.
*
* Every successful write broadcasts a "lora-manager:setting-toggled" window
* event (see SETTING_TOGGLED_EVENT_NAME) so widgets mirroring the setting
* (e.g. the active-filters indicator in the autocomplete text widget) stay in
* sync with slash-command / context-menu toggles.
*/ */
const SETTING_TOGGLED_EVENT_NAME = "lora-manager:setting-toggled";
const setLoraManagerSettingValue = async (settingId, value) => { const setLoraManagerSettingValue = async (settingId, value) => {
const settingManager = app?.extensionManager?.setting; const settingManager = app?.extensionManager?.setting;
if (settingManager && typeof settingManager.set === "function") { if (settingManager && typeof settingManager.set === "function") {
await settingManager.set(settingId, value); await settingManager.set(settingId, value);
_notifySettingToggled(settingId, value);
return true; return true;
} }
const setting = app?.ui?.settings?.settingsById?.[settingId]; const setting = app?.ui?.settings?.settingsById?.[settingId];
if (setting) { if (setting) {
app.ui.settings.setSettingValue(settingId, value); app.ui.settings.setSettingValue(settingId, value);
_notifySettingToggled(settingId, value);
return true; return true;
} }
return false; return false;
}; };
const _notifySettingToggled = (settingId, value) => {
try {
window.dispatchEvent(new CustomEvent(SETTING_TOGGLED_EVENT_NAME, {
detail: { settingId, value },
}));
} catch (error) {
// Best-effort notification; ignore non-browser environments
}
};
const getAutocompleteAppendCommaPreference = (() => { const getAutocompleteAppendCommaPreference = (() => {
let settingsUnavailableLogged = false; let settingsUnavailableLogged = false;
@@ -469,7 +450,7 @@ app.registerExtension({
name: "Search LoRA autocomplete within active filters", name: "Search LoRA autocomplete within active filters",
type: "boolean", type: "boolean",
defaultValue: LORA_ACTIVE_FILTERS_AUTOCOMPLETE_DEFAULT, defaultValue: LORA_ACTIVE_FILTERS_AUTOCOMPLETE_DEFAULT,
tooltip: "When enabled, LoRA autocomplete suggestions respect the active filters (folder/base model/tags) set in the LoRA Manager page. Commands /activefilters and /noactivefilters toggle this mode.", tooltip: "When enabled, LoRA autocomplete suggestions respect the active filters (folder/base model/tags) set in the LoRA Manager page. You can also toggle it by typing /activefilters or /noactivefilters in the LoRA field, or from the node's right-click menu.",
category: ["LoRA Manager", "Autocomplete", "LoRA Active Filters"], category: ["LoRA Manager", "Autocomplete", "LoRA Active Filters"],
}, },
{ {
@@ -617,7 +598,6 @@ app.registerExtension({
export { export {
PROMPT_TAG_AUTOCOMPLETE_SETTING_ID, PROMPT_TAG_AUTOCOMPLETE_SETTING_ID,
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID, LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID,
SETTING_TOGGLED_EVENT_NAME,
getWheelSensitivity, getWheelSensitivity,
getAutoPathCorrectionPreference, getAutoPathCorrectionPreference,
getAutocompleteAppendCommaPreference, getAutocompleteAppendCommaPreference,
+71 -234
View File
@@ -2118,14 +2118,14 @@ to { transform: rotate(360deg);
padding: 20px 0; padding: 20px 0;
} }
.autocomplete-text-widget[data-v-22743258] { .autocomplete-text-widget[data-v-793d67d2] {
background: transparent; background: transparent;
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
box-sizing: border-box; box-sizing: border-box;
} }
.input-wrapper[data-v-22743258] { .input-wrapper[data-v-793d67d2] {
position: relative; position: relative;
flex: 1; flex: 1;
display: flex; display: flex;
@@ -2133,7 +2133,7 @@ to { transform: rotate(360deg);
} }
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */ /* Canvas mode styles (default) - matches built-in comfy-multiline-input */
.text-input[data-v-22743258] { .text-input[data-v-793d67d2] {
flex: 1; flex: 1;
width: 100%; width: 100%;
background-color: var(--comfy-input-bg, #222); background-color: var(--comfy-input-bg, #222);
@@ -2152,7 +2152,7 @@ to { transform: rotate(360deg);
} }
/* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */ /* Vue DOM mode styles - matches built-in p-textarea in Vue DOM mode */
.text-input.vue-dom-mode[data-v-22743258] { .text-input.vue-dom-mode[data-v-793d67d2] {
background-color: var(--color-charcoal-400, #313235); background-color: var(--color-charcoal-400, #313235);
color: #fff; color: #fff;
padding: 8px 12px 30px 12px; /* Reserve bottom space for clear button */ padding: 8px 12px 30px 12px; /* Reserve bottom space for clear button */
@@ -2161,12 +2161,12 @@ to { transform: rotate(360deg);
font-size: 12px; font-size: 12px;
font-family: inherit; font-family: inherit;
} }
.text-input[data-v-22743258]:focus { .text-input[data-v-793d67d2]:focus {
outline: none; outline: none;
} }
/* Clear button styles */ /* Clear button styles */
.clear-button[data-v-22743258] { .clear-button[data-v-793d67d2] {
position: absolute; position: absolute;
right: calc(6px + var(--lm-vscrollbar-width, 0px)); right: calc(6px + var(--lm-vscrollbar-width, 0px));
bottom: 6px; /* Changed from top to bottom */ bottom: 6px; /* Changed from top to bottom */
@@ -2189,79 +2189,31 @@ to { transform: rotate(360deg);
} }
/* Show clear button when hovering over input wrapper */ /* Show clear button when hovering over input wrapper */
.input-wrapper:hover .clear-button[data-v-22743258] { .input-wrapper:hover .clear-button[data-v-793d67d2] {
opacity: 0.7; opacity: 0.7;
pointer-events: auto; pointer-events: auto;
} }
.clear-button[data-v-22743258]:hover { .clear-button[data-v-793d67d2]:hover {
opacity: 1; opacity: 1;
background: rgba(255, 100, 100, 0.8); background: rgba(255, 100, 100, 0.8);
} }
.clear-button svg[data-v-22743258] { .clear-button svg[data-v-793d67d2] {
width: 12px; width: 12px;
height: 12px; height: 12px;
} }
/* Active-filters search indicator (loras nodes only) */
.active-filters-toggle[data-v-22743258] {
position: absolute;
top: 3px;
right: calc(3px + var(--lm-vscrollbar-width, 0px));
width: 16px;
height: 16px;
padding: 2px;
margin: 0;
border: none;
border-radius: 4px;
background: rgba(128, 128, 128, 0.25);
color: rgba(255, 255, 255, 0.5);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
transition: opacity 0.2s ease, background-color 0.2s ease, color 0.2s ease;
z-index: 10;
}
.active-filters-toggle[data-v-22743258]:hover {
opacity: 1;
background: rgba(128, 128, 128, 0.45);
color: rgba(255, 255, 255, 0.85);
}
.active-filters-toggle.is-active[data-v-22743258] {
background: rgba(59, 130, 246, 0.35);
color: #7db8ff;
opacity: 1;
}
.active-filters-toggle svg[data-v-22743258] {
width: 11px;
height: 11px;
}
/* Vue DOM mode adjustments for the indicator */
.text-input.vue-dom-mode ~ .active-filters-toggle[data-v-22743258] {
top: 8px;
right: calc(8px + var(--lm-vscrollbar-width, 0px));
width: 20px;
height: 20px;
}
.text-input.vue-dom-mode ~ .active-filters-toggle svg[data-v-22743258] {
width: 13px;
height: 13px;
}
/* Vue DOM mode adjustments for clear button */ /* Vue DOM mode adjustments for clear button */
.text-input.vue-dom-mode ~ .clear-button[data-v-22743258] { .text-input.vue-dom-mode ~ .clear-button[data-v-793d67d2] {
right: calc(8px + var(--lm-vscrollbar-width, 0px)); right: calc(8px + var(--lm-vscrollbar-width, 0px));
bottom: 10px; /* Changed from top to bottom, adjusted for Vue DOM padding */ bottom: 10px; /* Changed from top to bottom, adjusted for Vue DOM padding */
width: 20px; width: 20px;
height: 20px; height: 20px;
background: rgba(107, 114, 128, 0.6); background: rgba(107, 114, 128, 0.6);
} }
.text-input.vue-dom-mode ~ .clear-button[data-v-22743258]:hover { .text-input.vue-dom-mode ~ .clear-button[data-v-793d67d2]:hover {
background: oklch(62% 0.18 25); background: oklch(62% 0.18 25);
} }
.text-input.vue-dom-mode ~ .clear-button svg[data-v-22743258] { .text-input.vue-dom-mode ~ .clear-button svg[data-v-793d67d2] {
width: 14px; width: 14px;
height: 14px; height: 14px;
} }
@@ -2529,8 +2481,9 @@ to { transform: rotate(360deg);
} }
})(); })();
var _a; var _a;
import { app as app$1 } from "../../../scripts/app.js"; import { app } from "../../../scripts/app.js";
import { api as api$1 } from "../../../scripts/api.js"; import { api } from "../../../scripts/api.js";
import "../settings.js";
/** /**
* @vue/shared v3.5.26 * @vue/shared v3.5.26
* (c) 2018-present Yuxi (Evan) You and Vue contributors * (c) 2018-present Yuxi (Evan) You and Vue contributors
@@ -11100,7 +11053,7 @@ const _sfc_main$o = /* @__PURE__ */ defineComponent({
const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$o, [["__scopeId", "data-v-8da8aa4b"]]); const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$o, [["__scopeId", "data-v-8da8aa4b"]]);
const _hoisted_1$k = { class: "section" }; const _hoisted_1$k = { class: "section" };
const _hoisted_2$j = { class: "section__header" }; const _hoisted_2$j = { class: "section__header" };
const _hoisted_3$h = { class: "section__content" }; const _hoisted_3$g = { class: "section__content" };
const _hoisted_4$f = { const _hoisted_4$f = {
key: 0, key: 0,
class: "section__placeholder" class: "section__placeholder"
@@ -11130,7 +11083,7 @@ const _sfc_main$n = /* @__PURE__ */ defineComponent({
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit")) onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit"))
}) })
]), ]),
createBaseVNode("div", _hoisted_3$h, [ createBaseVNode("div", _hoisted_3$g, [
__props.selected.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_4$f, " All models ")) : (openBlock(), createElementBlock("div", _hoisted_5$d, [ __props.selected.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_4$f, " All models ")) : (openBlock(), createElementBlock("div", _hoisted_5$d, [
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.selected, (name) => { (openBlock(true), createElementBlock(Fragment, null, renderList(__props.selected, (name) => {
return openBlock(), createBlock(FilterChip, { return openBlock(), createBlock(FilterChip, {
@@ -11149,7 +11102,7 @@ const _sfc_main$n = /* @__PURE__ */ defineComponent({
const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-12f059e2"]]); const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-12f059e2"]]);
const _hoisted_1$j = { class: "section" }; const _hoisted_1$j = { class: "section" };
const _hoisted_2$i = { class: "section__columns" }; const _hoisted_2$i = { class: "section__columns" };
const _hoisted_3$g = { class: "section__column" }; const _hoisted_3$f = { class: "section__column" };
const _hoisted_4$e = { class: "section__column-header" }; const _hoisted_4$e = { class: "section__column-header" };
const _hoisted_5$c = { class: "section__column-content" }; const _hoisted_5$c = { class: "section__column-content" };
const _hoisted_6$c = { const _hoisted_6$c = {
@@ -11185,7 +11138,7 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
createBaseVNode("span", { class: "section__title" }, "TAGS") createBaseVNode("span", { class: "section__title" }, "TAGS")
], -1)), ], -1)),
createBaseVNode("div", _hoisted_2$i, [ createBaseVNode("div", _hoisted_2$i, [
createBaseVNode("div", _hoisted_3$g, [ createBaseVNode("div", _hoisted_3$f, [
createBaseVNode("div", _hoisted_4$e, [ createBaseVNode("div", _hoisted_4$e, [
_cache[2] || (_cache[2] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)), _cache[2] || (_cache[2] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
createVNode(EditButton, { createVNode(EditButton, {
@@ -11231,7 +11184,7 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-b869b780"]]); const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-b869b780"]]);
const _hoisted_1$i = { class: "section" }; const _hoisted_1$i = { class: "section" };
const _hoisted_2$h = { class: "section__columns" }; const _hoisted_2$h = { class: "section__columns" };
const _hoisted_3$f = { class: "section__column" }; const _hoisted_3$e = { class: "section__column" };
const _hoisted_4$d = { class: "section__column-header" }; const _hoisted_4$d = { class: "section__column-header" };
const _hoisted_5$b = { class: "section__content" }; const _hoisted_5$b = { class: "section__content" };
const _hoisted_6$b = { const _hoisted_6$b = {
@@ -11279,7 +11232,7 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
createBaseVNode("span", { class: "section__title" }, "FOLDERS") createBaseVNode("span", { class: "section__title" }, "FOLDERS")
], -1)), ], -1)),
createBaseVNode("div", _hoisted_2$h, [ createBaseVNode("div", _hoisted_2$h, [
createBaseVNode("div", _hoisted_3$f, [ createBaseVNode("div", _hoisted_3$e, [
createBaseVNode("div", _hoisted_4$d, [ createBaseVNode("div", _hoisted_4$d, [
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)), _cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
createBaseVNode("button", { createBaseVNode("button", {
@@ -11347,7 +11300,7 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-af9caf84"]]); const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-af9caf84"]]);
const _hoisted_1$h = { class: "section" }; const _hoisted_1$h = { class: "section" };
const _hoisted_2$g = { class: "section__header" }; const _hoisted_2$g = { class: "section__header" };
const _hoisted_3$e = { class: "section__toggle" }; const _hoisted_3$d = { class: "section__toggle" };
const _hoisted_4$c = ["checked"]; const _hoisted_4$c = ["checked"];
const _hoisted_5$a = { class: "section__columns" }; const _hoisted_5$a = { class: "section__columns" };
const _hoisted_6$a = { class: "section__column" }; const _hoisted_6$a = { class: "section__column" };
@@ -11403,7 +11356,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
return openBlock(), createElementBlock("div", _hoisted_1$h, [ return openBlock(), createElementBlock("div", _hoisted_1$h, [
createBaseVNode("div", _hoisted_2$g, [ createBaseVNode("div", _hoisted_2$g, [
_cache[4] || (_cache[4] = createBaseVNode("span", { class: "section__title" }, "NAME PATTERNS", -1)), _cache[4] || (_cache[4] = createBaseVNode("span", { class: "section__title" }, "NAME PATTERNS", -1)),
createBaseVNode("label", _hoisted_3$e, [ createBaseVNode("label", _hoisted_3$d, [
createBaseVNode("input", { createBaseVNode("input", {
type: "checkbox", type: "checkbox",
checked: __props.useRegex, checked: __props.useRegex,
@@ -11487,7 +11440,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
const NamePatternsSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-9995b5ed"]]); const NamePatternsSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-9995b5ed"]]);
const _hoisted_1$g = { class: "section" }; const _hoisted_1$g = { class: "section" };
const _hoisted_2$f = { class: "section__toggles" }; const _hoisted_2$f = { class: "section__toggles" };
const _hoisted_3$d = { class: "toggle-item" }; const _hoisted_3$c = { class: "toggle-item" };
const _hoisted_4$b = ["aria-checked"]; const _hoisted_4$b = ["aria-checked"];
const _hoisted_5$9 = { class: "toggle-item" }; const _hoisted_5$9 = { class: "toggle-item" };
const _hoisted_6$9 = ["aria-checked"]; const _hoisted_6$9 = ["aria-checked"];
@@ -11505,7 +11458,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
createBaseVNode("span", { class: "section__title" }, "LICENSE") createBaseVNode("span", { class: "section__title" }, "LICENSE")
], -1)), ], -1)),
createBaseVNode("div", _hoisted_2$f, [ createBaseVNode("div", _hoisted_2$f, [
createBaseVNode("label", _hoisted_3$d, [ createBaseVNode("label", _hoisted_3$c, [
_cache[3] || (_cache[3] = createBaseVNode("span", { _cache[3] || (_cache[3] = createBaseVNode("span", {
class: "toggle-item__label", class: "toggle-item__label",
title: "Use the model without crediting the creator" title: "Use the model without crediting the creator"
@@ -11545,7 +11498,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-07ddd3df"]]); const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-07ddd3df"]]);
const _hoisted_1$f = { class: "preview" }; const _hoisted_1$f = { class: "preview" };
const _hoisted_2$e = { class: "preview__title" }; const _hoisted_2$e = { class: "preview__title" };
const _hoisted_3$c = ["disabled"]; const _hoisted_3$b = ["disabled"];
const _hoisted_4$a = { const _hoisted_4$a = {
key: 0, key: 0,
class: "preview__tooltip" class: "preview__tooltip"
@@ -11604,7 +11557,7 @@ const _sfc_main$i = /* @__PURE__ */ defineComponent({
d: "M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z" d: "M8 3c-1.552 0-2.94.707-3.857 1.818a.5.5 0 1 1-.771-.636A6.002 6.002 0 0 1 13.917 7H12.9A5.002 5.002 0 0 0 8 3zM3.1 9a5.002 5.002 0 0 0 8.757 2.182.5.5 0 1 1 .771.636A6.002 6.002 0 0 1 2.083 9H3.1z"
}) })
], -1) ], -1)
])], 10, _hoisted_3$c) ])], 10, _hoisted_3$b)
], 32), ], 32),
createVNode(Transition, { name: "tooltip" }, { createVNode(Transition, { name: "tooltip" }, {
default: withCtx(() => [ default: withCtx(() => [
@@ -11716,7 +11669,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-83235a00"]]); const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-83235a00"]]);
const _hoisted_1$d = { class: "lora-pool-modal__header" }; const _hoisted_1$d = { class: "lora-pool-modal__header" };
const _hoisted_2$c = { class: "lora-pool-modal__title-container" }; const _hoisted_2$c = { class: "lora-pool-modal__title-container" };
const _hoisted_3$b = { class: "lora-pool-modal__title" }; const _hoisted_3$a = { class: "lora-pool-modal__title" };
const _hoisted_4$9 = { const _hoisted_4$9 = {
key: 0, key: 0,
class: "lora-pool-modal__subtitle" class: "lora-pool-modal__subtitle"
@@ -11776,7 +11729,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
}, [ }, [
createBaseVNode("div", _hoisted_1$d, [ createBaseVNode("div", _hoisted_1$d, [
createBaseVNode("div", _hoisted_2$c, [ createBaseVNode("div", _hoisted_2$c, [
createBaseVNode("h3", _hoisted_3$b, toDisplayString(__props.title), 1), createBaseVNode("h3", _hoisted_3$a, toDisplayString(__props.title), 1),
__props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_4$9, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true) __props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_4$9, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)
]), ]),
createBaseVNode("button", { createBaseVNode("button", {
@@ -11804,7 +11757,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
const ModalWrapper = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-7b4de03d"]]); const ModalWrapper = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-7b4de03d"]]);
const _hoisted_1$c = { class: "search-container" }; const _hoisted_1$c = { class: "search-container" };
const _hoisted_2$b = { class: "model-list" }; const _hoisted_2$b = { class: "model-list" };
const _hoisted_3$a = ["checked", "onChange"]; const _hoisted_3$9 = ["checked", "onChange"];
const _hoisted_4$8 = { class: "model-checkbox-visual" }; const _hoisted_4$8 = { class: "model-checkbox-visual" };
const _hoisted_5$6 = { const _hoisted_5$6 = {
key: 0, key: 0,
@@ -11914,7 +11867,7 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
checked: isSelected(model.name), checked: isSelected(model.name),
onChange: ($event) => toggleModel(model.name), onChange: ($event) => toggleModel(model.name),
class: "model-checkbox" class: "model-checkbox"
}, null, 40, _hoisted_3$a), }, null, 40, _hoisted_3$9),
createBaseVNode("span", _hoisted_4$8, [ createBaseVNode("span", _hoisted_4$8, [
isSelected(model.name) ? (openBlock(), createElementBlock("svg", _hoisted_5$6, [..._cache[4] || (_cache[4] = [ isSelected(model.name) ? (openBlock(), createElementBlock("svg", _hoisted_5$6, [..._cache[4] || (_cache[4] = [
createBaseVNode("path", { d: "M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" }, null, -1) createBaseVNode("path", { d: "M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" }, null, -1)
@@ -11935,7 +11888,7 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
const BaseModelModal = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-e02ca44a"]]); const BaseModelModal = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-e02ca44a"]]);
const _hoisted_1$b = { class: "search-container" }; const _hoisted_1$b = { class: "search-container" };
const _hoisted_2$a = ["onClick"]; const _hoisted_2$a = ["onClick"];
const _hoisted_3$9 = { const _hoisted_3$8 = {
key: 0, key: 0,
class: "no-results" class: "no-results"
}; };
@@ -12079,7 +12032,7 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
onClick: ($event) => toggleTag(tag.tag) onClick: ($event) => toggleTag(tag.tag)
}, toDisplayString(tag.tag), 11, _hoisted_2$a); }, toDisplayString(tag.tag), 11, _hoisted_2$a);
}), 128)), }), 128)),
visibleTags.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_3$9, " No tags found ")) : createCommentVNode("", true), visibleTags.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_3$8, " No tags found ")) : createCommentVNode("", true),
hasMoreTags.value ? (openBlock(), createElementBlock("div", _hoisted_4$7, " Scroll to load more... ")) : createCommentVNode("", true) hasMoreTags.value ? (openBlock(), createElementBlock("div", _hoisted_4$7, " Scroll to load more... ")) : createCommentVNode("", true)
], 544) ], 544)
]), ]),
@@ -12094,7 +12047,7 @@ const _hoisted_2$9 = {
key: 1, key: 1,
class: "tree-node__toggle-spacer" class: "tree-node__toggle-spacer"
}; };
const _hoisted_3$8 = { class: "tree-node__checkbox-label" }; const _hoisted_3$7 = { class: "tree-node__checkbox-label" };
const _hoisted_4$6 = ["checked"]; const _hoisted_4$6 = ["checked"];
const _hoisted_5$5 = { const _hoisted_5$5 = {
key: 0, key: 0,
@@ -12155,7 +12108,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
createBaseVNode("path", { d: "M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" }, null, -1) createBaseVNode("path", { d: "M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" }, null, -1)
])], 2)) ])], 2))
])) : (openBlock(), createElementBlock("span", _hoisted_2$9)), ])) : (openBlock(), createElementBlock("span", _hoisted_2$9)),
createBaseVNode("label", _hoisted_3$8, [ createBaseVNode("label", _hoisted_3$7, [
createBaseVNode("input", { createBaseVNode("input", {
type: "checkbox", type: "checkbox",
class: "tree-node__checkbox", class: "tree-node__checkbox",
@@ -12201,7 +12154,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
const FolderTreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-90187dd4"]]); const FolderTreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-90187dd4"]]);
const _hoisted_1$9 = { class: "search-container" }; const _hoisted_1$9 = { class: "search-container" };
const _hoisted_2$8 = { class: "folder-tree" }; const _hoisted_2$8 = { class: "folder-tree" };
const _hoisted_3$7 = { const _hoisted_3$6 = {
key: 1, key: 1,
class: "no-results" class: "no-results"
}; };
@@ -12300,7 +12253,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
onToggleExpand: toggleExpand, onToggleExpand: toggleExpand,
onToggleSelect: toggleSelect onToggleSelect: toggleSelect
}, null, 8, ["node", "selected", "expanded", "variant"]); }, null, 8, ["node", "selected", "expanded", "variant"]);
}), 128)) : (openBlock(), createElementBlock("div", _hoisted_3$7, " No folders found ")) }), 128)) : (openBlock(), createElementBlock("div", _hoisted_3$6, " No folders found "))
]) ])
]), ]),
_: 1 _: 1
@@ -12706,7 +12659,7 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-ed73eab5"]]); const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-ed73eab5"]]);
const _hoisted_1$8 = { class: "last-used-preview" }; const _hoisted_1$8 = { class: "last-used-preview" };
const _hoisted_2$7 = { class: "last-used-preview__content" }; const _hoisted_2$7 = { class: "last-used-preview__content" };
const _hoisted_3$6 = ["src", "onError"]; const _hoisted_3$5 = ["src", "onError"];
const _hoisted_4$5 = { const _hoisted_4$5 = {
key: 1, key: 1,
class: "last-used-preview__thumb last-used-preview__thumb--placeholder" class: "last-used-preview__thumb last-used-preview__thumb--placeholder"
@@ -12758,7 +12711,7 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
src: previewUrls.value[lora.name], src: previewUrls.value[lora.name],
class: "last-used-preview__thumb", class: "last-used-preview__thumb",
onError: ($event) => onImageError(lora.name) onError: ($event) => onImageError(lora.name)
}, null, 40, _hoisted_3$6)) : (openBlock(), createElementBlock("div", _hoisted_4$5, [..._cache[0] || (_cache[0] = [ }, null, 40, _hoisted_3$5)) : (openBlock(), createElementBlock("div", _hoisted_4$5, [..._cache[0] || (_cache[0] = [
createBaseVNode("svg", { createBaseVNode("svg", {
viewBox: "0 0 16 16", viewBox: "0 0 16 16",
fill: "currentColor" fill: "currentColor"
@@ -13199,7 +13152,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
const DualRangeSlider = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-e0c8dc9f"]]); const DualRangeSlider = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-e0c8dc9f"]]);
const _hoisted_1$5 = { class: "randomizer-settings" }; const _hoisted_1$5 = { class: "randomizer-settings" };
const _hoisted_2$5 = { class: "setting-section" }; const _hoisted_2$5 = { class: "setting-section" };
const _hoisted_3$5 = { class: "count-mode-tabs" }; const _hoisted_3$4 = { class: "count-mode-tabs" };
const _hoisted_4$4 = ["checked"]; const _hoisted_4$4 = ["checked"];
const _hoisted_5$3 = ["checked"]; const _hoisted_5$3 = ["checked"];
const _hoisted_6$3 = { class: "slider-container" }; const _hoisted_6$3 = { class: "slider-container" };
@@ -13264,7 +13217,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
], -1)), ], -1)),
createBaseVNode("div", _hoisted_2$5, [ createBaseVNode("div", _hoisted_2$5, [
_cache[20] || (_cache[20] = createBaseVNode("label", { class: "setting-label" }, "LoRA Count", -1)), _cache[20] || (_cache[20] = createBaseVNode("label", { class: "setting-label" }, "LoRA Count", -1)),
createBaseVNode("div", _hoisted_3$5, [ createBaseVNode("div", _hoisted_3$4, [
createBaseVNode("label", { createBaseVNode("label", {
class: normalizeClass(["count-mode-tab", { active: __props.countMode === "fixed" }]) class: normalizeClass(["count-mode-tab", { active: __props.countMode === "fixed" }])
}, [ }, [
@@ -13895,7 +13848,7 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
const LoraRandomizerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-ca6e8cec"]]); const LoraRandomizerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-ca6e8cec"]]);
const _hoisted_1$4 = { class: "cycler-settings" }; const _hoisted_1$4 = { class: "cycler-settings" };
const _hoisted_2$4 = { class: "setting-section progress-section" }; const _hoisted_2$4 = { class: "setting-section progress-section" };
const _hoisted_3$4 = { class: "progress-label" }; const _hoisted_3$3 = { class: "progress-label" };
const _hoisted_4$3 = ["title"]; const _hoisted_4$3 = ["title"];
const _hoisted_5$2 = { class: "progress-counter" }; const _hoisted_5$2 = { class: "progress-counter" };
const _hoisted_6$2 = { class: "progress-index" }; const _hoisted_6$2 = { class: "progress-index" };
@@ -14020,7 +13973,7 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
class: normalizeClass(["progress-info", { disabled: __props.isPauseDisabled }]), class: normalizeClass(["progress-info", { disabled: __props.isPauseDisabled }]),
onClick: handleOpenSelector onClick: handleOpenSelector
}, [ }, [
createBaseVNode("span", _hoisted_3$4, toDisplayString(__props.isWorkflowExecuting ? "Using LoRA:" : "Next LoRA:"), 1), createBaseVNode("span", _hoisted_3$3, toDisplayString(__props.isWorkflowExecuting ? "Using LoRA:" : "Next LoRA:"), 1),
createBaseVNode("span", { createBaseVNode("span", {
class: normalizeClass(["progress-name clickable", { disabled: __props.isPauseDisabled, "no-lora": __props.isNoLora }]), class: normalizeClass(["progress-name clickable", { disabled: __props.isPauseDisabled, "no-lora": __props.isNoLora }]),
title: __props.currentLoraFilename title: __props.currentLoraFilename
@@ -14220,7 +14173,7 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-f0663be4"]]); const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-f0663be4"]]);
const _hoisted_1$3 = { class: "search-container" }; const _hoisted_1$3 = { class: "search-container" };
const _hoisted_2$3 = { class: "lora-list" }; const _hoisted_2$3 = { class: "lora-list" };
const _hoisted_3$3 = ["onMouseenter", "onClick"]; const _hoisted_3$2 = ["onMouseenter", "onClick"];
const _hoisted_4$2 = { class: "lora-index" }; const _hoisted_4$2 = { class: "lora-index" };
const _hoisted_5$1 = ["title"]; const _hoisted_5$1 = ["title"];
const _hoisted_6$1 = { const _hoisted_6$1 = {
@@ -14401,7 +14354,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
title: item.lora.file_name title: item.lora.file_name
}, toDisplayString(item.lora.file_name), 9, _hoisted_5$1), }, toDisplayString(item.lora.file_name), 9, _hoisted_5$1),
__props.currentIndex === item.index ? (openBlock(), createElementBlock("span", _hoisted_6$1, "Current")) : createCommentVNode("", true) __props.currentIndex === item.index ? (openBlock(), createElementBlock("span", _hoisted_6$1, "Current")) : createCommentVNode("", true)
], 42, _hoisted_3$3); ], 42, _hoisted_3$2);
}), 128)), }), 128)),
filteredList.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_7$1, " No LoRAs found ")) : createCommentVNode("", true) filteredList.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_7$1, " No LoRAs found ")) : createCommentVNode("", true)
]) ])
@@ -15017,7 +14970,7 @@ const _hoisted_2$2 = {
class: "json-content", class: "json-content",
ref: "contentRef" ref: "contentRef"
}; };
const _hoisted_3$2 = ["innerHTML"]; const _hoisted_3$1 = ["innerHTML"];
const _hoisted_4$1 = { const _hoisted_4$1 = {
key: 1, key: 1,
class: "placeholder" class: "placeholder"
@@ -15112,7 +15065,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
hasMetadata.value ? (openBlock(), createElementBlock("pre", { hasMetadata.value ? (openBlock(), createElementBlock("pre", {
key: 0, key: 0,
innerHTML: highlightedJson.value innerHTML: highlightedJson.value
}, null, 8, _hoisted_3$2)) : (openBlock(), createElementBlock("div", _hoisted_4$1, "No metadata available")) }, null, 8, _hoisted_3$1)) : (openBlock(), createElementBlock("div", _hoisted_4$1, "No metadata available"))
], 512) ], 512)
]); ]);
}; };
@@ -15184,71 +15137,8 @@ function useAutocomplete(textareaRef, modelType = "loras", options = {}) {
refreshCaretHelper refreshCaretHelper
}; };
} }
const settingsStore = /* @__PURE__ */ new Map();
const app = {
extensionManager: {
setting: {
get: (id) => settingsStore.has(id) ? settingsStore.get(id) : void 0,
set: async (id, value) => {
settingsStore.set(id, value);
}
}
}
};
const LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID = "loramanager.lora_active_filters_autocomplete";
const LORA_ACTIVE_FILTERS_AUTOCOMPLETE_DEFAULT = false;
const SETTING_TOGGLED_EVENT_NAME = "lora-manager:setting-toggled";
const setLoraManagerSettingValue = async (settingId, value) => {
var _a2, _b, _c, _d;
const settingManager = (_a2 = app == null ? void 0 : app.extensionManager) == null ? void 0 : _a2.setting;
if (settingManager && typeof settingManager.set === "function") {
await settingManager.set(settingId, value);
_notifySettingToggled(settingId, value);
return true;
}
const setting = (_d = (_c = (_b = app == null ? void 0 : app.ui) == null ? void 0 : _b.settings) == null ? void 0 : _c.settingsById) == null ? void 0 : _d[settingId];
if (setting) {
app.ui.settings.setSettingValue(settingId, value);
_notifySettingToggled(settingId, value);
return true;
}
return false;
};
const _notifySettingToggled = (settingId, value) => {
try {
window.dispatchEvent(new CustomEvent(SETTING_TOGGLED_EVENT_NAME, {
detail: { settingId, value }
}));
} catch (error) {
}
};
const getLoraActiveFiltersAutocompletePreference = /* @__PURE__ */ (() => {
let settingsUnavailableLogged = false;
return () => {
var _a2;
const settingManager = (_a2 = app == null ? void 0 : app.extensionManager) == null ? void 0 : _a2.setting;
if (!settingManager || typeof settingManager.get !== "function") {
if (!settingsUnavailableLogged) {
console.warn("LoRA Manager: settings API unavailable, using default lora active filters autocomplete setting.");
settingsUnavailableLogged = true;
}
return LORA_ACTIVE_FILTERS_AUTOCOMPLETE_DEFAULT;
}
try {
const value = settingManager.get(LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID);
return value ?? LORA_ACTIVE_FILTERS_AUTOCOMPLETE_DEFAULT;
} catch (error) {
if (!settingsUnavailableLogged) {
console.warn("LoRA Manager: unable to read lora active filters autocomplete setting, using default.", error);
settingsUnavailableLogged = true;
}
return LORA_ACTIVE_FILTERS_AUTOCOMPLETE_DEFAULT;
}
};
})();
const _hoisted_1$1 = { class: "autocomplete-text-widget" }; const _hoisted_1$1 = { class: "autocomplete-text-widget" };
const _hoisted_2$1 = ["placeholder", "spellcheck"]; const _hoisted_2$1 = ["placeholder", "spellcheck"];
const _hoisted_3$1 = ["title"];
const _sfc_main$1 = /* @__PURE__ */ defineComponent({ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
__name: "AutocompleteTextWidget", __name: "AutocompleteTextWidget",
props: { props: {
@@ -15297,37 +15187,6 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
}; };
const hasText = ref(false); const hasText = ref(false);
const showClearButton = computed(() => hasText.value); const showClearButton = computed(() => hasText.value);
const isLorasMode = (props.modelType ?? "loras") === "loras";
const activeFiltersEnabled = ref(false);
const refreshActiveFiltersState = () => {
if (isLorasMode) {
activeFiltersEnabled.value = getLoraActiveFiltersAutocompletePreference();
}
};
const onSettingToggled = (event) => {
const detail = event.detail;
if ((detail == null ? void 0 : detail.settingId) === LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID) {
activeFiltersEnabled.value = detail.value === true;
}
};
const activeFiltersToggleTitle = computed(
() => activeFiltersEnabled.value ? "Active Filters Search is ON: suggestions respect the LoRA Manager page filters. Click to disable, or type /noactivefilters." : "Active Filters Search is OFF: suggestions search the full library. Click to enable, or type /activefilters."
);
const toggleActiveFiltersSearch = async () => {
const newValue = !activeFiltersEnabled.value;
try {
const success = await setLoraManagerSettingValue(
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID,
newValue
);
if (!success) {
throw new Error("settings API unavailable");
}
activeFiltersEnabled.value = newValue;
} catch (error) {
console.error("[Lora Manager] Failed to toggle active filters search:", error);
}
};
useAutocomplete( useAutocomplete(
textareaRef, textareaRef,
props.modelType ?? "loras", props.modelType ?? "loras",
@@ -15380,12 +15239,14 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
} }
}; };
const onExternalValueChange = () => { const onExternalValueChange = () => {
updateVScrollbarWidth();
updateHasTextState(); updateHasTextState();
}; };
const setupWidgetOnSetValue = () => { const setupWidgetOnSetValue = () => {
if (props.widget) { if (props.widget) {
props.widget.onSetValue = (value) => { props.widget.onSetValue = (value) => {
hasText.value = value.length > 0; hasText.value = value.length > 0;
updateVScrollbarWidth();
}; };
} }
}; };
@@ -15435,8 +15296,6 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
props.widget.callback(textareaRef.value.value); props.widget.callback(textareaRef.value.value);
} }
setupWidgetOnSetValue(); setupWidgetOnSetValue();
refreshActiveFiltersState();
window.addEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled);
updateVScrollbarWidth(); updateVScrollbarWidth();
observeScrollbarWidth(); observeScrollbarWidth();
document.addEventListener("lora-manager:vue-mode-change", onModeChange); document.addEventListener("lora-manager:vue-mode-change", onModeChange);
@@ -15456,7 +15315,6 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
props.widget.onSetValue = void 0; props.widget.onSetValue = void 0;
} }
document.removeEventListener("lora-manager:vue-mode-change", onModeChange); document.removeEventListener("lora-manager:vue-mode-change", onModeChange);
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled);
}); });
return (_ctx, _cache) => { return (_ctx, _cache) => {
return openBlock(), createElementBlock("div", _hoisted_1$1, [ return openBlock(), createElementBlock("div", _hoisted_1$1, [
@@ -15503,29 +15361,13 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
y2: "18" y2: "18"
}) })
], -1) ], -1)
])])) : createCommentVNode("", true), ])])) : createCommentVNode("", true)
isLorasMode ? (openBlock(), createElementBlock("button", {
key: 1,
type: "button",
class: normalizeClass(["active-filters-toggle", { "is-active": activeFiltersEnabled.value }]),
title: activeFiltersToggleTitle.value,
onClick: toggleActiveFiltersSearch
}, [..._cache[1] || (_cache[1] = [
createBaseVNode("svg", {
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2"
}, [
createBaseVNode("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" })
], -1)
])], 10, _hoisted_3$1)) : createCommentVNode("", true)
], 4) ], 4)
]); ]);
}; };
} }
}); });
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-22743258"]]); const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-793d67d2"]]);
const _hoisted_1 = { class: "lora-info-tabs" }; const _hoisted_1 = { class: "lora-info-tabs" };
const _hoisted_2 = { class: "tab-content notes-tab" }; const _hoisted_2 = { class: "tab-content notes-tab" };
const _hoisted_3 = { class: "info-field" }; const _hoisted_3 = { class: "info-field" };
@@ -15942,11 +15784,6 @@ function createModeChangeCallback(node, updateDownstreamLoaders2, nodeSpecificCa
updateDownstreamLoaders2(node); updateDownstreamLoaders2(node);
}; };
} }
const api = {
fetchApi: (...args) => fetch(...args),
addEventListener: (eventName, handler) => document.addEventListener(eventName, handler),
removeEventListener: (eventName, handler) => document.removeEventListener(eventName, handler)
};
let _loraSyntaxFormatCache = null; let _loraSyntaxFormatCache = null;
let _loraSyntaxFormatRefreshPromise = null; let _loraSyntaxFormatRefreshPromise = null;
async function _fetchLoraSyntaxFormat() { async function _fetchLoraSyntaxFormat() {
@@ -16303,8 +16140,8 @@ const AUTOCOMPLETE_TEXT_MIN_WIDTH_DEFAULT = 400;
const AUTOCOMPLETE_TEXT_MIN_HEIGHT_DEFAULT = 300; const AUTOCOMPLETE_TEXT_MIN_HEIGHT_DEFAULT = 300;
const AUTOCOMPLETE_METADATA_VERSION = 1; const AUTOCOMPLETE_METADATA_VERSION = 1;
const LORA_MANAGER_WIDGET_IDS_PROPERTY = "__lm_widget_ids"; const LORA_MANAGER_WIDGET_IDS_PROPERTY = "__lm_widget_ids";
const originalGraphToPrompt = app$1.graphToPrompt.bind(app$1); const originalGraphToPrompt = app.graphToPrompt.bind(app);
app$1.graphToPrompt = async (...args) => { app.graphToPrompt = async (...args) => {
const result = await originalGraphToPrompt(...args); const result = await originalGraphToPrompt(...args);
stripAutocompleteMetadataFromPromptResult(result); stripAutocompleteMetadataFromPromptResult(result);
return result; return result;
@@ -16313,7 +16150,7 @@ function forwardMiddleMouseToCanvas(container) {
if (!container) return; if (!container) return;
container.addEventListener("pointerdown", (event) => { container.addEventListener("pointerdown", (event) => {
if (event.button === 1) { if (event.button === 1) {
const canvas = app$1.canvas; const canvas = app.canvas;
if (canvas && typeof canvas.processMouseDown === "function") { if (canvas && typeof canvas.processMouseDown === "function") {
canvas.processMouseDown(event); canvas.processMouseDown(event);
} }
@@ -16321,7 +16158,7 @@ function forwardMiddleMouseToCanvas(container) {
}); });
container.addEventListener("pointermove", (event) => { container.addEventListener("pointermove", (event) => {
if ((event.buttons & 4) === 4) { if ((event.buttons & 4) === 4) {
const canvas = app$1.canvas; const canvas = app.canvas;
if (canvas && typeof canvas.processMouseMove === "function") { if (canvas && typeof canvas.processMouseMove === "function") {
canvas.processMouseMove(event); canvas.processMouseMove(event);
} }
@@ -16329,7 +16166,7 @@ function forwardMiddleMouseToCanvas(container) {
}); });
container.addEventListener("pointerup", (event) => { container.addEventListener("pointerup", (event) => {
if (event.button === 1) { if (event.button === 1) {
const canvas = app$1.canvas; const canvas = app.canvas;
if (canvas && typeof canvas.processMouseUp === "function") { if (canvas && typeof canvas.processMouseUp === "function") {
canvas.processMouseUp(event); canvas.processMouseUp(event);
} }
@@ -16447,7 +16284,7 @@ function createLoraRandomizerWidget(node) {
const vueApp = createApp(LoraRandomizerWidget, { const vueApp = createApp(LoraRandomizerWidget, {
widget, widget,
node, node,
api: api$1 api
}); });
vueApp.use(PrimeVue, { vueApp.use(PrimeVue, {
unstyled: true, unstyled: true,
@@ -16522,7 +16359,7 @@ function createLoraCyclerWidget(node) {
const vueApp = createApp(LoraCyclerWidget, { const vueApp = createApp(LoraCyclerWidget, {
widget, widget,
node, node,
api: api$1 api
}); });
vueApp.use(PrimeVue, { vueApp.use(PrimeVue, {
unstyled: true, unstyled: true,
@@ -16710,13 +16547,13 @@ function applyAutocompleteTextLayoutFix(widget, _container, isVueMode) {
} }
const initVueDomModeListener = () => { const initVueDomModeListener = () => {
var _a2, _b; var _a2, _b;
if ((_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.addEventListener) { if ((_b = (_a2 = app.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.addEventListener) {
app$1.ui.settings.addEventListener("Comfy.VueNodes.Enabled.change", () => { app.ui.settings.addEventListener("Comfy.VueNodes.Enabled.change", () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
var _a3, _b2, _c, _d, _e2, _f; var _a3, _b2, _c, _d, _e2, _f;
const isVueDomMode = ((_c = (_b2 = (_a3 = app$1.ui) == null ? void 0 : _a3.settings) == null ? void 0 : _b2.getSettingValue) == null ? void 0 : _c.call(_b2, "Comfy.VueNodes.Enabled")) ?? false; const isVueDomMode = ((_c = (_b2 = (_a3 = app.ui) == null ? void 0 : _a3.settings) == null ? void 0 : _b2.getSettingValue) == null ? void 0 : _c.call(_b2, "Comfy.VueNodes.Enabled")) ?? false;
if ((_d = app$1.graph) == null ? void 0 : _d.nodes) { if ((_d = app.graph) == null ? void 0 : _d.nodes) {
for (const node of app$1.graph.nodes) { for (const node of app.graph.nodes) {
const textWidget = (_e2 = node.widgets) == null ? void 0 : _e2.find( const textWidget = (_e2 = node.widgets) == null ? void 0 : _e2.find(
(w2) => w2.type === "AUTOCOMPLETE_TEXT_LORAS" (w2) => w2.type === "AUTOCOMPLETE_TEXT_LORAS"
); );
@@ -16731,7 +16568,7 @@ const initVueDomModeListener = () => {
const grid = nodeEl.querySelector('[data-testid="node-widgets"]'); const grid = nodeEl.querySelector('[data-testid="node-widgets"]');
if (!grid) continue; if (!grid) continue;
const nodeId = nodeEl.getAttribute("data-node-id"); const nodeId = nodeEl.getAttribute("data-node-id");
const node = (_a4 = app$1.graph) == null ? void 0 : _a4.getNodeById(nodeId); const node = (_a4 = app.graph) == null ? void 0 : _a4.getNodeById(nodeId);
if (!node) continue; if (!node) continue;
const rows = []; const rows = [];
let needsFix = false; let needsFix = false;
@@ -16752,7 +16589,7 @@ const initVueDomModeListener = () => {
} }
} }
}); });
(_f = app$1.canvas) == null ? void 0 : _f.setDirty(true, true); (_f = app.canvas) == null ? void 0 : _f.setDirty(true, true);
document.dispatchEvent(new CustomEvent("lora-manager:vue-mode-change", { document.dispatchEvent(new CustomEvent("lora-manager:vue-mode-change", {
detail: { isVueDomMode } detail: { isVueDomMode }
})); }));
@@ -16760,12 +16597,12 @@ const initVueDomModeListener = () => {
}); });
} }
}; };
if ((_a = app$1.ui) == null ? void 0 : _a.settings) { if ((_a = app.ui) == null ? void 0 : _a.settings) {
initVueDomModeListener(); initVueDomModeListener();
} else { } else {
const checkAppReady = setInterval(() => { const checkAppReady = setInterval(() => {
var _a2; var _a2;
if ((_a2 = app$1.ui) == null ? void 0 : _a2.settings) { if ((_a2 = app.ui) == null ? void 0 : _a2.settings) {
initVueDomModeListener(); initVueDomModeListener();
clearInterval(checkAppReady); clearInterval(checkAppReady);
} }
@@ -16804,8 +16641,8 @@ function createLoraInfoWidget(node) {
const vueApp = createApp(LoraInfoWidget, { const vueApp = createApp(LoraInfoWidget, {
widget, widget,
node, node,
api: api$1, api,
app: app$1, app,
isVueMode: typeof LiteGraph !== "undefined" && LiteGraph.vueNodesMode isVueMode: typeof LiteGraph !== "undefined" && LiteGraph.vueNodesMode
}); });
vueApp.use(PrimeVue, { vueApp.use(PrimeVue, {
@@ -16900,7 +16737,7 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
} }
); );
widget.metadataWidget = metadataWidget; widget.metadataWidget = metadataWidget;
const spellcheck = ((_c = (_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.getSettingValue) == null ? void 0 : _c.call(_b, "Comfy.TextareaWidget.Spellcheck")) ?? false; const spellcheck = ((_c = (_b = (_a2 = app.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.getSettingValue) == null ? void 0 : _c.call(_b, "Comfy.TextareaWidget.Spellcheck")) ?? false;
const maxHeight = modelType === "loras" ? AUTOCOMPLETE_TEXT_WIDGET_MAX_HEIGHT : void 0; const maxHeight = modelType === "loras" ? AUTOCOMPLETE_TEXT_WIDGET_MAX_HEIGHT : void 0;
const vueApp = createApp(AutocompleteTextWidget, { const vueApp = createApp(AutocompleteTextWidget, {
widget, widget,
@@ -16938,7 +16775,7 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
const minHeight = modelType === "loras" ? void 0 : AUTOCOMPLETE_TEXT_MIN_HEIGHT_DEFAULT; const minHeight = modelType === "loras" ? void 0 : AUTOCOMPLETE_TEXT_MIN_HEIGHT_DEFAULT;
return { widget, minWidth, minHeight }; return { widget, minWidth, minHeight };
} }
app$1.registerExtension({ app.registerExtension({
name: "LoraManager.VueWidgets", name: "LoraManager.VueWidgets",
getCustomWidgets() { getCustomWidgets() {
return { return {
File diff suppressed because one or more lines are too long