Files
PozzettiAndrea 4888e4886e Vendor viewer JS, drop comfy-3d-viewers
Vendor the viewer JavaScript into javascript/ and drop the
comfy-3d-viewers dependency. The viewers were previously copied into web/ at
every startup by prestartup_script.py, which made the served frontend depend on
a second package resolving at runtime.

Bundles are vendored as .mjs so ComfyUI's **/*.js auto-import glob skips them:
they are iframe-only and must never enter the shared main browser realm. Only
the widget entry points stay .js.

Also replace comfy_env.copy_files with a local helper. Its two extra features
(frame-derived source dir, silent no-op on a missing source) were unused here,
and the asset seeding now resolves the input directory through
folder_paths.get_input_directory() so it follows ComfyUI Desktop and
--input-directory instead of writing into the code tree.

comfy-test.toml moves to the current schema: [test.workflows] cuda (there is no
"gpu" key -- the accelerator is named by backend, and the old spelling parsed to
an empty list, so CUDA runners silently ran the full unfiltered set), an
explicit [test.platforms] platforms allowlist (per-platform booleans are no
longer supported), and the javascript level.

Namespace every registerExtension name under sam3. and inline the spinner
styles: the stylesheet used to be injected into document.head, which is shared
state every other pack inherits and nobody can clean up. It is now inline on the
element with the rotation driven by the Web Animations API.
2026-08-22 19:44:03 +02:00

282 lines
7.8 KiB
JavaScript

