mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
feat(autocomplete): add toggle commands for autocomplete setting
- Add `/ac` and `/noac` commands to toggle prompt tag autocomplete on/off - Commands only appear when relevant (e.g., `/ac` shows when autocomplete is off) - Show toast notification when toggling setting - Use ComfyUI's setting API with fallback to legacy API - Clear autocomplete token after toggling to provide clean UX
This commit is contained in:
@@ -2,6 +2,7 @@ import { api } from "../../scripts/api.js";
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { TextAreaCaretHelper } from "./textarea_caret_helper.js";
|
import { TextAreaCaretHelper } from "./textarea_caret_helper.js";
|
||||||
import { getPromptTagAutocompletePreference, getTagSpaceReplacementPreference } from "./settings.js";
|
import { getPromptTagAutocompletePreference, getTagSpaceReplacementPreference } from "./settings.js";
|
||||||
|
import { showToast } from "./utils.js";
|
||||||
|
|
||||||
// Command definitions for category filtering
|
// Command definitions for category filtering
|
||||||
const TAG_COMMANDS = {
|
const TAG_COMMANDS = {
|
||||||
@@ -15,6 +16,21 @@ const TAG_COMMANDS = {
|
|||||||
'/lore': { categories: [15], label: 'Lore' },
|
'/lore': { categories: [15], label: 'Lore' },
|
||||||
'/emb': { type: 'embedding', label: 'Embeddings' },
|
'/emb': { type: 'embedding', label: 'Embeddings' },
|
||||||
'/embedding': { type: 'embedding', label: 'Embeddings' },
|
'/embedding': { type: 'embedding', label: 'Embeddings' },
|
||||||
|
// Autocomplete toggle commands - only show one based on current state
|
||||||
|
'/ac': {
|
||||||
|
type: 'toggle_setting',
|
||||||
|
settingId: 'loramanager.prompt_tag_autocomplete',
|
||||||
|
value: true,
|
||||||
|
label: 'Autocomplete: ON',
|
||||||
|
condition: () => !getPromptTagAutocompletePreference()
|
||||||
|
},
|
||||||
|
'/noac': {
|
||||||
|
type: 'toggle_setting',
|
||||||
|
settingId: 'loramanager.prompt_tag_autocomplete',
|
||||||
|
value: false,
|
||||||
|
label: 'Autocomplete: OFF',
|
||||||
|
condition: () => getPromptTagAutocompletePreference()
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Category display information
|
// Category display information
|
||||||
@@ -488,6 +504,10 @@ class AutoComplete {
|
|||||||
this.searchType = 'commands';
|
this.searchType = 'commands';
|
||||||
this._showCommandList(commandResult.commandFilter);
|
this._showCommandList(commandResult.commandFilter);
|
||||||
return;
|
return;
|
||||||
|
} else if (commandResult.command?.type === 'toggle_setting') {
|
||||||
|
// Handle toggle setting command (/ac, /noac)
|
||||||
|
this._handleToggleSettingCommand(commandResult.command);
|
||||||
|
return;
|
||||||
} else if (commandResult.command) {
|
} else if (commandResult.command) {
|
||||||
// Command is active, use filtered search
|
// Command is active, use filtered search
|
||||||
this.showingCommands = false;
|
this.showingCommands = false;
|
||||||
@@ -606,9 +626,14 @@ class AutoComplete {
|
|||||||
|
|
||||||
// Check for exact command match
|
// Check for exact command match
|
||||||
if (TAG_COMMANDS[partialCommand]) {
|
if (TAG_COMMANDS[partialCommand]) {
|
||||||
|
const cmd = TAG_COMMANDS[partialCommand];
|
||||||
|
// Filter out toggle commands that don't meet their condition
|
||||||
|
if (cmd.type === 'toggle_setting' && cmd.condition && !cmd.condition()) {
|
||||||
|
return { showCommands: false, command: null, searchTerm: '' };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
showCommands: false,
|
showCommands: false,
|
||||||
command: TAG_COMMANDS[partialCommand],
|
command: cmd,
|
||||||
searchTerm: '',
|
searchTerm: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -627,9 +652,14 @@ class AutoComplete {
|
|||||||
const searchPart = trimmed.slice(spaceIndex + 1).trim();
|
const searchPart = trimmed.slice(spaceIndex + 1).trim();
|
||||||
|
|
||||||
if (TAG_COMMANDS[commandPart]) {
|
if (TAG_COMMANDS[commandPart]) {
|
||||||
|
const cmd = TAG_COMMANDS[commandPart];
|
||||||
|
// Filter out toggle commands that don't meet their condition
|
||||||
|
if (cmd.type === 'toggle_setting' && cmd.condition && !cmd.condition()) {
|
||||||
|
return { showCommands: false, command: null, searchTerm: trimmed };
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
showCommands: false,
|
showCommands: false,
|
||||||
command: TAG_COMMANDS[commandPart],
|
command: cmd,
|
||||||
searchTerm: searchPart,
|
searchTerm: searchPart,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -652,6 +682,11 @@ class AutoComplete {
|
|||||||
for (const [cmd, info] of Object.entries(TAG_COMMANDS)) {
|
for (const [cmd, info] of Object.entries(TAG_COMMANDS)) {
|
||||||
if (seenLabels.has(info.label)) continue;
|
if (seenLabels.has(info.label)) continue;
|
||||||
|
|
||||||
|
// Filter out toggle commands that don't meet their condition
|
||||||
|
if (info.type === 'toggle_setting' && info.condition) {
|
||||||
|
if (!info.condition()) continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!filter || cmd.slice(1).startsWith(filterLower)) {
|
if (!filter || cmd.slice(1).startsWith(filterLower)) {
|
||||||
seenLabels.add(info.label);
|
seenLabels.add(info.label);
|
||||||
commands.push({ command: cmd, ...info });
|
commands.push({ command: cmd, ...info });
|
||||||
@@ -1175,6 +1210,119 @@ class AutoComplete {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle toggle setting command (/ac, /noac)
|
||||||
|
* @param {Object} command - The toggle command with settingId and value
|
||||||
|
*/
|
||||||
|
async _handleToggleSettingCommand(command) {
|
||||||
|
const { settingId, value } = command;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use ComfyUI's setting API to update global setting
|
||||||
|
const settingManager = app?.extensionManager?.setting;
|
||||||
|
if (settingManager && typeof settingManager.set === 'function') {
|
||||||
|
await settingManager.set(settingId, value);
|
||||||
|
this._showToggleFeedback(value);
|
||||||
|
this._clearCurrentToken();
|
||||||
|
} else {
|
||||||
|
// Fallback: use legacy settings API
|
||||||
|
const setting = app.ui.settings.settingsById?.[settingId];
|
||||||
|
if (setting) {
|
||||||
|
app.ui.settings.setSettingValue(settingId, value);
|
||||||
|
this._showToggleFeedback(value);
|
||||||
|
this._clearCurrentToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Lora Manager] Failed to toggle setting:', error);
|
||||||
|
showToast({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Error',
|
||||||
|
detail: 'Failed to toggle autocomplete setting',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show visual feedback for toggle action using toast
|
||||||
|
* @param {boolean} enabled - New autocomplete state
|
||||||
|
*/
|
||||||
|
_showToggleFeedback(enabled) {
|
||||||
|
showToast({
|
||||||
|
severity: enabled ? 'success' : 'secondary',
|
||||||
|
summary: enabled ? 'Autocomplete Enabled' : 'Autocomplete Disabled',
|
||||||
|
detail: enabled
|
||||||
|
? 'Tag autocomplete is now ON. Type to see suggestions.'
|
||||||
|
: 'Tag autocomplete is now OFF. Use /ac to re-enable.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the current command token from input
|
||||||
|
* Preserves leading spaces after delimiters (e.g., "1girl, /ac" -> "1girl, ")
|
||||||
|
*/
|
||||||
|
_clearCurrentToken() {
|
||||||
|
const currentValue = this.inputElement.value;
|
||||||
|
const caretPos = this.inputElement.selectionStart;
|
||||||
|
|
||||||
|
// Find the command text before cursor
|
||||||
|
const beforeCursor = currentValue.substring(0, caretPos);
|
||||||
|
const segments = beforeCursor.split(/[,\>]+/);
|
||||||
|
const lastSegment = segments[segments.length - 1] || '';
|
||||||
|
|
||||||
|
// Find the command start position, preserving leading spaces
|
||||||
|
// lastSegment includes leading spaces (e.g., " /ac"), find where command actually starts
|
||||||
|
const commandMatch = lastSegment.match(/^(\s*)(\/\w+)/);
|
||||||
|
if (commandMatch) {
|
||||||
|
// commandMatch[1] is leading spaces, commandMatch[2] is the command
|
||||||
|
const leadingSpaces = commandMatch[1].length;
|
||||||
|
// Keep the spaces by starting after them
|
||||||
|
const commandStartPos = caretPos - lastSegment.length + leadingSpaces;
|
||||||
|
|
||||||
|
// Skip trailing spaces when deleting
|
||||||
|
let endPos = caretPos;
|
||||||
|
while (endPos < currentValue.length && currentValue[endPos] === ' ') {
|
||||||
|
endPos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = currentValue.substring(0, commandStartPos) + currentValue.substring(endPos);
|
||||||
|
const newCaretPos = commandStartPos;
|
||||||
|
|
||||||
|
this.inputElement.value = newValue;
|
||||||
|
|
||||||
|
// Trigger input event to notify about the change
|
||||||
|
const event = new Event('input', { bubbles: true });
|
||||||
|
this.inputElement.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Focus back to input and position cursor
|
||||||
|
this.inputElement.focus();
|
||||||
|
this.inputElement.setSelectionRange(newCaretPos, newCaretPos);
|
||||||
|
} else {
|
||||||
|
// Fallback: delete the whole last segment (original behavior)
|
||||||
|
const commandStartPos = caretPos - lastSegment.length;
|
||||||
|
|
||||||
|
let endPos = caretPos;
|
||||||
|
while (endPos < currentValue.length && currentValue[endPos] === ' ') {
|
||||||
|
endPos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = currentValue.substring(0, commandStartPos) + currentValue.substring(endPos);
|
||||||
|
const newCaretPos = commandStartPos;
|
||||||
|
|
||||||
|
this.inputElement.value = newValue;
|
||||||
|
|
||||||
|
const event = new Event('input', { bubbles: true });
|
||||||
|
this.inputElement.dispatchEvent(event);
|
||||||
|
|
||||||
|
this.inputElement.focus();
|
||||||
|
this.inputElement.setSelectionRange(newCaretPos, newCaretPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.debounceTimer) {
|
if (this.debounceTimer) {
|
||||||
clearTimeout(this.debounceTimer);
|
clearTimeout(this.debounceTimer);
|
||||||
|
|||||||
Reference in New Issue
Block a user