mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(ui): add trigger word highlighting for selected LoRAs
- Import applySelectionHighlight in lora_loader and lora_stacker - Pass onSelectionChange callback to loras_widget to handle selection changes - Implement selection tracking and payload building in loras_widget - Emit selection changes when LoRA selection is modified - Update tags_widget to support highlighted tag styling This provides visual feedback when LoRAs are selected by highlighting associated trigger words in the interface.
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { addLorasWidget } from "./loras_widget.js";
|
import { addLorasWidget } from "./loras_widget.js";
|
||||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||||
|
import { applySelectionHighlight } from "./trigger_word_highlight.js";
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "LoraManager.LoraLoader",
|
name: "LoraManager.LoraLoader",
|
||||||
@@ -178,7 +179,10 @@ app.registerExtension({
|
|||||||
this.lorasWidget = addLorasWidget(
|
this.lorasWidget = addLorasWidget(
|
||||||
this,
|
this,
|
||||||
"loras",
|
"loras",
|
||||||
{},
|
{
|
||||||
|
onSelectionChange: (selection) =>
|
||||||
|
applySelectionHighlight(this, selection),
|
||||||
|
},
|
||||||
(value) => {
|
(value) => {
|
||||||
// Prevent recursive calls
|
// Prevent recursive calls
|
||||||
if (isUpdating) return;
|
if (isUpdating) return;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "./utils.js";
|
} from "./utils.js";
|
||||||
import { addLorasWidget } from "./loras_widget.js";
|
import { addLorasWidget } from "./loras_widget.js";
|
||||||
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
import { applyLoraValuesToText, debounce } from "./lora_syntax_utils.js";
|
||||||
|
import { applySelectionHighlight } from "./trigger_word_highlight.js";
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "LoraManager.LoraStacker",
|
name: "LoraManager.LoraStacker",
|
||||||
@@ -84,10 +85,17 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = addLorasWidget(this, "loras", {}, (value) => {
|
const result = addLorasWidget(
|
||||||
// Prevent recursive calls
|
this,
|
||||||
if (isUpdating) return;
|
"loras",
|
||||||
isUpdating = true;
|
{
|
||||||
|
onSelectionChange: (selection) =>
|
||||||
|
applySelectionHighlight(this, selection),
|
||||||
|
},
|
||||||
|
(value) => {
|
||||||
|
// Prevent recursive calls
|
||||||
|
if (isUpdating) return;
|
||||||
|
isUpdating = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update this stacker's direct trigger toggles with its own active loras
|
// Update this stacker's direct trigger toggles with its own active loras
|
||||||
|
|||||||
@@ -29,12 +29,17 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
|
|
||||||
// Initialize default value
|
// Initialize default value
|
||||||
const defaultValue = opts?.defaultVal || [];
|
const defaultValue = opts?.defaultVal || [];
|
||||||
|
const onSelectionChange = typeof opts?.onSelectionChange === "function"
|
||||||
|
? opts.onSelectionChange
|
||||||
|
: null;
|
||||||
|
|
||||||
// Create preview tooltip instance
|
// Create preview tooltip instance
|
||||||
const previewTooltip = new PreviewTooltip({ modelType: "loras" });
|
const previewTooltip = new PreviewTooltip({ modelType: "loras" });
|
||||||
|
|
||||||
// Selection state - only one LoRA can be selected at a time
|
// Selection state - only one LoRA can be selected at a time
|
||||||
let selectedLora = null;
|
let selectedLora = null;
|
||||||
|
let currentLorasData = parseLoraValue(defaultValue);
|
||||||
|
let lastSelectionKey = "__none__";
|
||||||
let pendingFocusTarget = null;
|
let pendingFocusTarget = null;
|
||||||
|
|
||||||
const PREVIEW_SUPPRESSION_AFTER_DRAG_MS = 500;
|
const PREVIEW_SUPPRESSION_AFTER_DRAG_MS = 500;
|
||||||
@@ -60,13 +65,51 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Function to select a LoRA
|
// Function to select a LoRA
|
||||||
const selectLora = (loraName) => {
|
const buildSelectionPayload = (loraName) => {
|
||||||
|
if (!loraName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = currentLorasData.find((lora) => lora.name === loraName);
|
||||||
|
if (!entry) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: entry.name,
|
||||||
|
active: !!entry.active,
|
||||||
|
entry: { ...entry },
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const emitSelectionChange = (payload, options = {}) => {
|
||||||
|
if (!onSelectionChange) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = payload
|
||||||
|
? `${payload.name || ""}|${payload.active ? "1" : "0"}`
|
||||||
|
: "__null__";
|
||||||
|
|
||||||
|
if (!options.force && key === lastSelectionKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSelectionKey = key;
|
||||||
|
onSelectionChange(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectLora = (loraName, options = {}) => {
|
||||||
selectedLora = loraName;
|
selectedLora = loraName;
|
||||||
// Update visual feedback for all entries
|
// Update visual feedback for all entries
|
||||||
container.querySelectorAll('.lm-lora-entry').forEach(entry => {
|
container.querySelectorAll('.lm-lora-entry').forEach(entry => {
|
||||||
const entryLoraName = entry.dataset.loraName;
|
const entryLoraName = entry.dataset.loraName;
|
||||||
updateEntrySelection(entry, entryLoraName === selectedLora);
|
updateEntrySelection(entry, entryLoraName === selectedLora);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!options.silent) {
|
||||||
|
emitSelectionChange(buildSelectionPayload(loraName));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add keyboard event listener to container
|
// Add keyboard event listener to container
|
||||||
@@ -88,6 +131,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
|
|
||||||
// Parse the loras data
|
// Parse the loras data
|
||||||
const lorasData = parseLoraValue(value);
|
const lorasData = parseLoraValue(value);
|
||||||
|
currentLorasData = lorasData;
|
||||||
const focusSequence = [];
|
const focusSequence = [];
|
||||||
|
|
||||||
const updateWidgetValue = (newValue) => {
|
const updateWidgetValue = (newValue) => {
|
||||||
@@ -247,6 +291,14 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
if (loraIndex >= 0) {
|
if (loraIndex >= 0) {
|
||||||
lorasData[loraIndex].active = newActive;
|
lorasData[loraIndex].active = newActive;
|
||||||
|
|
||||||
|
if (selectedLora === name) {
|
||||||
|
emitSelectionChange({
|
||||||
|
name,
|
||||||
|
active: newActive,
|
||||||
|
entry: { ...lorasData[loraIndex] },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const newValue = formatLoraValue(lorasData);
|
const newValue = formatLoraValue(lorasData);
|
||||||
updateWidgetValue(newValue);
|
updateWidgetValue(newValue);
|
||||||
}
|
}
|
||||||
@@ -359,13 +411,13 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
pendingFocusTarget = { name, type: "strength" };
|
pendingFocusTarget = { name, type: "strength" };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle focus
|
// Handle focus
|
||||||
strengthEl.addEventListener('focus', () => {
|
strengthEl.addEventListener('focus', () => {
|
||||||
pendingFocusTarget = null;
|
pendingFocusTarget = null;
|
||||||
// Auto-select all content
|
// Auto-select all content
|
||||||
strengthEl.select();
|
strengthEl.select();
|
||||||
selectLora(name);
|
selectLora(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle input changes
|
// Handle input changes
|
||||||
const commitStrengthValue = () => {
|
const commitStrengthValue = () => {
|
||||||
@@ -577,6 +629,16 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
updateEntrySelection(entry, entryLoraName === selectedLora);
|
updateEntrySelection(entry, entryLoraName === selectedLora);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectionExists = selectedLora
|
||||||
|
? currentLorasData.some((lora) => lora.name === selectedLora)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
if (selectedLora && !selectionExists) {
|
||||||
|
selectLora(null);
|
||||||
|
} else if (selectedLora) {
|
||||||
|
emitSelectionChange(buildSelectionPayload(selectedLora));
|
||||||
|
}
|
||||||
|
|
||||||
if (pendingFocusTarget) {
|
if (pendingFocusTarget) {
|
||||||
const focusTarget = pendingFocusTarget;
|
const focusTarget = pendingFocusTarget;
|
||||||
const safeName = escapeLoraName(focusTarget.name);
|
const safeName = escapeLoraName(focusTarget.name);
|
||||||
@@ -596,7 +658,7 @@ export function addLorasWidget(node, name, opts, callback) {
|
|||||||
if (typeof targetInput.select === "function") {
|
if (typeof targetInput.select === "function") {
|
||||||
targetInput.select();
|
targetInput.select();
|
||||||
}
|
}
|
||||||
selectLora(focusTarget.name);
|
selectLora(focusTarget.name, { silent: true });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,11 +78,11 @@ export function addTagsWidget(node, name, opts, callback) {
|
|||||||
|
|
||||||
let tagCount = 0;
|
let tagCount = 0;
|
||||||
normalizedTags.forEach((tagData, index) => {
|
normalizedTags.forEach((tagData, index) => {
|
||||||
const { text, active } = tagData;
|
const { text, active, highlighted } = tagData;
|
||||||
const tagEl = document.createElement("div");
|
const tagEl = document.createElement("div");
|
||||||
tagEl.className = "comfy-tag";
|
tagEl.className = "comfy-tag";
|
||||||
|
|
||||||
updateTagStyle(tagEl, active);
|
updateTagStyle(tagEl, active, highlighted);
|
||||||
|
|
||||||
tagEl.textContent = text;
|
tagEl.textContent = text;
|
||||||
tagEl.title = text; // Set tooltip for full content
|
tagEl.title = text; // Set tooltip for full content
|
||||||
@@ -94,7 +94,14 @@ export function addTagsWidget(node, name, opts, callback) {
|
|||||||
// Toggle active state for this specific tag using its index
|
// Toggle active state for this specific tag using its index
|
||||||
const updatedTags = [...widget.value];
|
const updatedTags = [...widget.value];
|
||||||
updatedTags[index].active = !updatedTags[index].active;
|
updatedTags[index].active = !updatedTags[index].active;
|
||||||
updateTagStyle(tagEl, updatedTags[index].active);
|
updateTagStyle(
|
||||||
|
tagEl,
|
||||||
|
updatedTags[index].active,
|
||||||
|
updatedTags[index].highlighted
|
||||||
|
);
|
||||||
|
|
||||||
|
tagEl.dataset.active = updatedTags[index].active ? "true" : "false";
|
||||||
|
tagEl.dataset.highlighted = updatedTags[index].highlighted ? "true" : "false";
|
||||||
|
|
||||||
widget.value = updatedTags;
|
widget.value = updatedTags;
|
||||||
});
|
});
|
||||||
@@ -130,7 +137,7 @@ export function addTagsWidget(node, name, opts, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to update tag style based on active state
|
// Helper function to update tag style based on active state
|
||||||
function updateTagStyle(tagEl, active) {
|
function updateTagStyle(tagEl, active, highlighted = false) {
|
||||||
const baseStyles = {
|
const baseStyles = {
|
||||||
padding: "3px 10px", // Adjusted vertical padding to balance text
|
padding: "3px 10px", // Adjusted vertical padding to balance text
|
||||||
borderRadius: "6px",
|
borderRadius: "6px",
|
||||||
@@ -160,12 +167,24 @@ export function addTagsWidget(node, name, opts, callback) {
|
|||||||
textAlign: "center", // Center text horizontally
|
textAlign: "center", // Center text horizontally
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const highlightStyles = highlighted
|
||||||
|
? {
|
||||||
|
boxShadow: "0 0 0 2px rgba(255, 255, 255, 0.35), 0 1px 2px rgba(0,0,0,0.15)",
|
||||||
|
borderColor: "rgba(246, 224, 94, 0.8)",
|
||||||
|
backgroundImage: "linear-gradient(120deg, rgba(255,255,255,0.08), rgba(255,255,255,0))",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
|
||||||
|
backgroundImage: "none",
|
||||||
|
};
|
||||||
|
|
||||||
if (active) {
|
if (active) {
|
||||||
Object.assign(tagEl.style, {
|
Object.assign(tagEl.style, {
|
||||||
...baseStyles,
|
...baseStyles,
|
||||||
backgroundColor: "rgba(66, 153, 225, 0.9)",
|
backgroundColor: "rgba(66, 153, 225, 0.9)",
|
||||||
color: "white",
|
color: "white",
|
||||||
borderColor: "rgba(66, 153, 225, 0.9)",
|
borderColor: "rgba(66, 153, 225, 0.9)",
|
||||||
|
...highlightStyles,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Object.assign(tagEl.style, {
|
Object.assign(tagEl.style, {
|
||||||
@@ -173,19 +192,24 @@ export function addTagsWidget(node, name, opts, callback) {
|
|||||||
backgroundColor: "rgba(45, 55, 72, 0.7)",
|
backgroundColor: "rgba(45, 55, 72, 0.7)",
|
||||||
color: "rgba(226, 232, 240, 0.8)",
|
color: "rgba(226, 232, 240, 0.8)",
|
||||||
borderColor: "rgba(226, 232, 240, 0.2)",
|
borderColor: "rgba(226, 232, 240, 0.2)",
|
||||||
|
...highlightStyles,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add hover effect
|
// Add hover effect
|
||||||
tagEl.onmouseenter = () => {
|
tagEl.onmouseenter = () => {
|
||||||
tagEl.style.transform = "translateY(-1px)";
|
tagEl.style.transform = "translateY(-1px)";
|
||||||
tagEl.style.boxShadow = "0 2px 4px rgba(0,0,0,0.15)";
|
tagEl.dataset.prevBoxShadow = tagEl.style.boxShadow || "";
|
||||||
|
tagEl.style.boxShadow = "0 2px 4px rgba(0,0,0,0.2)";
|
||||||
};
|
};
|
||||||
|
|
||||||
tagEl.onmouseleave = () => {
|
tagEl.onmouseleave = () => {
|
||||||
tagEl.style.transform = "translateY(0)";
|
tagEl.style.transform = "translateY(0)";
|
||||||
tagEl.style.boxShadow = "0 1px 2px rgba(0,0,0,0.1)";
|
tagEl.style.boxShadow = tagEl.dataset.prevBoxShadow || "0 1px 2px rgba(0,0,0,0.1)";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
tagEl.dataset.active = active ? "true" : "false";
|
||||||
|
tagEl.dataset.highlighted = highlighted ? "true" : "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the value as array
|
// Store the value as array
|
||||||
|
|||||||
166
web/comfyui/trigger_word_highlight.js
Normal file
166
web/comfyui/trigger_word_highlight.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
import {
|
||||||
|
getConnectedTriggerToggleNodes,
|
||||||
|
getLinkFromGraph,
|
||||||
|
getNodeKey,
|
||||||
|
} from "./utils.js";
|
||||||
|
|
||||||
|
const TRIGGER_WORD_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
||||||
|
const triggerWordCache = new Map();
|
||||||
|
|
||||||
|
const LORA_NODE_CLASSES = new Set([
|
||||||
|
"Lora Loader (LoraManager)",
|
||||||
|
"Lora Stacker (LoraManager)",
|
||||||
|
"WanVideo Lora Select (LoraManager)",
|
||||||
|
]);
|
||||||
|
|
||||||
|
function normalizeTriggerWordList(triggerWords) {
|
||||||
|
if (!triggerWords) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggerWords instanceof Set) {
|
||||||
|
return Array.from(triggerWords)
|
||||||
|
.map((word) => (word == null ? "" : String(word)).trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(triggerWords)) {
|
||||||
|
return [(triggerWords == null ? "" : String(triggerWords)).trim()].filter(
|
||||||
|
Boolean
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return triggerWords
|
||||||
|
.map((word) => (word == null ? "" : String(word)).trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTriggerWordsForLora(loraName) {
|
||||||
|
if (!loraName) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = triggerWordCache.get(loraName);
|
||||||
|
if (cached && Date.now() - cached.timestamp < TRIGGER_WORD_CACHE_TTL) {
|
||||||
|
return cached.words;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.fetchApi(
|
||||||
|
`/lm/loras/get-trigger-words?name=${encodeURIComponent(loraName)}`,
|
||||||
|
{ method: "GET" }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response?.ok) {
|
||||||
|
const errorText = response ? await response.text().catch(() => "") : "";
|
||||||
|
throw new Error(errorText || `Failed to fetch trigger words for ${loraName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json().catch(() => ({}))) || {};
|
||||||
|
const triggerWords = Array.isArray(data.trigger_words)
|
||||||
|
? data.trigger_words.filter((word) => typeof word === "string")
|
||||||
|
: [];
|
||||||
|
const normalized = triggerWords
|
||||||
|
.map((word) => word.trim())
|
||||||
|
.filter((word) => word.length > 0);
|
||||||
|
|
||||||
|
triggerWordCache.set(loraName, {
|
||||||
|
words: normalized,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function highlightTriggerWordsAlongChain(startNode, triggerWords) {
|
||||||
|
const normalizedWords = normalizeTriggerWordList(triggerWords);
|
||||||
|
highlightNodeRecursive(startNode, normalizedWords, new Set());
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applySelectionHighlight(node, selection) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.__lmSelectionHighlightToken =
|
||||||
|
(node.__lmSelectionHighlightToken || 0) + 1;
|
||||||
|
const requestId = node.__lmSelectionHighlightToken;
|
||||||
|
|
||||||
|
const loraName = selection?.name;
|
||||||
|
const isActive = !!selection?.active;
|
||||||
|
|
||||||
|
if (!loraName || !isActive) {
|
||||||
|
highlightTriggerWordsAlongChain(node, []);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const triggerWords = await fetchTriggerWordsForLora(loraName);
|
||||||
|
if (node.__lmSelectionHighlightToken !== requestId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
highlightTriggerWordsAlongChain(node, triggerWords);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching trigger words for highlight:", error);
|
||||||
|
if (node.__lmSelectionHighlightToken === requestId) {
|
||||||
|
highlightTriggerWordsAlongChain(node, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightNodeRecursive(node, triggerWords, visited) {
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeKey = getNodeKey(node);
|
||||||
|
if (!nodeKey || visited.has(nodeKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited.add(nodeKey);
|
||||||
|
|
||||||
|
highlightTriggerWordsOnNode(node, triggerWords);
|
||||||
|
|
||||||
|
if (!node.outputs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const output of node.outputs) {
|
||||||
|
if (!output?.links?.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const linkId of output.links) {
|
||||||
|
const link = getLinkFromGraph(node.graph, linkId);
|
||||||
|
if (!link) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetNode = node.graph?.getNodeById?.(link.target_id);
|
||||||
|
if (!targetNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (LORA_NODE_CLASSES.has(targetNode.comfyClass)) {
|
||||||
|
highlightNodeRecursive(targetNode, triggerWords, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function highlightTriggerWordsOnNode(node, triggerWords) {
|
||||||
|
const connectedToggles = getConnectedTriggerToggleNodes(node);
|
||||||
|
if (!connectedToggles.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedToggles.forEach((toggleNode) => {
|
||||||
|
if (typeof toggleNode?.highlightTriggerWords === "function") {
|
||||||
|
toggleNode.highlightTriggerWords(triggerWords);
|
||||||
|
} else {
|
||||||
|
toggleNode.__pendingHighlightWords = Array.isArray(triggerWords)
|
||||||
|
? [...triggerWords]
|
||||||
|
: triggerWords;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -33,6 +33,66 @@ app.registerExtension({
|
|||||||
|
|
||||||
node.tagWidget = result.widget;
|
node.tagWidget = result.widget;
|
||||||
|
|
||||||
|
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
|
// Add hidden widget to store original message
|
||||||
const hiddenWidget = node.addWidget('text', 'orinalMessage', '');
|
const hiddenWidget = node.addWidget('text', 'orinalMessage', '');
|
||||||
hiddenWidget.type = CONVERTED_TYPE;
|
hiddenWidget.type = CONVERTED_TYPE;
|
||||||
@@ -52,6 +112,8 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => node.applyTriggerHighlightState?.());
|
||||||
|
|
||||||
const groupModeWidget = node.widgets[0];
|
const groupModeWidget = node.widgets[0];
|
||||||
groupModeWidget.callback = (value) => {
|
groupModeWidget.callback = (value) => {
|
||||||
if (node.widgets[3].value) {
|
if (node.widgets[3].value) {
|
||||||
@@ -69,6 +131,7 @@ app.registerExtension({
|
|||||||
active: value
|
active: value
|
||||||
}));
|
}));
|
||||||
node.tagWidget.value = updatedTags;
|
node.tagWidget.value = updatedTags;
|
||||||
|
node.applyTriggerHighlightState?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -147,5 +210,6 @@ app.registerExtension({
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.tagWidget.value = tagArray;
|
node.tagWidget.value = tagArray;
|
||||||
|
node.applyTriggerHighlightState?.();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user