mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-24 14:31:26 -03:00
074d1f2e51
- Add Tag Autocomplete ON/OFF entry to the Prompt (LoraManager) node right-click menu, cross-referencing the slash commands - Show the current autocomplete state (/autocomplete or /noautocomplete hint) below the slash command list - Show a one-time dismissible tip in the suggestion dropdown on first use - Clarify toggle command labels (Turn autocomplete ON/OFF) and cross-link all three entry points in the settings tooltip - Share the setting write path via setLoraManagerSettingValue()
161 lines
7.0 KiB
JavaScript
161 lines
7.0 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import {
|
|
PROMPT_TAG_AUTOCOMPLETE_SETTING_ID,
|
|
getPromptTagAutocompletePreference,
|
|
setLoraManagerSettingValue,
|
|
} from "./settings.js";
|
|
import { showToast } from "./utils.js";
|
|
|
|
/**
|
|
* Extension for PromptLM node to support dynamic trigger_words inputs.
|
|
* Based on the dynamic input pattern from Impact Pack's Switch (Any) node.
|
|
*/
|
|
app.registerExtension({
|
|
name: "Comfy.LoraManager.PromptLM",
|
|
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name !== "Prompt (LoraManager)") {
|
|
return;
|
|
}
|
|
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
|
|
|
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info) {
|
|
const stackTrace = new Error().stack;
|
|
|
|
// Skip during graph loading/pasting to avoid interference
|
|
if (stackTrace.includes('loadGraphData') || stackTrace.includes('pasteFromClipboard')) {
|
|
return onConnectionsChange?.apply?.(this, arguments);
|
|
}
|
|
|
|
// Skip subgraph operations
|
|
if (stackTrace.includes('convertToSubgraph') || stackTrace.includes('Subgraph.configure')) {
|
|
return onConnectionsChange?.apply?.(this, arguments);
|
|
}
|
|
|
|
if (!link_info) {
|
|
return onConnectionsChange?.apply?.(this, arguments);
|
|
}
|
|
|
|
// Handle input connections (type === 1)
|
|
if (type === 1) {
|
|
const input = this.inputs[index];
|
|
|
|
// Only process trigger_words inputs
|
|
if (!input || !input.name.startsWith('trigger_words')) {
|
|
return onConnectionsChange?.apply?.(this, arguments);
|
|
}
|
|
|
|
// Count existing trigger_words inputs
|
|
let triggerWordCount = 0;
|
|
for (const inp of this.inputs) {
|
|
if (inp.name.startsWith('trigger_words')) {
|
|
triggerWordCount++;
|
|
}
|
|
}
|
|
|
|
// Renumber all trigger_words inputs sequentially
|
|
let slotIndex = 1;
|
|
for (const inp of this.inputs) {
|
|
if (inp.name.startsWith('trigger_words')) {
|
|
inp.name = `trigger_words${slotIndex}`;
|
|
slotIndex++;
|
|
}
|
|
}
|
|
|
|
// Add new input slot if connected and this was the last one
|
|
if (connected) {
|
|
const lastTriggerIndex = triggerWordCount;
|
|
if (index === lastTriggerIndex || index === this.inputs.findIndex(i => i.name === `trigger_words${lastTriggerIndex}`)) {
|
|
this.addInput(`trigger_words${slotIndex}`, "STRING", {
|
|
forceInput: true,
|
|
tooltip: "Trigger words to prepend. Connect to add more inputs."
|
|
});
|
|
}
|
|
}
|
|
|
|
// Remove disconnected empty input slots (but keep at least one)
|
|
if (!connected && triggerWordCount > 1) {
|
|
// Check if this input is now empty and can be removed
|
|
const disconnectedInput = this.inputs[index];
|
|
if (disconnectedInput && disconnectedInput.name.startsWith('trigger_words')) {
|
|
// Only remove if it has no link and is not the last trigger_words input
|
|
const isLastTriggerSlot = index === this.inputs.findLastIndex(i => i.name.startsWith('trigger_words'));
|
|
if (!isLastTriggerSlot && !disconnectedInput.link) {
|
|
this.removeInput(index);
|
|
|
|
// Renumber again after removal
|
|
let newSlotIndex = 1;
|
|
for (const inp of this.inputs) {
|
|
if (inp.name.startsWith('trigger_words')) {
|
|
inp.name = `trigger_words${newSlotIndex}`;
|
|
newSlotIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return onConnectionsChange?.apply?.(this, arguments);
|
|
};
|
|
|
|
// Expose the tag autocomplete toggle in the node's right-click menu so
|
|
// users can discover the switch where the behavior actually happens,
|
|
// instead of only via slash commands or the global settings dialog.
|
|
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
|
|
nodeType.prototype.getExtraMenuOptions = function(_, options) {
|
|
getExtraMenuOptions?.apply?.(this, arguments);
|
|
|
|
options.push(null);
|
|
|
|
const autocompleteEnabled = getPromptTagAutocompletePreference();
|
|
options.push({
|
|
content: autocompleteEnabled
|
|
? "Tag Autocomplete: ON (/noautocomplete to disable)"
|
|
: "Tag Autocomplete: OFF (/autocomplete to enable)",
|
|
callback: async () => {
|
|
const newValue = !autocompleteEnabled;
|
|
try {
|
|
const success = await setLoraManagerSettingValue(PROMPT_TAG_AUTOCOMPLETE_SETTING_ID, newValue);
|
|
if (!success) {
|
|
throw new Error("settings API unavailable");
|
|
}
|
|
showToast({
|
|
severity: newValue ? 'success' : 'secondary',
|
|
summary: newValue ? 'Autocomplete Enabled' : 'Autocomplete Disabled',
|
|
detail: newValue
|
|
? 'Tag autocomplete is now ON. Type to see suggestions.'
|
|
: 'Tag autocomplete is now OFF. Type /autocomplete in the prompt field to re-enable.',
|
|
life: 3000
|
|
});
|
|
} catch (error) {
|
|
console.error('[Lora Manager] Failed to toggle setting:', error);
|
|
showToast({
|
|
severity: 'error',
|
|
summary: 'Error',
|
|
detail: 'Failed to toggle autocomplete setting',
|
|
life: 3000
|
|
});
|
|
}
|
|
}
|
|
});
|
|
};
|
|
},
|
|
|
|
nodeCreated(node, app) {
|
|
if (node.comfyClass !== "Prompt (LoraManager)") {
|
|
return;
|
|
}
|
|
|
|
// Ensure at least one trigger_words input exists on creation
|
|
const hasTriggerWords = node.inputs.some(inp => inp.name.startsWith('trigger_words'));
|
|
if (!hasTriggerWords) {
|
|
node.addInput("trigger_words1", "STRING", {
|
|
forceInput: true,
|
|
tooltip: "Trigger words to prepend. Connect to add more inputs."
|
|
});
|
|
}
|
|
}
|
|
});
|