mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed7cf418b4 |
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "Nach Modell gruppieren",
|
||||
"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",
|
||||
"displayDensityOptions": {
|
||||
"default": "Standard",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"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.",
|
||||
"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",
|
||||
"displayDensityOptions": {
|
||||
"default": "Default",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"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.",
|
||||
"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",
|
||||
"displayDensityOptions": {
|
||||
"default": "Predeterminado",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"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.",
|
||||
"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",
|
||||
"displayDensityOptions": {
|
||||
"default": "Par défaut",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "קיבוץ לפי מודל",
|
||||
"groupByModelHelp": "כאשר מופעל, רק הגרסה העדכנית ביותר של כל מודל CivitAI מוצגת ככרטיס בודד. גרסאות ישנות יותר מוסתרות.",
|
||||
"stickyControls": "השארת סרגל הפעולות גלוי",
|
||||
"stickyControlsHelp": "כאשר מופעל, סרגל הפעולות (רענון, הורדה וכו') נשאר מוצמד לחלק העליון בעת גלילה, יחד עם ניווט פירורי הלחם.",
|
||||
"displayDensity": "צפיפות תצוגה",
|
||||
"displayDensityOptions": {
|
||||
"default": "ברירת מחדל",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "モデルでグループ化",
|
||||
"groupByModelHelp": "有効にすると、各CivitAIモデルの最新バージョンのみが1枚のカードとして表示され、古いバージョンは非表示になります。",
|
||||
"stickyControls": "アクションバーを常に表示",
|
||||
"stickyControlsHelp": "有効にすると、アクションバー(更新、ダウンロードなど)がスクロール時にパンくずナビゲーションと一緒に画面上部に固定されます。",
|
||||
"displayDensity": "表示密度",
|
||||
"displayDensityOptions": {
|
||||
"default": "デフォルト",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "모델별 그룹화",
|
||||
"groupByModelHelp": "활성화하면 각 CivitAI 모델의 최신 버전만 단일 카드로 표시되며, 이전 버전은 숨겨집니다.",
|
||||
"stickyControls": "작업 표시줄 항상 표시",
|
||||
"stickyControlsHelp": "활성화하면 작업 표시줄(새로고침, 다운로드 등)이 스크롤 시 브레드크럼 내비게이션과 함께 상단에 고정됩니다.",
|
||||
"displayDensity": "표시 밀도",
|
||||
"displayDensityOptions": {
|
||||
"default": "기본",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "Группировать по модели",
|
||||
"groupByModelHelp": "При включении отображается только последняя версия каждой модели CivitAI в виде одной карточки. Старые версии скрыты.",
|
||||
"stickyControls": "Держать панель действий видимой",
|
||||
"stickyControlsHelp": "При включении панель действий (Обновить, Загрузить и т. д.) остаётся закреплённой вверху при прокрутке вместе с навигацией по папкам.",
|
||||
"displayDensity": "Плотность отображения",
|
||||
"displayDensityOptions": {
|
||||
"default": "По умолчанию",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "按模型分组",
|
||||
"groupByModelHelp": "开启后,每个 CivitAI 模型仅显示最新版本的单张卡片,旧版本将被隐藏。",
|
||||
"stickyControls": "保持操作栏可见",
|
||||
"stickyControlsHelp": "开启后,操作栏(刷新、下载等)会在滚动时与路径导航一起固定在页面顶部。",
|
||||
"displayDensity": "显示密度",
|
||||
"displayDensityOptions": {
|
||||
"default": "默认",
|
||||
|
||||
@@ -484,8 +484,6 @@
|
||||
"layoutSettings": {
|
||||
"groupByModel": "按模型分組",
|
||||
"groupByModelHelp": "啟用後,每個 CivitAI 模型僅顯示最新版本的單張卡片,舊版本將被隱藏。",
|
||||
"stickyControls": "保持操作列可見",
|
||||
"stickyControlsHelp": "啟用後,操作列(重新整理、下載等)會在捲動時與麵包屑導覽一起固定在頁面頂端。",
|
||||
"displayDensity": "顯示密度",
|
||||
"displayDensityOptions": {
|
||||
"default": "預設",
|
||||
|
||||
@@ -116,7 +116,6 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
|
||||
"backup_retention_count": 5,
|
||||
"use_new_license_icons": True,
|
||||
"group_by_model": False,
|
||||
"sticky_controls": False,
|
||||
# AI / LLM provider configuration (BYOK)
|
||||
"llm_provider": "openai", # "openai" | "ollama" | "custom"
|
||||
"llm_api_key": "",
|
||||
|
||||
+8
-33
@@ -25,23 +25,6 @@
|
||||
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 */
|
||||
@media (min-width: 2150px) {
|
||||
.container {
|
||||
@@ -218,17 +201,20 @@ body.sticky-controls .sticky-topbar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
margin-left: 6px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
text-transform: uppercase;
|
||||
border-radius: var(--border-radius-xs);
|
||||
background-color: var(--shortcut-bg);
|
||||
border: 1px solid var(--shortcut-border);
|
||||
box-shadow: var(--shortcut-shadow);
|
||||
color: var(--shortcut-text);
|
||||
vertical-align: middle;
|
||||
opacity: 0.8;
|
||||
transition: var(--transition-base);
|
||||
}
|
||||
@@ -239,21 +225,10 @@ body.sticky-controls .sticky-topbar {
|
||||
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 */
|
||||
.control-group button span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* Select dropdown styling */
|
||||
|
||||
@@ -1047,12 +1047,6 @@ export class SettingsManager {
|
||||
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
|
||||
const modelNameDisplaySelect = document.getElementById('modelNameDisplay');
|
||||
if (modelNameDisplaySelect) {
|
||||
@@ -3402,10 +3396,6 @@ export class SettingsManager {
|
||||
const groupByModel = !!state.global.settings.group_by_model;
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,6 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
|
||||
strip_lora_on_copy: false,
|
||||
use_new_license_icons: true,
|
||||
group_by_model: false,
|
||||
sticky_controls: false,
|
||||
llm_provider: 'openai',
|
||||
llm_api_key: '',
|
||||
llm_api_base: '',
|
||||
|
||||
@@ -54,10 +54,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sticky-topbar">
|
||||
{% include 'components/controls.html' %}
|
||||
{% include 'components/breadcrumb.html' %}
|
||||
</div>
|
||||
{% include 'components/duplicates_banner.html' %}
|
||||
{% include 'components/folder_sidebar.html' %}
|
||||
|
||||
|
||||
@@ -79,9 +79,6 @@
|
||||
<!-- Group by model toggle -->
|
||||
{{ 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', [
|
||||
('always', 'settings.layoutSettings.cardInfoDisplayOptions.always'),
|
||||
('hover', 'settings.layoutSettings.cardInfoDisplayOptions.hover'),
|
||||
|
||||
@@ -53,10 +53,8 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sticky-topbar">
|
||||
{% include 'components/controls.html' %}
|
||||
{% include 'components/breadcrumb.html' %}
|
||||
</div>
|
||||
{% include 'components/duplicates_banner.html' %}
|
||||
{% include 'components/folder_sidebar.html' %}
|
||||
|
||||
|
||||
@@ -8,10 +8,8 @@
|
||||
{% block init_check_url %}/api/loras/list?page=1&page_size=1{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="sticky-topbar">
|
||||
{% include 'components/controls.html' %}
|
||||
{% include 'components/breadcrumb.html' %}
|
||||
</div>
|
||||
{% include 'components/duplicates_banner.html' %}
|
||||
{% include 'components/folder_sidebar.html' %}
|
||||
|
||||
|
||||
@@ -64,12 +64,10 @@
|
||||
{% block init_check_url %}/api/recipes?page=1&page_size=1{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Sticky topbar: controls + breadcrumb -->
|
||||
<div class="sticky-topbar">
|
||||
<!-- Recipe controls -->
|
||||
{% include 'components/controls.html' %}
|
||||
<!-- Breadcrumb Navigation -->
|
||||
{% include 'components/breadcrumb.html' %}
|
||||
</div>
|
||||
|
||||
<!-- Duplicates banner (hidden by default) -->
|
||||
<div id="duplicatesBanner" class="duplicates-banner" style="display: none;">
|
||||
|
||||
@@ -253,4 +253,126 @@ describe('AutoComplete active-filters flag', () => {
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
it('removes a stale first-run hint when the toggle is switched on while the dropdown stays open', async () => {
|
||||
localStorage.removeItem('lm:activefilters-tip-dismissed');
|
||||
let enabled = false;
|
||||
settingGetMock.mockImplementation((key) => {
|
||||
if (key === 'loramanager.lora_active_filters_autocomplete') return enabled;
|
||||
if (key === 'loramanager.autocomplete_append_comma') return false;
|
||||
if (key === 'loramanager.autocomplete_auto_format') return false;
|
||||
if (key === 'loramanager.autocomplete_accept_key') return 'both';
|
||||
return undefined;
|
||||
});
|
||||
|
||||
fetchApiMock.mockResolvedValue({
|
||||
json: () => Promise.resolve({ success: true, relative_paths: ['models/example.safetensors'] }),
|
||||
});
|
||||
|
||||
const input = document.createElement('textarea');
|
||||
input.value = 'example';
|
||||
input.selectionStart = 7;
|
||||
document.body.append(input);
|
||||
caretHelperInstance.getBeforeCursor.mockReturnValue('example');
|
||||
|
||||
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||
const autoComplete = new AutoComplete(input, 'loras', {
|
||||
debounceDelay: 0,
|
||||
showPreview: false,
|
||||
minChars: 1,
|
||||
});
|
||||
|
||||
const triggerShow = async () => {
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await Promise.resolve();
|
||||
return autoComplete.dropdown.querySelector('.lm-autocomplete-first-run-hint');
|
||||
};
|
||||
|
||||
// OFF → the enable hint is shown in the suggestions dropdown.
|
||||
expect(await triggerShow()).not.toBeNull();
|
||||
|
||||
// The node's filter chip toggles the setting ON while the dropdown is
|
||||
// still open (ComfyUI can keep focus in the textarea, so no blur/hide
|
||||
// fires). settings.js broadcasts the setting-toggled event.
|
||||
enabled = true;
|
||||
window.dispatchEvent(new CustomEvent('lora-manager:setting-toggled', {
|
||||
detail: { settingId: 'loramanager.lora_active_filters_autocomplete', value: true },
|
||||
}));
|
||||
|
||||
// The stale OFF hint must be gone even though the dropdown never closed.
|
||||
expect(autoComplete.dropdown.querySelector('.lm-autocomplete-first-run-hint')).toBeNull();
|
||||
|
||||
// Further typing while ON must not resurrect the enable hint.
|
||||
expect(await triggerShow()).toBeNull();
|
||||
});
|
||||
|
||||
it('updates the command-list footer when the toggle changes while the command list is open', async () => {
|
||||
let enabled = false;
|
||||
settingGetMock.mockImplementation((key) => {
|
||||
if (key === 'loramanager.lora_active_filters_autocomplete') return enabled;
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const input = document.createElement('textarea');
|
||||
input.value = '/';
|
||||
input.selectionStart = 1;
|
||||
document.body.append(input);
|
||||
caretHelperInstance.getBeforeCursor.mockReturnValue('/');
|
||||
|
||||
const { AutoComplete } = await import(AUTOCOMPLETE_MODULE);
|
||||
const autoComplete = new AutoComplete(input, 'loras', { showPreview: false, minChars: 1 });
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await Promise.resolve();
|
||||
|
||||
const footer = () => autoComplete.dropdown.querySelector('.lm-autocomplete-command-footer');
|
||||
expect(footer()).not.toBeNull();
|
||||
expect(footer().textContent).toContain('Active Filters Search: OFF');
|
||||
expect(footer().textContent).toContain('/activefilters to enable');
|
||||
|
||||
enabled = true;
|
||||
window.dispatchEvent(new CustomEvent('lora-manager:setting-toggled', {
|
||||
detail: { settingId: 'loramanager.lora_active_filters_autocomplete', value: true },
|
||||
}));
|
||||
|
||||
expect(footer()).not.toBeNull();
|
||||
expect(footer().textContent).toContain('Active Filters Search: ON');
|
||||
expect(footer().textContent).toContain('/noactivefilters to disable');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ const setSettingValueMock = vi.fn();
|
||||
vi.mock(SETTINGS_MODULE, () => ({
|
||||
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID:
|
||||
"loramanager.lora_active_filters_autocomplete",
|
||||
SETTING_TOGGLED_EVENT_NAME: "lora-manager:setting-toggled",
|
||||
getLoraActiveFiltersAutocompletePreference: getActiveFiltersPreferenceMock,
|
||||
setLoraManagerSettingValue: setSettingValueMock,
|
||||
}));
|
||||
|
||||
@@ -27,6 +27,18 @@
|
||||
<line x1="6" y1="6" x2="18" y2="18" />
|
||||
</svg>
|
||||
</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>
|
||||
</template>
|
||||
@@ -34,6 +46,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
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
|
||||
declare const LiteGraph: { vueNodesMode?: boolean } | undefined
|
||||
@@ -73,10 +87,10 @@ const inputWrapperRef = ref<HTMLElement | null>(null)
|
||||
// Width of the textarea's own vertical scrollbar gutter. When the content
|
||||
// overflows and a classic (non-overlay) scrollbar is shown, the scrollbar
|
||||
// occupies the textarea's rightmost pixels and the absolutely-positioned
|
||||
// corner clear (x) button would overlap it. We expose this width as a CSS
|
||||
// var so the button can shift left of the scrollbar; it is 0 when there is
|
||||
// no scrollbar (content fits, or platform overlay scrollbars that float
|
||||
// over the content).
|
||||
// corner buttons (clear x / active-filters filter) would overlap it. We
|
||||
// expose this width as a CSS var so those buttons can shift left of the
|
||||
// scrollbar; it is 0 when there is no scrollbar (content fits, or platform
|
||||
// overlay scrollbars that float over the content).
|
||||
const vScrollbarWidth = ref(0)
|
||||
let scrollbarResizeObserver: ResizeObserver | null = null
|
||||
|
||||
@@ -111,6 +125,48 @@ const hasText = ref(false)
|
||||
// Show clear button when there is text
|
||||
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
|
||||
useAutocomplete(
|
||||
textareaRef,
|
||||
@@ -200,9 +256,6 @@ const onWheel = (event: WheelEvent) => {
|
||||
|
||||
// Handle external value changes (e.g., from "send lora to workflow")
|
||||
const onExternalValueChange = () => {
|
||||
// The DOM value was set synchronously by the caller; the new content may
|
||||
// have toggled the vertical scrollbar on/off.
|
||||
updateVScrollbarWidth()
|
||||
updateHasTextState()
|
||||
}
|
||||
|
||||
@@ -212,9 +265,6 @@ const setupWidgetOnSetValue = () => {
|
||||
props.widget.onSetValue = (value: string) => {
|
||||
// The DOM value is already set by setValue, just update our state
|
||||
hasText.value = value.length > 0
|
||||
// Programmatic sets can toggle the scrollbar; re-measure so the
|
||||
// corner clear button stays clear of it.
|
||||
updateVScrollbarWidth()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,6 +355,11 @@ onMounted(() => {
|
||||
// Setup widget.onSetValue callback
|
||||
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.
|
||||
updateVScrollbarWidth()
|
||||
observeScrollbarWidth()
|
||||
@@ -336,6 +391,7 @@ onUnmounted(() => {
|
||||
|
||||
// Remove event listener
|
||||
document.removeEventListener('lora-manager:vue-mode-change', onModeChange)
|
||||
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -428,6 +484,58 @@ onUnmounted(() => {
|
||||
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 */
|
||||
.text-input.vue-dom-mode ~ .clear-button {
|
||||
right: calc(8px + var(--lm-vscrollbar-width, 0px));
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
|
||||
import { nextTick } from 'vue'
|
||||
import { shallowMount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi, afterEach } from 'vitest'
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
|
||||
import AutocompleteTextWidget from '@/components/AutocompleteTextWidget.vue'
|
||||
|
||||
function createMockWidget() {
|
||||
return {
|
||||
callback: vi.fn(),
|
||||
onSetValue: undefined as ((v: string) => void) | undefined,
|
||||
onSetValue: undefined,
|
||||
inputEl: undefined,
|
||||
metadataWidget: undefined,
|
||||
name: 'text',
|
||||
@@ -135,15 +135,121 @@ 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.
|
||||
*
|
||||
* When the textarea content overflows and a classic (non-overlay) scrollbar
|
||||
* is shown, the absolutely-positioned corner clear (x) button would sit on
|
||||
* top of the scrollbar. The component measures the scrollbar gutter and
|
||||
* exposes it as the --lm-vscrollbar-width CSS var on .input-wrapper so the
|
||||
* button shifts left of the scrollbar. jsdom does no layout, so overflow is
|
||||
* simulated by overriding the scroll/dimension props.
|
||||
* is shown, the absolutely-positioned corner buttons (clear x, active-filters
|
||||
* filter chip) would sit on top of the scrollbar. The component measures the
|
||||
* scrollbar gutter and exposes it as the --lm-vscrollbar-width CSS var on
|
||||
* .input-wrapper so the buttons shift left of the scrollbar. jsdom does no
|
||||
* layout, so overflow is simulated by overriding the scroll/dimension props.
|
||||
*/
|
||||
describe('AutocompleteTextWidget vertical scrollbar inset', () => {
|
||||
function overrideTextareaMetrics(
|
||||
@@ -216,66 +322,4 @@ describe('AutocompleteTextWidget vertical scrollbar inset', () => {
|
||||
await nextTick()
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,86 +1,12 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
|
||||
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'
|
||||
]
|
||||
import { resolve } from 'path'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
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
|
||||
},
|
||||
},
|
||||
cssInjectedByJsPlugin() // Inject CSS into JS for ComfyUI compatibility
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
@@ -94,7 +20,13 @@ export default defineConfig({
|
||||
fileName: 'lora-manager-widgets'
|
||||
},
|
||||
rollupOptions: {
|
||||
external: EXTERNAL_SPECIFIERS,
|
||||
external: [
|
||||
'../../../scripts/app.js',
|
||||
'../../../scripts/api.js',
|
||||
'../loras_widget.js',
|
||||
'../autocomplete.js',
|
||||
'../preview_tooltip.js'
|
||||
],
|
||||
output: {
|
||||
dir: '../web/comfyui/vue-widgets',
|
||||
entryFileNames: 'lora-manager-widgets.js',
|
||||
@@ -109,4 +41,3 @@ export default defineConfig({
|
||||
'process.env.NODE_ENV': JSON.stringify('production')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -14,9 +14,11 @@ import {
|
||||
getAutocompleteAppendCommaPreference,
|
||||
getAutocompleteAutoFormatPreference,
|
||||
getAutocompleteAcceptKeyPreference,
|
||||
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID,
|
||||
getLoraActiveFiltersAutocompletePreference,
|
||||
getPromptTagAutocompletePreference,
|
||||
getTagSpaceReplacementPreference,
|
||||
SETTING_TOGGLED_EVENT_NAME,
|
||||
setLoraManagerSettingValue,
|
||||
} from "./settings.js";
|
||||
import { showToast } from "./utils.js";
|
||||
@@ -607,6 +609,7 @@ class AutoComplete {
|
||||
this.onBlur = null;
|
||||
this.onDocumentClick = null;
|
||||
this.onScroll = null;
|
||||
this.onSettingToggled = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
@@ -774,6 +777,22 @@ class AutoComplete {
|
||||
};
|
||||
document.addEventListener('click', this.onDocumentClick);
|
||||
|
||||
// React to setting changes that happen underneath an open dropdown
|
||||
// (e.g. toggling the active-filters search from the node's filter
|
||||
// chip can keep the dropdown open in ComfyUI). Refresh the state
|
||||
// hints so they never show a message for the previous state.
|
||||
if (this.onSettingToggled) {
|
||||
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, this.onSettingToggled);
|
||||
}
|
||||
this.onSettingToggled = (e) => {
|
||||
const detail = e && e.detail;
|
||||
if (!detail || detail.settingId !== LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID) {
|
||||
return;
|
||||
}
|
||||
this._refreshStateHints();
|
||||
};
|
||||
window.addEventListener(SETTING_TOGGLED_EVENT_NAME, this.onSettingToggled);
|
||||
|
||||
// Mark this element as having autocomplete events bound
|
||||
this.inputElement._autocompleteEventsBound = true;
|
||||
|
||||
@@ -1811,15 +1830,37 @@ class AutoComplete {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh state-dependent dropdown decorations (first-run Tip and the
|
||||
* slash-command-list footer) from the live setting. Called when the
|
||||
* active-filters toggle changes underneath an open dropdown, so the
|
||||
* dropdown never keeps showing a message for the previous state.
|
||||
*/
|
||||
_refreshStateHints() {
|
||||
if (!this.isVisible || this.modelType !== 'loras') {
|
||||
return;
|
||||
}
|
||||
if (this.showingCommands) {
|
||||
// Command list: footer text carries the ON/OFF state.
|
||||
this._renderCommandListFooter();
|
||||
} else {
|
||||
// Plain suggestions: the first-run Tip advertises /activefilters
|
||||
// only while the feature is disabled.
|
||||
this._maybeShowFirstRunHint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a one-time, dismissible hint inside the dropdown surfacing the
|
||||
* toggle commands: prompt nodes advertise /noautocomplete, loras nodes
|
||||
* advertise /activefilters. Dismissal is persisted in localStorage.
|
||||
*/
|
||||
_maybeShowFirstRunHint() {
|
||||
if (this.firstRunHint) {
|
||||
return;
|
||||
}
|
||||
// Dropdown decorations can outlive a state change (the dropdown may
|
||||
// stay open when the setting is toggled from the node, e.g. clicking
|
||||
// the active-filters filter chip). Always recompute from the live
|
||||
// state so a stale Tip is never kept for the previous state.
|
||||
this._removeFirstRunHint();
|
||||
|
||||
let hintText = null;
|
||||
let storageKey = null;
|
||||
@@ -3178,6 +3219,11 @@ class AutoComplete {
|
||||
this.onDocumentClick = null;
|
||||
}
|
||||
|
||||
if (this.onSettingToggled) {
|
||||
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, this.onSettingToggled);
|
||||
this.onSettingToggled = null;
|
||||
}
|
||||
|
||||
if (this.onScroll && this.scrollContainer) {
|
||||
this.scrollContainer.removeEventListener('scroll', this.onScroll);
|
||||
this.onScroll = null;
|
||||
|
||||
+21
-1
@@ -175,23 +175,42 @@ const getPromptTagAutocompletePreference = (() => {
|
||||
/**
|
||||
* Persist a LoRA Manager setting through ComfyUI's setting API.
|
||||
* 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 settingManager = app?.extensionManager?.setting;
|
||||
if (settingManager && typeof settingManager.set === "function") {
|
||||
await settingManager.set(settingId, value);
|
||||
_notifySettingToggled(settingId, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
const setting = app?.ui?.settings?.settingsById?.[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) {
|
||||
// Best-effort notification; ignore non-browser environments
|
||||
}
|
||||
};
|
||||
|
||||
const getAutocompleteAppendCommaPreference = (() => {
|
||||
let settingsUnavailableLogged = false;
|
||||
|
||||
@@ -450,7 +469,7 @@ app.registerExtension({
|
||||
name: "Search LoRA autocomplete within active filters",
|
||||
type: "boolean",
|
||||
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. You can also toggle it by typing /activefilters or /noactivefilters in the LoRA field, or from the node's right-click menu.",
|
||||
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.",
|
||||
category: ["LoRA Manager", "Autocomplete", "LoRA Active Filters"],
|
||||
},
|
||||
{
|
||||
@@ -598,6 +617,7 @@ app.registerExtension({
|
||||
export {
|
||||
PROMPT_TAG_AUTOCOMPLETE_SETTING_ID,
|
||||
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID,
|
||||
SETTING_TOGGLED_EVENT_NAME,
|
||||
getWheelSensitivity,
|
||||
getAutoPathCorrectionPreference,
|
||||
getAutocompleteAppendCommaPreference,
|
||||
|
||||
@@ -2118,14 +2118,14 @@ to { transform: rotate(360deg);
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.autocomplete-text-widget[data-v-793d67d2] {
|
||||
.autocomplete-text-widget[data-v-22743258] {
|
||||
background: transparent;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.input-wrapper[data-v-793d67d2] {
|
||||
.input-wrapper[data-v-22743258] {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -2133,7 +2133,7 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Canvas mode styles (default) - matches built-in comfy-multiline-input */
|
||||
.text-input[data-v-793d67d2] {
|
||||
.text-input[data-v-22743258] {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
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 */
|
||||
.text-input.vue-dom-mode[data-v-793d67d2] {
|
||||
.text-input.vue-dom-mode[data-v-22743258] {
|
||||
background-color: var(--color-charcoal-400, #313235);
|
||||
color: #fff;
|
||||
padding: 8px 12px 30px 12px; /* Reserve bottom space for clear button */
|
||||
@@ -2161,12 +2161,12 @@ to { transform: rotate(360deg);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
}
|
||||
.text-input[data-v-793d67d2]:focus {
|
||||
.text-input[data-v-22743258]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Clear button styles */
|
||||
.clear-button[data-v-793d67d2] {
|
||||
.clear-button[data-v-22743258] {
|
||||
position: absolute;
|
||||
right: calc(6px + var(--lm-vscrollbar-width, 0px));
|
||||
bottom: 6px; /* Changed from top to bottom */
|
||||
@@ -2189,31 +2189,79 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
|
||||
/* Show clear button when hovering over input wrapper */
|
||||
.input-wrapper:hover .clear-button[data-v-793d67d2] {
|
||||
.input-wrapper:hover .clear-button[data-v-22743258] {
|
||||
opacity: 0.7;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.clear-button[data-v-793d67d2]:hover {
|
||||
.clear-button[data-v-22743258]:hover {
|
||||
opacity: 1;
|
||||
background: rgba(255, 100, 100, 0.8);
|
||||
}
|
||||
.clear-button svg[data-v-793d67d2] {
|
||||
.clear-button svg[data-v-22743258] {
|
||||
width: 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 */
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-793d67d2] {
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-22743258] {
|
||||
right: calc(8px + var(--lm-vscrollbar-width, 0px));
|
||||
bottom: 10px; /* Changed from top to bottom, adjusted for Vue DOM padding */
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(107, 114, 128, 0.6);
|
||||
}
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-793d67d2]:hover {
|
||||
.text-input.vue-dom-mode ~ .clear-button[data-v-22743258]:hover {
|
||||
background: oklch(62% 0.18 25);
|
||||
}
|
||||
.text-input.vue-dom-mode ~ .clear-button svg[data-v-793d67d2] {
|
||||
.text-input.vue-dom-mode ~ .clear-button svg[data-v-22743258] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
@@ -2481,9 +2529,8 @@ to { transform: rotate(360deg);
|
||||
}
|
||||
})();
|
||||
var _a;
|
||||
import { app } from "../../../scripts/app.js";
|
||||
import { api } from "../../../scripts/api.js";
|
||||
import "../settings.js";
|
||||
import { app as app$1 } from "../../../scripts/app.js";
|
||||
import { api as api$1 } from "../../../scripts/api.js";
|
||||
/**
|
||||
* @vue/shared v3.5.26
|
||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||
@@ -11053,7 +11100,7 @@ const _sfc_main$o = /* @__PURE__ */ defineComponent({
|
||||
const EditButton = /* @__PURE__ */ _export_sfc(_sfc_main$o, [["__scopeId", "data-v-8da8aa4b"]]);
|
||||
const _hoisted_1$k = { class: "section" };
|
||||
const _hoisted_2$j = { class: "section__header" };
|
||||
const _hoisted_3$g = { class: "section__content" };
|
||||
const _hoisted_3$h = { class: "section__content" };
|
||||
const _hoisted_4$f = {
|
||||
key: 0,
|
||||
class: "section__placeholder"
|
||||
@@ -11083,7 +11130,7 @@ const _sfc_main$n = /* @__PURE__ */ defineComponent({
|
||||
onClick: _cache[0] || (_cache[0] = ($event) => _ctx.$emit("edit"))
|
||||
})
|
||||
]),
|
||||
createBaseVNode("div", _hoisted_3$g, [
|
||||
createBaseVNode("div", _hoisted_3$h, [
|
||||
__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) => {
|
||||
return openBlock(), createBlock(FilterChip, {
|
||||
@@ -11102,7 +11149,7 @@ const _sfc_main$n = /* @__PURE__ */ defineComponent({
|
||||
const BaseModelSection = /* @__PURE__ */ _export_sfc(_sfc_main$n, [["__scopeId", "data-v-12f059e2"]]);
|
||||
const _hoisted_1$j = { class: "section" };
|
||||
const _hoisted_2$i = { class: "section__columns" };
|
||||
const _hoisted_3$f = { class: "section__column" };
|
||||
const _hoisted_3$g = { class: "section__column" };
|
||||
const _hoisted_4$e = { class: "section__column-header" };
|
||||
const _hoisted_5$c = { class: "section__column-content" };
|
||||
const _hoisted_6$c = {
|
||||
@@ -11138,7 +11185,7 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
||||
createBaseVNode("span", { class: "section__title" }, "TAGS")
|
||||
], -1)),
|
||||
createBaseVNode("div", _hoisted_2$i, [
|
||||
createBaseVNode("div", _hoisted_3$f, [
|
||||
createBaseVNode("div", _hoisted_3$g, [
|
||||
createBaseVNode("div", _hoisted_4$e, [
|
||||
_cache[2] || (_cache[2] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
|
||||
createVNode(EditButton, {
|
||||
@@ -11184,7 +11231,7 @@ const _sfc_main$m = /* @__PURE__ */ defineComponent({
|
||||
const TagsSection = /* @__PURE__ */ _export_sfc(_sfc_main$m, [["__scopeId", "data-v-b869b780"]]);
|
||||
const _hoisted_1$i = { class: "section" };
|
||||
const _hoisted_2$h = { class: "section__columns" };
|
||||
const _hoisted_3$e = { class: "section__column" };
|
||||
const _hoisted_3$f = { class: "section__column" };
|
||||
const _hoisted_4$d = { class: "section__column-header" };
|
||||
const _hoisted_5$b = { class: "section__content" };
|
||||
const _hoisted_6$b = {
|
||||
@@ -11232,7 +11279,7 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
||||
createBaseVNode("span", { class: "section__title" }, "FOLDERS")
|
||||
], -1)),
|
||||
createBaseVNode("div", _hoisted_2$h, [
|
||||
createBaseVNode("div", _hoisted_3$e, [
|
||||
createBaseVNode("div", _hoisted_3$f, [
|
||||
createBaseVNode("div", _hoisted_4$d, [
|
||||
_cache[3] || (_cache[3] = createBaseVNode("span", { class: "section__column-title section__column-title--include" }, "INCLUDE", -1)),
|
||||
createBaseVNode("button", {
|
||||
@@ -11300,7 +11347,7 @@ const _sfc_main$l = /* @__PURE__ */ defineComponent({
|
||||
const FoldersSection = /* @__PURE__ */ _export_sfc(_sfc_main$l, [["__scopeId", "data-v-af9caf84"]]);
|
||||
const _hoisted_1$h = { class: "section" };
|
||||
const _hoisted_2$g = { class: "section__header" };
|
||||
const _hoisted_3$d = { class: "section__toggle" };
|
||||
const _hoisted_3$e = { class: "section__toggle" };
|
||||
const _hoisted_4$c = ["checked"];
|
||||
const _hoisted_5$a = { class: "section__columns" };
|
||||
const _hoisted_6$a = { class: "section__column" };
|
||||
@@ -11356,7 +11403,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
||||
return openBlock(), createElementBlock("div", _hoisted_1$h, [
|
||||
createBaseVNode("div", _hoisted_2$g, [
|
||||
_cache[4] || (_cache[4] = createBaseVNode("span", { class: "section__title" }, "NAME PATTERNS", -1)),
|
||||
createBaseVNode("label", _hoisted_3$d, [
|
||||
createBaseVNode("label", _hoisted_3$e, [
|
||||
createBaseVNode("input", {
|
||||
type: "checkbox",
|
||||
checked: __props.useRegex,
|
||||
@@ -11440,7 +11487,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
|
||||
const NamePatternsSection = /* @__PURE__ */ _export_sfc(_sfc_main$k, [["__scopeId", "data-v-9995b5ed"]]);
|
||||
const _hoisted_1$g = { class: "section" };
|
||||
const _hoisted_2$f = { class: "section__toggles" };
|
||||
const _hoisted_3$c = { class: "toggle-item" };
|
||||
const _hoisted_3$d = { class: "toggle-item" };
|
||||
const _hoisted_4$b = ["aria-checked"];
|
||||
const _hoisted_5$9 = { class: "toggle-item" };
|
||||
const _hoisted_6$9 = ["aria-checked"];
|
||||
@@ -11458,7 +11505,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
||||
createBaseVNode("span", { class: "section__title" }, "LICENSE")
|
||||
], -1)),
|
||||
createBaseVNode("div", _hoisted_2$f, [
|
||||
createBaseVNode("label", _hoisted_3$c, [
|
||||
createBaseVNode("label", _hoisted_3$d, [
|
||||
_cache[3] || (_cache[3] = createBaseVNode("span", {
|
||||
class: "toggle-item__label",
|
||||
title: "Use the model without crediting the creator"
|
||||
@@ -11498,7 +11545,7 @@ const _sfc_main$j = /* @__PURE__ */ defineComponent({
|
||||
const LicenseSection = /* @__PURE__ */ _export_sfc(_sfc_main$j, [["__scopeId", "data-v-07ddd3df"]]);
|
||||
const _hoisted_1$f = { class: "preview" };
|
||||
const _hoisted_2$e = { class: "preview__title" };
|
||||
const _hoisted_3$b = ["disabled"];
|
||||
const _hoisted_3$c = ["disabled"];
|
||||
const _hoisted_4$a = {
|
||||
key: 0,
|
||||
class: "preview__tooltip"
|
||||
@@ -11557,7 +11604,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"
|
||||
})
|
||||
], -1)
|
||||
])], 10, _hoisted_3$b)
|
||||
])], 10, _hoisted_3$c)
|
||||
], 32),
|
||||
createVNode(Transition, { name: "tooltip" }, {
|
||||
default: withCtx(() => [
|
||||
@@ -11669,7 +11716,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
|
||||
const LoraPoolSummaryView = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-83235a00"]]);
|
||||
const _hoisted_1$d = { class: "lora-pool-modal__header" };
|
||||
const _hoisted_2$c = { class: "lora-pool-modal__title-container" };
|
||||
const _hoisted_3$a = { class: "lora-pool-modal__title" };
|
||||
const _hoisted_3$b = { class: "lora-pool-modal__title" };
|
||||
const _hoisted_4$9 = {
|
||||
key: 0,
|
||||
class: "lora-pool-modal__subtitle"
|
||||
@@ -11729,7 +11776,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
||||
}, [
|
||||
createBaseVNode("div", _hoisted_1$d, [
|
||||
createBaseVNode("div", _hoisted_2$c, [
|
||||
createBaseVNode("h3", _hoisted_3$a, toDisplayString(__props.title), 1),
|
||||
createBaseVNode("h3", _hoisted_3$b, toDisplayString(__props.title), 1),
|
||||
__props.subtitle ? (openBlock(), createElementBlock("p", _hoisted_4$9, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)
|
||||
]),
|
||||
createBaseVNode("button", {
|
||||
@@ -11757,7 +11804,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
|
||||
const ModalWrapper = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-7b4de03d"]]);
|
||||
const _hoisted_1$c = { class: "search-container" };
|
||||
const _hoisted_2$b = { class: "model-list" };
|
||||
const _hoisted_3$9 = ["checked", "onChange"];
|
||||
const _hoisted_3$a = ["checked", "onChange"];
|
||||
const _hoisted_4$8 = { class: "model-checkbox-visual" };
|
||||
const _hoisted_5$6 = {
|
||||
key: 0,
|
||||
@@ -11867,7 +11914,7 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
|
||||
checked: isSelected(model.name),
|
||||
onChange: ($event) => toggleModel(model.name),
|
||||
class: "model-checkbox"
|
||||
}, null, 40, _hoisted_3$9),
|
||||
}, null, 40, _hoisted_3$a),
|
||||
createBaseVNode("span", _hoisted_4$8, [
|
||||
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)
|
||||
@@ -11888,7 +11935,7 @@ const _sfc_main$f = /* @__PURE__ */ defineComponent({
|
||||
const BaseModelModal = /* @__PURE__ */ _export_sfc(_sfc_main$f, [["__scopeId", "data-v-e02ca44a"]]);
|
||||
const _hoisted_1$b = { class: "search-container" };
|
||||
const _hoisted_2$a = ["onClick"];
|
||||
const _hoisted_3$8 = {
|
||||
const _hoisted_3$9 = {
|
||||
key: 0,
|
||||
class: "no-results"
|
||||
};
|
||||
@@ -12032,7 +12079,7 @@ const _sfc_main$e = /* @__PURE__ */ defineComponent({
|
||||
onClick: ($event) => toggleTag(tag.tag)
|
||||
}, toDisplayString(tag.tag), 11, _hoisted_2$a);
|
||||
}), 128)),
|
||||
visibleTags.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_3$8, " No tags found ")) : createCommentVNode("", true),
|
||||
visibleTags.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_3$9, " No tags found ")) : createCommentVNode("", true),
|
||||
hasMoreTags.value ? (openBlock(), createElementBlock("div", _hoisted_4$7, " Scroll to load more... ")) : createCommentVNode("", true)
|
||||
], 544)
|
||||
]),
|
||||
@@ -12047,7 +12094,7 @@ const _hoisted_2$9 = {
|
||||
key: 1,
|
||||
class: "tree-node__toggle-spacer"
|
||||
};
|
||||
const _hoisted_3$7 = { class: "tree-node__checkbox-label" };
|
||||
const _hoisted_3$8 = { class: "tree-node__checkbox-label" };
|
||||
const _hoisted_4$6 = ["checked"];
|
||||
const _hoisted_5$5 = {
|
||||
key: 0,
|
||||
@@ -12108,7 +12155,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)
|
||||
])], 2))
|
||||
])) : (openBlock(), createElementBlock("span", _hoisted_2$9)),
|
||||
createBaseVNode("label", _hoisted_3$7, [
|
||||
createBaseVNode("label", _hoisted_3$8, [
|
||||
createBaseVNode("input", {
|
||||
type: "checkbox",
|
||||
class: "tree-node__checkbox",
|
||||
@@ -12154,7 +12201,7 @@ const _sfc_main$d = /* @__PURE__ */ defineComponent({
|
||||
const FolderTreeNode = /* @__PURE__ */ _export_sfc(_sfc_main$d, [["__scopeId", "data-v-90187dd4"]]);
|
||||
const _hoisted_1$9 = { class: "search-container" };
|
||||
const _hoisted_2$8 = { class: "folder-tree" };
|
||||
const _hoisted_3$6 = {
|
||||
const _hoisted_3$7 = {
|
||||
key: 1,
|
||||
class: "no-results"
|
||||
};
|
||||
@@ -12253,7 +12300,7 @@ const _sfc_main$c = /* @__PURE__ */ defineComponent({
|
||||
onToggleExpand: toggleExpand,
|
||||
onToggleSelect: toggleSelect
|
||||
}, null, 8, ["node", "selected", "expanded", "variant"]);
|
||||
}), 128)) : (openBlock(), createElementBlock("div", _hoisted_3$6, " No folders found "))
|
||||
}), 128)) : (openBlock(), createElementBlock("div", _hoisted_3$7, " No folders found "))
|
||||
])
|
||||
]),
|
||||
_: 1
|
||||
@@ -12659,7 +12706,7 @@ const _sfc_main$b = /* @__PURE__ */ defineComponent({
|
||||
const LoraPoolWidget = /* @__PURE__ */ _export_sfc(_sfc_main$b, [["__scopeId", "data-v-ed73eab5"]]);
|
||||
const _hoisted_1$8 = { class: "last-used-preview" };
|
||||
const _hoisted_2$7 = { class: "last-used-preview__content" };
|
||||
const _hoisted_3$5 = ["src", "onError"];
|
||||
const _hoisted_3$6 = ["src", "onError"];
|
||||
const _hoisted_4$5 = {
|
||||
key: 1,
|
||||
class: "last-used-preview__thumb last-used-preview__thumb--placeholder"
|
||||
@@ -12711,7 +12758,7 @@ const _sfc_main$a = /* @__PURE__ */ defineComponent({
|
||||
src: previewUrls.value[lora.name],
|
||||
class: "last-used-preview__thumb",
|
||||
onError: ($event) => onImageError(lora.name)
|
||||
}, null, 40, _hoisted_3$5)) : (openBlock(), createElementBlock("div", _hoisted_4$5, [..._cache[0] || (_cache[0] = [
|
||||
}, null, 40, _hoisted_3$6)) : (openBlock(), createElementBlock("div", _hoisted_4$5, [..._cache[0] || (_cache[0] = [
|
||||
createBaseVNode("svg", {
|
||||
viewBox: "0 0 16 16",
|
||||
fill: "currentColor"
|
||||
@@ -13152,7 +13199,7 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
|
||||
const DualRangeSlider = /* @__PURE__ */ _export_sfc(_sfc_main$8, [["__scopeId", "data-v-e0c8dc9f"]]);
|
||||
const _hoisted_1$5 = { class: "randomizer-settings" };
|
||||
const _hoisted_2$5 = { class: "setting-section" };
|
||||
const _hoisted_3$4 = { class: "count-mode-tabs" };
|
||||
const _hoisted_3$5 = { class: "count-mode-tabs" };
|
||||
const _hoisted_4$4 = ["checked"];
|
||||
const _hoisted_5$3 = ["checked"];
|
||||
const _hoisted_6$3 = { class: "slider-container" };
|
||||
@@ -13217,7 +13264,7 @@ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
|
||||
], -1)),
|
||||
createBaseVNode("div", _hoisted_2$5, [
|
||||
_cache[20] || (_cache[20] = createBaseVNode("label", { class: "setting-label" }, "LoRA Count", -1)),
|
||||
createBaseVNode("div", _hoisted_3$4, [
|
||||
createBaseVNode("div", _hoisted_3$5, [
|
||||
createBaseVNode("label", {
|
||||
class: normalizeClass(["count-mode-tab", { active: __props.countMode === "fixed" }])
|
||||
}, [
|
||||
@@ -13848,7 +13895,7 @@ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
|
||||
const LoraRandomizerWidget = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-ca6e8cec"]]);
|
||||
const _hoisted_1$4 = { class: "cycler-settings" };
|
||||
const _hoisted_2$4 = { class: "setting-section progress-section" };
|
||||
const _hoisted_3$3 = { class: "progress-label" };
|
||||
const _hoisted_3$4 = { class: "progress-label" };
|
||||
const _hoisted_4$3 = ["title"];
|
||||
const _hoisted_5$2 = { class: "progress-counter" };
|
||||
const _hoisted_6$2 = { class: "progress-index" };
|
||||
@@ -13973,7 +14020,7 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
||||
class: normalizeClass(["progress-info", { disabled: __props.isPauseDisabled }]),
|
||||
onClick: handleOpenSelector
|
||||
}, [
|
||||
createBaseVNode("span", _hoisted_3$3, toDisplayString(__props.isWorkflowExecuting ? "Using LoRA:" : "Next LoRA:"), 1),
|
||||
createBaseVNode("span", _hoisted_3$4, toDisplayString(__props.isWorkflowExecuting ? "Using LoRA:" : "Next LoRA:"), 1),
|
||||
createBaseVNode("span", {
|
||||
class: normalizeClass(["progress-name clickable", { disabled: __props.isPauseDisabled, "no-lora": __props.isNoLora }]),
|
||||
title: __props.currentLoraFilename
|
||||
@@ -14173,7 +14220,7 @@ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
|
||||
const LoraCyclerSettingsView = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-f0663be4"]]);
|
||||
const _hoisted_1$3 = { class: "search-container" };
|
||||
const _hoisted_2$3 = { class: "lora-list" };
|
||||
const _hoisted_3$2 = ["onMouseenter", "onClick"];
|
||||
const _hoisted_3$3 = ["onMouseenter", "onClick"];
|
||||
const _hoisted_4$2 = { class: "lora-index" };
|
||||
const _hoisted_5$1 = ["title"];
|
||||
const _hoisted_6$1 = {
|
||||
@@ -14354,7 +14401,7 @@ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
|
||||
title: item.lora.file_name
|
||||
}, toDisplayString(item.lora.file_name), 9, _hoisted_5$1),
|
||||
__props.currentIndex === item.index ? (openBlock(), createElementBlock("span", _hoisted_6$1, "Current")) : createCommentVNode("", true)
|
||||
], 42, _hoisted_3$2);
|
||||
], 42, _hoisted_3$3);
|
||||
}), 128)),
|
||||
filteredList.value.length === 0 ? (openBlock(), createElementBlock("div", _hoisted_7$1, " No LoRAs found ")) : createCommentVNode("", true)
|
||||
])
|
||||
@@ -14970,7 +15017,7 @@ const _hoisted_2$2 = {
|
||||
class: "json-content",
|
||||
ref: "contentRef"
|
||||
};
|
||||
const _hoisted_3$1 = ["innerHTML"];
|
||||
const _hoisted_3$2 = ["innerHTML"];
|
||||
const _hoisted_4$1 = {
|
||||
key: 1,
|
||||
class: "placeholder"
|
||||
@@ -15065,7 +15112,7 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
||||
hasMetadata.value ? (openBlock(), createElementBlock("pre", {
|
||||
key: 0,
|
||||
innerHTML: highlightedJson.value
|
||||
}, null, 8, _hoisted_3$1)) : (openBlock(), createElementBlock("div", _hoisted_4$1, "No metadata available"))
|
||||
}, null, 8, _hoisted_3$2)) : (openBlock(), createElementBlock("div", _hoisted_4$1, "No metadata available"))
|
||||
], 512)
|
||||
]);
|
||||
};
|
||||
@@ -15137,8 +15184,71 @@ function useAutocomplete(textareaRef, modelType = "loras", options = {}) {
|
||||
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_2$1 = ["placeholder", "spellcheck"];
|
||||
const _hoisted_3$1 = ["title"];
|
||||
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
__name: "AutocompleteTextWidget",
|
||||
props: {
|
||||
@@ -15187,6 +15297,37 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
};
|
||||
const hasText = ref(false);
|
||||
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(
|
||||
textareaRef,
|
||||
props.modelType ?? "loras",
|
||||
@@ -15239,14 +15380,12 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
}
|
||||
};
|
||||
const onExternalValueChange = () => {
|
||||
updateVScrollbarWidth();
|
||||
updateHasTextState();
|
||||
};
|
||||
const setupWidgetOnSetValue = () => {
|
||||
if (props.widget) {
|
||||
props.widget.onSetValue = (value) => {
|
||||
hasText.value = value.length > 0;
|
||||
updateVScrollbarWidth();
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -15296,6 +15435,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
props.widget.callback(textareaRef.value.value);
|
||||
}
|
||||
setupWidgetOnSetValue();
|
||||
refreshActiveFiltersState();
|
||||
window.addEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled);
|
||||
updateVScrollbarWidth();
|
||||
observeScrollbarWidth();
|
||||
document.addEventListener("lora-manager:vue-mode-change", onModeChange);
|
||||
@@ -15315,6 +15456,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
props.widget.onSetValue = void 0;
|
||||
}
|
||||
document.removeEventListener("lora-manager:vue-mode-change", onModeChange);
|
||||
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, onSettingToggled);
|
||||
});
|
||||
return (_ctx, _cache) => {
|
||||
return openBlock(), createElementBlock("div", _hoisted_1$1, [
|
||||
@@ -15361,13 +15503,29 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
||||
y2: "18"
|
||||
})
|
||||
], -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)
|
||||
]);
|
||||
};
|
||||
}
|
||||
});
|
||||
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-793d67d2"]]);
|
||||
const AutocompleteTextWidget = /* @__PURE__ */ _export_sfc(_sfc_main$1, [["__scopeId", "data-v-22743258"]]);
|
||||
const _hoisted_1 = { class: "lora-info-tabs" };
|
||||
const _hoisted_2 = { class: "tab-content notes-tab" };
|
||||
const _hoisted_3 = { class: "info-field" };
|
||||
@@ -15784,6 +15942,11 @@ function createModeChangeCallback(node, updateDownstreamLoaders2, nodeSpecificCa
|
||||
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 _loraSyntaxFormatRefreshPromise = null;
|
||||
async function _fetchLoraSyntaxFormat() {
|
||||
@@ -16140,8 +16303,8 @@ const AUTOCOMPLETE_TEXT_MIN_WIDTH_DEFAULT = 400;
|
||||
const AUTOCOMPLETE_TEXT_MIN_HEIGHT_DEFAULT = 300;
|
||||
const AUTOCOMPLETE_METADATA_VERSION = 1;
|
||||
const LORA_MANAGER_WIDGET_IDS_PROPERTY = "__lm_widget_ids";
|
||||
const originalGraphToPrompt = app.graphToPrompt.bind(app);
|
||||
app.graphToPrompt = async (...args) => {
|
||||
const originalGraphToPrompt = app$1.graphToPrompt.bind(app$1);
|
||||
app$1.graphToPrompt = async (...args) => {
|
||||
const result = await originalGraphToPrompt(...args);
|
||||
stripAutocompleteMetadataFromPromptResult(result);
|
||||
return result;
|
||||
@@ -16150,7 +16313,7 @@ function forwardMiddleMouseToCanvas(container) {
|
||||
if (!container) return;
|
||||
container.addEventListener("pointerdown", (event) => {
|
||||
if (event.button === 1) {
|
||||
const canvas = app.canvas;
|
||||
const canvas = app$1.canvas;
|
||||
if (canvas && typeof canvas.processMouseDown === "function") {
|
||||
canvas.processMouseDown(event);
|
||||
}
|
||||
@@ -16158,7 +16321,7 @@ function forwardMiddleMouseToCanvas(container) {
|
||||
});
|
||||
container.addEventListener("pointermove", (event) => {
|
||||
if ((event.buttons & 4) === 4) {
|
||||
const canvas = app.canvas;
|
||||
const canvas = app$1.canvas;
|
||||
if (canvas && typeof canvas.processMouseMove === "function") {
|
||||
canvas.processMouseMove(event);
|
||||
}
|
||||
@@ -16166,7 +16329,7 @@ function forwardMiddleMouseToCanvas(container) {
|
||||
});
|
||||
container.addEventListener("pointerup", (event) => {
|
||||
if (event.button === 1) {
|
||||
const canvas = app.canvas;
|
||||
const canvas = app$1.canvas;
|
||||
if (canvas && typeof canvas.processMouseUp === "function") {
|
||||
canvas.processMouseUp(event);
|
||||
}
|
||||
@@ -16284,7 +16447,7 @@ function createLoraRandomizerWidget(node) {
|
||||
const vueApp = createApp(LoraRandomizerWidget, {
|
||||
widget,
|
||||
node,
|
||||
api
|
||||
api: api$1
|
||||
});
|
||||
vueApp.use(PrimeVue, {
|
||||
unstyled: true,
|
||||
@@ -16359,7 +16522,7 @@ function createLoraCyclerWidget(node) {
|
||||
const vueApp = createApp(LoraCyclerWidget, {
|
||||
widget,
|
||||
node,
|
||||
api
|
||||
api: api$1
|
||||
});
|
||||
vueApp.use(PrimeVue, {
|
||||
unstyled: true,
|
||||
@@ -16547,13 +16710,13 @@ function applyAutocompleteTextLayoutFix(widget, _container, isVueMode) {
|
||||
}
|
||||
const initVueDomModeListener = () => {
|
||||
var _a2, _b;
|
||||
if ((_b = (_a2 = app.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.addEventListener) {
|
||||
app.ui.settings.addEventListener("Comfy.VueNodes.Enabled.change", () => {
|
||||
if ((_b = (_a2 = app$1.ui) == null ? void 0 : _a2.settings) == null ? void 0 : _b.addEventListener) {
|
||||
app$1.ui.settings.addEventListener("Comfy.VueNodes.Enabled.change", () => {
|
||||
requestAnimationFrame(() => {
|
||||
var _a3, _b2, _c, _d, _e2, _f;
|
||||
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.graph) == null ? void 0 : _d.nodes) {
|
||||
for (const node of app.graph.nodes) {
|
||||
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;
|
||||
if ((_d = app$1.graph) == null ? void 0 : _d.nodes) {
|
||||
for (const node of app$1.graph.nodes) {
|
||||
const textWidget = (_e2 = node.widgets) == null ? void 0 : _e2.find(
|
||||
(w2) => w2.type === "AUTOCOMPLETE_TEXT_LORAS"
|
||||
);
|
||||
@@ -16568,7 +16731,7 @@ const initVueDomModeListener = () => {
|
||||
const grid = nodeEl.querySelector('[data-testid="node-widgets"]');
|
||||
if (!grid) continue;
|
||||
const nodeId = nodeEl.getAttribute("data-node-id");
|
||||
const node = (_a4 = app.graph) == null ? void 0 : _a4.getNodeById(nodeId);
|
||||
const node = (_a4 = app$1.graph) == null ? void 0 : _a4.getNodeById(nodeId);
|
||||
if (!node) continue;
|
||||
const rows = [];
|
||||
let needsFix = false;
|
||||
@@ -16589,7 +16752,7 @@ const initVueDomModeListener = () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
(_f = app.canvas) == null ? void 0 : _f.setDirty(true, true);
|
||||
(_f = app$1.canvas) == null ? void 0 : _f.setDirty(true, true);
|
||||
document.dispatchEvent(new CustomEvent("lora-manager:vue-mode-change", {
|
||||
detail: { isVueDomMode }
|
||||
}));
|
||||
@@ -16597,12 +16760,12 @@ const initVueDomModeListener = () => {
|
||||
});
|
||||
}
|
||||
};
|
||||
if ((_a = app.ui) == null ? void 0 : _a.settings) {
|
||||
if ((_a = app$1.ui) == null ? void 0 : _a.settings) {
|
||||
initVueDomModeListener();
|
||||
} else {
|
||||
const checkAppReady = setInterval(() => {
|
||||
var _a2;
|
||||
if ((_a2 = app.ui) == null ? void 0 : _a2.settings) {
|
||||
if ((_a2 = app$1.ui) == null ? void 0 : _a2.settings) {
|
||||
initVueDomModeListener();
|
||||
clearInterval(checkAppReady);
|
||||
}
|
||||
@@ -16641,8 +16804,8 @@ function createLoraInfoWidget(node) {
|
||||
const vueApp = createApp(LoraInfoWidget, {
|
||||
widget,
|
||||
node,
|
||||
api,
|
||||
app,
|
||||
api: api$1,
|
||||
app: app$1,
|
||||
isVueMode: typeof LiteGraph !== "undefined" && LiteGraph.vueNodesMode
|
||||
});
|
||||
vueApp.use(PrimeVue, {
|
||||
@@ -16737,7 +16900,7 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
|
||||
}
|
||||
);
|
||||
widget.metadataWidget = metadataWidget;
|
||||
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 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 maxHeight = modelType === "loras" ? AUTOCOMPLETE_TEXT_WIDGET_MAX_HEIGHT : void 0;
|
||||
const vueApp = createApp(AutocompleteTextWidget, {
|
||||
widget,
|
||||
@@ -16775,7 +16938,7 @@ function createAutocompleteTextWidgetFactory(node, widgetName, modelType, inputO
|
||||
const minHeight = modelType === "loras" ? void 0 : AUTOCOMPLETE_TEXT_MIN_HEIGHT_DEFAULT;
|
||||
return { widget, minWidth, minHeight };
|
||||
}
|
||||
app.registerExtension({
|
||||
app$1.registerExtension({
|
||||
name: "LoraManager.VueWidgets",
|
||||
getCustomWidgets() {
|
||||
return {
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user