feat(trigger): add optional strength adjustment for trigger words

Add `allow_strength_adjustment` parameter to enable mouse wheel adjustment of trigger word strengths. When enabled, strength values are preserved and can be modified interactively. Also improves trigger word parsing by handling whitespace more consistently and adding debug logging for trigger data inspection.
This commit is contained in:
Will Miao
2025-11-09 22:24:23 +08:00
parent f81ff2efe9
commit 4dd8ce778e
4 changed files with 265 additions and 117 deletions

View File

@@ -1,10 +1,12 @@
import { forwardMiddleMouseToCanvas } from "./utils.js";
export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.02) {
export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.02, options = {}) {
// Create container for tags
const container = document.createElement("div");
container.className = "comfy-tags-container";
const { allowStrengthAdjustment = true } = options;
forwardMiddleMouseToCanvas(container);
// Set initial height
@@ -41,6 +43,7 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
}
const normalizedTags = tagsData;
const showStrengthInfo = widget.allowStrengthAdjustment ?? allowStrengthAdjustment;
if (normalizedTags.length === 0) {
// Show message when no tags are present
@@ -82,16 +85,44 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
const tagEl = document.createElement("div");
tagEl.className = "comfy-tag";
updateTagStyle(tagEl, active, highlighted, strength);
const textSpan = document.createElement("span");
textSpan.className = "comfy-tag-text";
textSpan.textContent = text;
Object.assign(textSpan.style, {
display: "inline-block",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
minWidth: "0",
flexGrow: "1",
});
tagEl.appendChild(textSpan);
// Set the text content to include strength if present
// Always show strength if it has been modified to avoid layout shift
if (strength !== undefined && strength !== null) {
tagEl.textContent = `${text}:${strength.toFixed(2)}`;
} else {
tagEl.textContent = text;
const strengthBadge = showStrengthInfo ? document.createElement("span") : null;
if (strengthBadge) {
strengthBadge.className = "comfy-tag-strength";
Object.assign(strengthBadge.style, {
fontSize: "11px",
fontWeight: "600",
padding: "1px 6px",
borderRadius: "999px",
letterSpacing: "0.2px",
backgroundColor: "rgba(255,255,255,0.08)",
color: "rgba(255,255,255,0.95)",
border: "1px solid rgba(255,255,255,0.25)",
lineHeight: "normal",
minWidth: "34px",
textAlign: "center",
pointerEvents: "none",
opacity: "0",
visibility: "hidden",
transition: "opacity 0.2s ease",
});
tagEl.appendChild(strengthBadge);
}
tagEl.title = text; // Set tooltip for full content
updateTagStyle(tagEl, active, highlighted, strength);
updateStrengthDisplay(tagEl, strength, text, showStrengthInfo);
// Add click handler to toggle state
tagEl.addEventListener("click", (e) => {
@@ -100,12 +131,14 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
// Toggle active state for this specific tag using its index
const updatedTags = [...widget.value];
updatedTags[index].active = !updatedTags[index].active;
textSpan.textContent = updatedTags[index].text;
updateTagStyle(
tagEl,
updatedTags[index].active,
updatedTags[index].highlighted,
updatedTags[index].strength
);
updateStrengthDisplay(tagEl, updatedTags[index].strength, updatedTags[index].text);
tagEl.dataset.active = updatedTags[index].active ? "true" : "false";
tagEl.dataset.highlighted = updatedTags[index].highlighted ? "true" : "false";
@@ -114,48 +147,42 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
});
// Add mouse wheel handler to adjust strength
tagEl.addEventListener("wheel", (e) => {
e.preventDefault();
e.stopPropagation();
if (showStrengthInfo) {
tagEl.addEventListener("wheel", (e) => {
e.preventDefault();
e.stopPropagation();
// Only adjust strength if the mouse is over the tag
const updatedTags = [...widget.value];
let currentStrength = updatedTags[index].strength;
// If no strength is set, default to 1.0
if (currentStrength === undefined || currentStrength === null) {
currentStrength = 1.0;
}
// Only adjust strength if the mouse is over the tag
const updatedTags = [...widget.value];
let currentStrength = updatedTags[index].strength;
// If no strength is set, default to 1.0
if (currentStrength === undefined || currentStrength === null) {
currentStrength = 1.0;
}
// Adjust strength based on scroll direction
// DeltaY < 0 is scroll up, deltaY > 0 is scroll down
if (e.deltaY < 0) {
// Scroll up: increase strength by wheelSensitivity
currentStrength += wheelSensitivity;
} else {
// Scroll down: decrease strength by wheelSensitivity
currentStrength -= wheelSensitivity;
}
// Adjust strength based on scroll direction
// DeltaY < 0 is scroll up, deltaY > 0 is scroll down
if (e.deltaY < 0) {
// Scroll up: increase strength by wheelSensitivity
currentStrength += wheelSensitivity;
} else {
// Scroll down: decrease strength by wheelSensitivity
currentStrength -= wheelSensitivity;
}
// Ensure strength doesn't go below 0
currentStrength = Math.max(0, currentStrength);
// Ensure strength doesn't go below 0
currentStrength = Math.max(0, currentStrength);
// Update the strength value
updatedTags[index].strength = currentStrength;
// Update the strength value
updatedTags[index].strength = currentStrength;
textSpan.textContent = updatedTags[index].text;
// Update the tag display to show the strength value
// Always show strength once it has been modified to avoid layout shift
tagEl.textContent = `${updatedTags[index].text}:${currentStrength.toFixed(2)}`;
updateStrengthDisplay(tagEl, currentStrength, updatedTags[index].text, showStrengthInfo);
updateTagStyle(
tagEl,
updatedTags[index].active,
updatedTags[index].highlighted,
updatedTags[index].strength
);
widget.value = updatedTags;
});
widget.value = updatedTags;
});
}
rowContainer.appendChild(tagEl);
tagCount++;
@@ -190,7 +217,7 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
// Helper function to update tag style based on active state
function updateTagStyle(tagEl, active, highlighted = false, strength = null) {
const baseStyles = {
padding: "3px 10px", // Adjusted vertical padding to balance text
padding: "3px 10px",
borderRadius: "6px",
maxWidth: "200px",
overflow: "hidden",
@@ -200,7 +227,9 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
cursor: "pointer",
transition: "all 0.2s ease",
border: "1px solid transparent",
display: "inline-block", // inline-block for better text truncation
display: "inline-flex",
alignItems: "center",
gap: "6px",
boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
margin: "1px",
userSelect: "none",
@@ -214,7 +243,6 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
maxWidth: "200px",
lineHeight: "16px", // Added explicit line-height
verticalAlign: "middle", // Added vertical alignment
position: "relative", // For better text positioning
textAlign: "center", // Center text horizontally
};
@@ -263,6 +291,42 @@ export function addTagsWidget(node, name, opts, callback, wheelSensitivity = 0.0
tagEl.dataset.highlighted = highlighted ? "true" : "false";
}
function formatStrengthValue(value) {
if (value === undefined || value === null) {
return null;
}
const num = Number(value);
if (!Number.isFinite(num)) {
return null;
}
return num.toFixed(2);
}
function updateStrengthDisplay(tagEl, strength, baseText, showStrengthInfo) {
if (!showStrengthInfo) {
tagEl.title = baseText;
return;
}
const badge = tagEl.querySelector(".comfy-tag-strength");
if (!badge) {
tagEl.title = baseText;
return;
}
const displayValue = strength === undefined || strength === null ? 1 : strength;
const formatted = formatStrengthValue(displayValue);
if (formatted !== null) {
badge.textContent = formatted;
badge.style.opacity = "1";
badge.style.visibility = "visible";
tagEl.title = `${baseText} (${formatted})`;
} else {
badge.textContent = "";
badge.style.opacity = "0";
badge.style.visibility = "hidden";
tagEl.title = baseText;
}
}
// Store the value as array
let widgetValue = initialTagsData;

View File

@@ -75,13 +75,20 @@ app.registerExtension({
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);
}, null, wheelSensitivity, {
allowStrengthAdjustment: initialStrengthAdjustment
});
node.tagWidget = result.widget;
node.tagWidget.allowStrengthAdjustment = initialStrengthAdjustment;
const normalizeTagText = (text) =>
(typeof text === 'string' ? text.trim().toLowerCase() : '');
@@ -148,31 +155,40 @@ app.registerExtension({
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) {
// 0 is group mode, 1 is default_active, 2 is tag widget, 3 is original message
const savedValue = node.widgets_values[2];
if (savedValue) {
result.widget.value = Array.isArray(savedValue) ? savedValue : [];
if (tagWidgetIndex >= 0) {
const savedValue = node.widgets_values[tagWidgetIndex];
if (savedValue) {
result.widget.value = Array.isArray(savedValue) ? savedValue : [];
}
}
const originalMessage = node.widgets_values[3];
if (originalMessage) {
hiddenWidget.value = originalMessage;
if (originalMessageWidgetIndex >= 0) {
const originalMessage = node.widgets_values[originalMessageWidgetIndex];
if (originalMessage) {
hiddenWidget.value = originalMessage;
}
}
}
requestAnimationFrame(() => node.applyTriggerHighlightState?.());
const groupModeWidget = node.widgets[0];
groupModeWidget.callback = (value) => {
if (node.widgets[3].value) {
this.updateTagsBasedOnMode(node, node.widgets[3].value, value);
if (node.originalMessageWidget?.value) {
this.updateTagsBasedOnMode(
node,
node.originalMessageWidget.value,
value,
Boolean(strengthAdjustmentWidget?.value)
);
}
}
// Add callback for default_active widget
const defaultActiveWidget = node.widgets[1];
defaultActiveWidget.callback = (value) => {
// Set all existing tags' active state to the new value
if (node.tagWidget && node.tagWidget.value) {
@@ -185,6 +201,21 @@ app.registerExtension({
}
}
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() {
@@ -215,27 +246,32 @@ app.registerExtension({
}
// Store the original message for mode switching
node.widgets[3].value = message;
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;
this.updateTagsBasedOnMode(node, message, groupMode);
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) {
updateTagsBasedOnMode(node, message, groupMode, allowStrengthAdjustment = false) {
if (!node.tagWidget) return;
node.tagWidget.allowStrengthAdjustment = allowStrengthAdjustment;
const existingTags = node.tagWidget.value || [];
const existingTagMap = {};
// Create a map of existing tags and their active states and strengths
existingTags.forEach(tag => {
existingTagMap[tag.text] = {
active: tag.active,
strength: tag.strength
strength: allowStrengthAdjustment ? tag.strength : null
};
});