mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 11:11:26 -03:00
fix(autocomplete): never show a stale /activefilters Tip when the toggle is on
The one-time dropdown Tip ('type /activefilters ...') was inserted while the
active-filters search was OFF and then stuck around: _maybeShowFirstRunHint
bailed out while a hint element existed, so toggling the search ON from the
node's filter chip (which can keep the dropdown open in ComfyUI) left the
enable-hint visible even though the feature was now active.
- _maybeShowFirstRunHint now removes any existing hint and recomputes from the
live setting on every dropdown render, so a Tip is never kept for the
previous state.
- AutoComplete listens for the lora-manager:setting-toggled window event and
refreshes an open dropdown's state hints (Tip in plain suggestions, footer
text in the slash command list) immediately when the toggle changes, instead
of waiting for the next hide/show cycle.
Regression tests cover: stale Tip removed when toggled ON while the dropdown
stays open (and not resurrected by further typing), and the command-list
footer flipping to ON.
This commit is contained in:
@@ -286,4 +286,93 @@ const autoComplete = new AutoComplete(input, 'loras', { showPreview: false, minC
|
|||||||
window.removeEventListener('lora-manager:setting-toggled', listener);
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import {
|
|||||||
getAutocompleteAppendCommaPreference,
|
getAutocompleteAppendCommaPreference,
|
||||||
getAutocompleteAutoFormatPreference,
|
getAutocompleteAutoFormatPreference,
|
||||||
getAutocompleteAcceptKeyPreference,
|
getAutocompleteAcceptKeyPreference,
|
||||||
|
LORA_ACTIVE_FILTERS_AUTOCOMPLETE_SETTING_ID,
|
||||||
getLoraActiveFiltersAutocompletePreference,
|
getLoraActiveFiltersAutocompletePreference,
|
||||||
getPromptTagAutocompletePreference,
|
getPromptTagAutocompletePreference,
|
||||||
getTagSpaceReplacementPreference,
|
getTagSpaceReplacementPreference,
|
||||||
|
SETTING_TOGGLED_EVENT_NAME,
|
||||||
setLoraManagerSettingValue,
|
setLoraManagerSettingValue,
|
||||||
} from "./settings.js";
|
} from "./settings.js";
|
||||||
import { showToast } from "./utils.js";
|
import { showToast } from "./utils.js";
|
||||||
@@ -607,6 +609,7 @@ class AutoComplete {
|
|||||||
this.onBlur = null;
|
this.onBlur = null;
|
||||||
this.onDocumentClick = null;
|
this.onDocumentClick = null;
|
||||||
this.onScroll = null;
|
this.onScroll = null;
|
||||||
|
this.onSettingToggled = null;
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
@@ -774,6 +777,22 @@ class AutoComplete {
|
|||||||
};
|
};
|
||||||
document.addEventListener('click', this.onDocumentClick);
|
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
|
// Mark this element as having autocomplete events bound
|
||||||
this.inputElement._autocompleteEventsBound = true;
|
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
|
* Show a one-time, dismissible hint inside the dropdown surfacing the
|
||||||
* toggle commands: prompt nodes advertise /noautocomplete, loras nodes
|
* toggle commands: prompt nodes advertise /noautocomplete, loras nodes
|
||||||
* advertise /activefilters. Dismissal is persisted in localStorage.
|
* advertise /activefilters. Dismissal is persisted in localStorage.
|
||||||
*/
|
*/
|
||||||
_maybeShowFirstRunHint() {
|
_maybeShowFirstRunHint() {
|
||||||
if (this.firstRunHint) {
|
// Dropdown decorations can outlive a state change (the dropdown may
|
||||||
return;
|
// 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 hintText = null;
|
||||||
let storageKey = null;
|
let storageKey = null;
|
||||||
@@ -3178,6 +3219,11 @@ class AutoComplete {
|
|||||||
this.onDocumentClick = null;
|
this.onDocumentClick = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.onSettingToggled) {
|
||||||
|
window.removeEventListener(SETTING_TOGGLED_EVENT_NAME, this.onSettingToggled);
|
||||||
|
this.onSettingToggled = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.onScroll && this.scrollContainer) {
|
if (this.onScroll && this.scrollContainer) {
|
||||||
this.scrollContainer.removeEventListener('scroll', this.onScroll);
|
this.scrollContainer.removeEventListener('scroll', this.onScroll);
|
||||||
this.onScroll = null;
|
this.onScroll = null;
|
||||||
|
|||||||
Reference in New Issue
Block a user