/**
* SAM3 Video Segmentation Dynamic Input Management
*
* This extension manages the visibility of prompt-mode-specific inputs
* based on the selected prompt_mode in SAM3VideoSegmentation node.
*/
import { app } from "../../../scripts/app.js";
const DEBUG = false;
function log(...args) {
if (DEBUG) {
console.log("[SAM3-Video]", ...args);
}
}
// Helper function to hide a widget
function hideWidget(node, widget) {
if (!widget) return;
if (widget._hidden) {
log("Widget already hidden:", widget.name);
return;
}
log("Hiding widget:", widget.name);
const index = node.widgets.indexOf(widget);
if (index === -1) {
log(" ERROR: Widget not found in node.widgets array!");
return;
}
// Store original properties for restoration
if (!widget.origType) {
widget.origType = widget.type;
widget.origComputeSize = widget.computeSize;
widget.origSerializeValue = widget.serializeValue;
}
widget._originalIndex = index;
widget._hidden = true;
// Remove widget from array
node.widgets.splice(index, 1);
log(" Widget removed at index:", index);
// Hide linked widgets if any
if (widget.linkedWidgets) {
widget.linkedWidgets.forEach(w => hideWidget(node, w));
}
}
// Helper function to show a widget
function showWidget(node, widget) {
if (!widget) return;
if (!widget._hidden) {
log("Widget already visible:", widget.name);
return;
}
log("Showing widget:", widget.name);
// Restore original properties
if (widget.origType) {
widget.type = widget.origType;
widget.computeSize = widget.origComputeSize;
if (widget.origSerializeValue) {
widget.serializeValue = widget.origSerializeValue;
}
}
// Re-insert widget at original position
const targetIndex = widget._originalIndex;
const insertIndex = Math.min(targetIndex, node.widgets.length);
node.widgets.splice(insertIndex, 0, widget);
log(" Widget restored at index:", insertIndex);
widget._hidden = false;
// Show linked widgets if any
if (widget.linkedWidgets) {
widget.linkedWidgets.forEach(w => showWidget(node, w));
}
}
// Helper function to hide an input slot (removes from node.inputs)
function hideInput(node, inputName) {
if (!node.inputs) return;
const index = node.inputs.findIndex(input => input.name === inputName);
if (index === -1) {
log(" Input not found:", inputName);
return;
}
const input = node.inputs[index];
// Already hidden
if (input._hidden) {
log(" Input already hidden:", inputName);
return;
}
log("Hiding input:", inputName, "at index:", index);
// Store for restoration
input._originalIndex = index;
input._hidden = true;
// Store in a hidden inputs map on the node
if (!node._hiddenInputs) {
node._hiddenInputs = {};
}
node._hiddenInputs[inputName] = input;
// Disconnect any existing link before removing
if (input.link !== null) {
const link = app.graph.links[input.link];
if (link) {
const originNode = app.graph.getNodeById(link.origin_id);
if (originNode) {
originNode.disconnectOutput(link.origin_slot, node);
}
}
}
// Remove from inputs array
node.inputs.splice(index, 1);
log(" Input removed, remaining inputs:", node.inputs.length);
}
// Helper function to show an input slot (restores to node.inputs)
function showInput(node, inputName) {
if (!node._hiddenInputs || !node._hiddenInputs[inputName]) {
log(" Input not in hidden list:", inputName);
return;
}
const input = node._hiddenInputs[inputName];
if (!input._hidden) {
log(" Input already visible:", inputName);
return;
}
log("Showing input:", inputName);
// Find the correct position to insert
// Try to restore at original index, but clamp to valid range
const targetIndex = input._originalIndex;
const insertIndex = Math.min(targetIndex, node.inputs.length);
// Clear hidden flag
input._hidden = false;
// Re-insert into inputs array
node.inputs.splice(insertIndex, 0, input);
// Remove from hidden map
delete node._hiddenInputs[inputName];
log(" Input restored at index:", insertIndex, "total inputs:", node.inputs.length);
}
// Refresh node layout
function refreshNode(node) {
node.setDirtyCanvas(true, true);
if (app.graph) {
app.graph.setDirtyCanvas(true, true);
}
requestAnimationFrame(() => {
const newSize = node.computeSize();
node.setSize([node.size[0], newSize[1]]);
node.setDirtyCanvas(true, true);
if (app.canvas) {
app.canvas.setDirty(true, true);
}
requestAnimationFrame(() => {
if (app.canvas) {
app.canvas.draw(true, true);
}
log("Widget visibility update complete");
});
});
}
// Main extension registration
app.registerExtension({
name: "sam3.video_dynamic",
async nodeCreated(node) {
if (node.comfyClass === "SAM3VideoSegmentation") {
setTimeout(() => {
this.setupVideoSegmentation(node);
}, 100);
}
},
setupVideoSegmentation(node) {
log("Setting up SAM3VideoSegmentation node");
log("Initial inputs:", node.inputs?.map(i => i.name));
log("Initial widgets:", node.widgets?.map(w => w.name));
// Find the prompt_mode widget
const promptModeWidget = node.widgets?.find(w => w.name === "prompt_mode");
if (!promptModeWidget) {
log("ERROR: prompt_mode widget not found!");
log("Available widgets:", node.widgets?.map(w => w.name));
return;
}
log("prompt_mode widget found, current value:", promptModeWidget.value);
// Find text_prompt widget (this is a widget, not an input slot)
const textPromptWidget = node.widgets?.find(w => w.name === "text_prompt");
log("Found widgets:", {
text_prompt: !!textPromptWidget,
});
// Function to update visibility based on mode
const updateVisibility = (mode) => {
log("Updating visibility for mode:", mode);
// Text mode widget
if (textPromptWidget) {
if (mode === "text") {
showWidget(node, textPromptWidget);
} else {
hideWidget(node, textPromptWidget);
}
}
// Point mode inputs (these are input SLOTS, not widgets)
if (mode === "point") {
showInput(node, "positive_points");
showInput(node, "negative_points");
} else {
hideInput(node, "positive_points");
hideInput(node, "negative_points");
}
// Box mode inputs
if (mode === "box") {
showInput(node, "positive_boxes");
showInput(node, "negative_boxes");
} else {
hideInput(node, "positive_boxes");
hideInput(node, "negative_boxes");
}
refreshNode(node);
};
// Store original callback
const origCallback = promptModeWidget.callback;
// Override callback to update visibility when mode changes
promptModeWidget.callback = function(value) {
log("prompt_mode changed to:", value);
const result = origCallback?.apply(this, arguments);
updateVisibility(value);
return result;
};
// Initialize visibility
log("Initializing visibility...");
updateVisibility(promptModeWidget.value);
}
});
log("SAM3 Video dynamic inputs extension loaded");