Files
ComfyUI-Lora-Manager/web/comfyui/usage_stats.js
Will Miao 6142b3dc0c feat: consolidate ComfyUI settings and add custom words autocomplete toggle
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
2026-01-25 12:53:41 +08:00

163 lines
6.2 KiB
JavaScript

// ComfyUI extension to track model usage statistics
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { showToast } from "./utils.js";
import { getAutoPathCorrectionPreference } from "./settings.js";
// Define target nodes and their widget configurations
const PATH_CORRECTION_TARGETS = [
{ comfyClass: "CheckpointLoaderSimple", widgetName: "ckpt_name", modelType: "checkpoints" },
{ comfyClass: "Checkpoint Loader with Name (Image Saver)", widgetName: "ckpt_name", modelType: "checkpoints" },
{ comfyClass: "UNETLoader", widgetName: "unet_name", modelType: "checkpoints" },
{ comfyClass: "easy comfyLoader", widgetName: "ckpt_name", modelType: "checkpoints" },
{ comfyClass: "CheckpointLoader|pysssss", widgetName: "ckpt_name", modelType: "checkpoints" },
{ comfyClass: "Efficient Loader", widgetName: "ckpt_name", modelType: "checkpoints" },
{ comfyClass: "UnetLoaderGGUF", widgetName: "unet_name", modelType: "checkpoints" },
{ comfyClass: "UnetLoaderGGUFAdvanced", widgetName: "unet_name", modelType: "checkpoints" },
{ comfyClass: "LoraLoader", widgetName: "lora_name", modelType: "loras" },
{ comfyClass: "easy loraStack", widgetNamePattern: "lora_\\d+_name", modelType: "loras" }
];
// Register the extension
app.registerExtension({
name: "LoraManager.UsageStats",
setup() {
// Listen for successful executions
api.addEventListener("execution_success", ({ detail }) => {
if (detail && detail.prompt_id) {
this.updateUsageStats(detail.prompt_id);
}
});
},
async updateUsageStats(promptId) {
try {
// Call backend endpoint with the prompt_id
const response = await fetch(`/api/lm/update-usage-stats`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ prompt_id: promptId }),
});
if (!response.ok) {
console.warn("Failed to update usage statistics:", response.statusText);
}
} catch (error) {
console.error("Error updating usage statistics:", error);
}
},
async loadedGraphNode(node) {
if (!getAutoPathCorrectionPreference()) {
return;
}
// Check if this node type needs path correction
const target = PATH_CORRECTION_TARGETS.find(t => t.comfyClass === node.comfyClass);
if (!target) {
return;
}
await this.correctNodePaths(node, target);
},
async correctNodePaths(node, target) {
try {
if (target.widgetNamePattern) {
// Handle pattern-based widget names (like lora_1_name, lora_2_name, etc.)
const pattern = new RegExp(target.widgetNamePattern);
const widgetIndexes = [];
if (node.widgets) {
node.widgets.forEach((widget, index) => {
if (pattern.test(widget.name)) {
widgetIndexes.push(index);
}
});
}
// Process each matching widget
for (const widgetIndex of widgetIndexes) {
await this.correctWidgetPath(node, widgetIndex, target.modelType);
}
} else {
// Handle single widget name
if (node.widgets) {
const widgetIndex = node.widgets.findIndex(w => w.name === target.widgetName);
if (widgetIndex !== -1) {
await this.correctWidgetPath(node, widgetIndex, target.modelType);
}
}
}
} catch (error) {
console.error("Error correcting node paths:", error);
}
},
async correctWidgetPath(node, widgetIndex, modelType) {
if (!node.widgets_values || !node.widgets_values[widgetIndex]) {
return;
}
const currentPath = node.widgets_values[widgetIndex];
if (!currentPath || typeof currentPath !== 'string') {
return;
}
// Extract filename from path (after last separator)
const fileName = currentPath.split(/[/\\]/).pop();
if (!fileName) {
return;
}
try {
// Search for current relative path
const response = await api.fetchApi(`/lm/${modelType}/relative-paths?search=${encodeURIComponent(fileName)}&limit=2`);
const data = await response.json();
if (!data.success || !data.relative_paths || data.relative_paths.length === 0) {
return;
}
const foundPaths = data.relative_paths;
const firstPath = foundPaths[0];
// Check if we need to update the path
if (firstPath !== currentPath) {
// Update the widget value
// node.widgets_values[widgetIndex] = firstPath;
node.widgets[widgetIndex].value = firstPath;
if (foundPaths.length === 1) {
// Single match found - success
showToast({
severity: 'info',
summary: 'LoRA Manager Path Correction',
detail: `Updated path for ${fileName}: ${firstPath}`,
life: 5000
});
} else {
// Multiple matches found - warning
showToast({
severity: 'warn',
summary: 'LoRA Manager Path Correction',
detail: `Multiple paths found for ${fileName}, using: ${firstPath}`,
life: 5000
});
}
// Mark node as modified
if (node.setDirtyCanvas) {
node.setDirtyCanvas(true);
}
}
} catch (error) {
console.error(`Error correcting path for ${fileName}:`, error);
}
}
});