mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-22 13:42:12 -03:00
Create unified settings.js extension to centralize all Lora Manager ComfyUI settings registration, eliminating code duplication across multiple files. Add new setting "Enable Custom Words Autocomplete in Prompt Nodes" (enabled by default) to control custom words autocomplete in prompt node text widgets. When disabled, only 'emb:' prefix triggers embeddings autocomplete. Changes: - Create web/comfyui/settings.js with all three settings: * Trigger Word Wheel Sensitivity (existing) * Auto path correction (existing) * Enable Custom Words Autocomplete in Prompt Nodes (new) - Refactor autocomplete.js to respect the new setting - Update trigger_word_toggle.js to import from settings.js - Update usage_stats.js to import from settings.js
298 lines
12 KiB
JavaScript
298 lines
12 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { api } from "../../scripts/api.js";
|
|
import { CONVERTED_TYPE, getNodeFromGraph } from "./utils.js";
|
|
import { addTagsWidget } from "./tags_widget.js";
|
|
import { getWheelSensitivity } from "./settings.js";
|
|
|
|
// TriggerWordToggle extension for ComfyUI
|
|
app.registerExtension({
|
|
name: "LoraManager.TriggerWordToggle",
|
|
|
|
setup() {
|
|
// Add message handler to listen for messages from Python
|
|
api.addEventListener("trigger_word_update", (event) => {
|
|
const { id, graph_id: graphId, message } = event.detail;
|
|
this.handleTriggerWordUpdate(id, graphId, message);
|
|
});
|
|
},
|
|
|
|
async nodeCreated(node) {
|
|
if (node.comfyClass === "TriggerWord Toggle (LoraManager)") {
|
|
// Enable widget serialization
|
|
node.serialize_widgets = true;
|
|
|
|
node.addInput("trigger_words", 'string', {
|
|
"shape": 7 // 7 is the shape of the optional input
|
|
});
|
|
|
|
// Wait for node to be properly initialized
|
|
requestAnimationFrame(async () => {
|
|
// Get the wheel sensitivity setting
|
|
const wheelSensitivity = getWheelSensitivity();
|
|
const groupModeWidget = node.widgets[0];
|
|
const defaultActiveWidget = node.widgets[1];
|
|
const strengthAdjustmentWidget = node.widgets[2];
|
|
const initialStrengthAdjustment = Boolean(strengthAdjustmentWidget?.value);
|
|
|
|
// Get the widget object directly from the returned object
|
|
const result = addTagsWidget(node, "toggle_trigger_words", {
|
|
defaultVal: []
|
|
}, null, wheelSensitivity, {
|
|
allowStrengthAdjustment: initialStrengthAdjustment
|
|
});
|
|
|
|
node.tagWidget = result.widget;
|
|
node.tagWidget.allowStrengthAdjustment = initialStrengthAdjustment;
|
|
|
|
const normalizeTagText = (text) =>
|
|
(typeof text === 'string' ? text.trim().toLowerCase() : '');
|
|
|
|
const collectHighlightTokens = (wordsArray) => {
|
|
const tokens = new Set();
|
|
|
|
const addToken = (text) => {
|
|
const normalized = normalizeTagText(text);
|
|
if (normalized) {
|
|
tokens.add(normalized);
|
|
}
|
|
};
|
|
|
|
wordsArray.forEach((rawWord) => {
|
|
if (typeof rawWord !== 'string') {
|
|
return;
|
|
}
|
|
|
|
addToken(rawWord);
|
|
|
|
const groupParts = rawWord.split(/,{2,}/);
|
|
groupParts.forEach((groupPart) => {
|
|
addToken(groupPart);
|
|
groupPart.split(',').forEach(addToken);
|
|
});
|
|
|
|
rawWord.split(',').forEach(addToken);
|
|
});
|
|
|
|
return tokens;
|
|
};
|
|
|
|
const applyHighlightState = () => {
|
|
if (!node.tagWidget) return;
|
|
const highlightSet = node._highlightedTriggerWords || new Set();
|
|
const updatedTags = (node.tagWidget.value || []).map(tag => ({
|
|
...tag,
|
|
highlighted: highlightSet.size > 0 && highlightSet.has(normalizeTagText(tag.text))
|
|
}));
|
|
node.tagWidget.value = updatedTags;
|
|
};
|
|
|
|
node.highlightTriggerWords = (triggerWords) => {
|
|
const wordsArray = Array.isArray(triggerWords)
|
|
? triggerWords
|
|
: triggerWords
|
|
? [triggerWords]
|
|
: [];
|
|
node._highlightedTriggerWords = collectHighlightTokens(wordsArray);
|
|
applyHighlightState();
|
|
};
|
|
|
|
if (node.__pendingHighlightWords !== undefined) {
|
|
const pending = node.__pendingHighlightWords;
|
|
delete node.__pendingHighlightWords;
|
|
node.highlightTriggerWords(pending);
|
|
}
|
|
|
|
node.applyTriggerHighlightState = applyHighlightState;
|
|
|
|
// Add hidden widget to store original message
|
|
const hiddenWidget = node.addWidget('text', 'orinalMessage', '');
|
|
hiddenWidget.type = CONVERTED_TYPE;
|
|
hiddenWidget.hidden = true;
|
|
hiddenWidget.computeSize = () => [0, -4];
|
|
node.originalMessageWidget = hiddenWidget;
|
|
|
|
// Restore saved value if exists
|
|
const tagWidgetIndex = node.widgets.indexOf(result.widget);
|
|
const originalMessageWidgetIndex = node.widgets.indexOf(hiddenWidget);
|
|
if (node.widgets_values && node.widgets_values.length > 0) {
|
|
if (tagWidgetIndex >= 0) {
|
|
const savedValue = node.widgets_values[tagWidgetIndex];
|
|
if (savedValue) {
|
|
result.widget.value = Array.isArray(savedValue) ? savedValue : [];
|
|
}
|
|
}
|
|
if (originalMessageWidgetIndex >= 0) {
|
|
const originalMessage = node.widgets_values[originalMessageWidgetIndex];
|
|
if (originalMessage) {
|
|
hiddenWidget.value = originalMessage;
|
|
}
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(() => node.applyTriggerHighlightState?.());
|
|
|
|
groupModeWidget.callback = (value) => {
|
|
if (node.originalMessageWidget?.value) {
|
|
this.updateTagsBasedOnMode(
|
|
node,
|
|
node.originalMessageWidget.value,
|
|
value,
|
|
Boolean(strengthAdjustmentWidget?.value)
|
|
);
|
|
}
|
|
}
|
|
|
|
// Add callback for default_active widget
|
|
defaultActiveWidget.callback = (value) => {
|
|
// Set all existing tags' active state to the new value
|
|
if (node.tagWidget && node.tagWidget.value) {
|
|
const updatedTags = node.tagWidget.value.map(tag => ({
|
|
...tag,
|
|
active: value
|
|
}));
|
|
node.tagWidget.value = updatedTags;
|
|
node.applyTriggerHighlightState?.();
|
|
}
|
|
}
|
|
|
|
if (strengthAdjustmentWidget) {
|
|
strengthAdjustmentWidget.callback = (value) => {
|
|
const allowStrengthAdjustment = Boolean(value);
|
|
if (node.tagWidget) {
|
|
node.tagWidget.allowStrengthAdjustment = allowStrengthAdjustment;
|
|
}
|
|
this.updateTagsBasedOnMode(
|
|
node,
|
|
node.originalMessageWidget?.value || "",
|
|
groupModeWidget?.value ?? false,
|
|
allowStrengthAdjustment
|
|
);
|
|
};
|
|
}
|
|
|
|
// Override the serializeValue method to properly format trigger words with strength
|
|
const originalSerializeValue = result.widget.serializeValue;
|
|
result.widget.serializeValue = function() {
|
|
const value = this.value || [];
|
|
// Transform the values to include strength in the proper format
|
|
const transformedValue = value.map(tag => {
|
|
// If strength is defined (even if it's 1.0), format as {text: "(original_text:strength)", ...}
|
|
if (tag.strength !== undefined && tag.strength !== null) {
|
|
return {
|
|
...tag,
|
|
text: `(${tag.text}:${tag.strength.toFixed(2)})`
|
|
};
|
|
}
|
|
return tag;
|
|
});
|
|
return transformedValue;
|
|
};
|
|
});
|
|
}
|
|
},
|
|
|
|
// Handle trigger word updates from Python
|
|
handleTriggerWordUpdate(id, graphId, message) {
|
|
const node = getNodeFromGraph(graphId, id);
|
|
if (!node || node.comfyClass !== "TriggerWord Toggle (LoraManager)") {
|
|
console.warn("Node not found or not a TriggerWordToggle:", id);
|
|
return;
|
|
}
|
|
|
|
// Store the original message for mode switching
|
|
if (node.originalMessageWidget) {
|
|
node.originalMessageWidget.value = message;
|
|
}
|
|
|
|
if (node.tagWidget) {
|
|
// Parse tags based on current group mode
|
|
const groupMode = node.widgets[0] ? node.widgets[0].value : false;
|
|
const allowStrengthAdjustment = Boolean(node.widgets[2]?.value);
|
|
node.tagWidget.allowStrengthAdjustment = allowStrengthAdjustment;
|
|
this.updateTagsBasedOnMode(node, message, groupMode, allowStrengthAdjustment);
|
|
}
|
|
},
|
|
|
|
// Update tags display based on group mode
|
|
updateTagsBasedOnMode(node, message, groupMode, allowStrengthAdjustment = false) {
|
|
if (!node.tagWidget) return;
|
|
node.tagWidget.allowStrengthAdjustment = allowStrengthAdjustment;
|
|
|
|
const existingTags = node.tagWidget.value || [];
|
|
const existingTagState = existingTags.reduce((acc, tag) => {
|
|
const key = tag.text;
|
|
if (!acc[key]) {
|
|
acc[key] = [];
|
|
}
|
|
acc[key].push({
|
|
active: tag.active,
|
|
strength: allowStrengthAdjustment ? tag.strength : null,
|
|
});
|
|
return acc;
|
|
}, {});
|
|
const consumeExistingState = (text) => {
|
|
const states = existingTagState[text];
|
|
if (states && states.length > 0) {
|
|
return states.shift();
|
|
}
|
|
return null;
|
|
};
|
|
|
|
// Get default active state from the widget
|
|
const defaultActive = node.widgets[1] ? node.widgets[1].value : true;
|
|
|
|
let tagArray = [];
|
|
|
|
if (groupMode) {
|
|
if (message.trim() === '') {
|
|
tagArray = [];
|
|
}
|
|
// Group mode: split by ',,' and treat each group as a single tag
|
|
else if (message.includes(',,')) {
|
|
const groups = message.split(/,{2,}/); // Match 2 or more consecutive commas
|
|
tagArray = groups
|
|
.map(group => group.trim())
|
|
.filter(group => group)
|
|
.map(group => {
|
|
// Check if this group already exists with strength info
|
|
const existing = consumeExistingState(group);
|
|
return {
|
|
text: group,
|
|
// Use existing values if available, otherwise use defaults
|
|
active: existing ? existing.active : defaultActive,
|
|
strength: existing ? existing.strength : null
|
|
};
|
|
});
|
|
} else {
|
|
// If no ',,' delimiter, treat the entire message as one group
|
|
const existing = consumeExistingState(message.trim());
|
|
tagArray = [{
|
|
text: message.trim(),
|
|
// Use existing values if available, otherwise use defaults
|
|
active: existing ? existing.active : defaultActive,
|
|
strength: existing ? existing.strength : null
|
|
}];
|
|
}
|
|
} else {
|
|
// Normal mode: split by commas and treat each word as a separate tag
|
|
tagArray = message
|
|
.split(',')
|
|
.map(word => word.trim())
|
|
.filter(word => word)
|
|
.map(word => {
|
|
// Check if this word already exists with strength info
|
|
const existing = consumeExistingState(word);
|
|
return {
|
|
text: word,
|
|
// Use existing values if available, otherwise use defaults
|
|
active: existing ? existing.active : defaultActive,
|
|
strength: existing ? existing.strength : null
|
|
};
|
|
});
|
|
}
|
|
|
|
node.tagWidget.value = tagArray;
|
|
node.applyTriggerHighlightState?.();
|
|
}
|
|
});
|