Files
pixelworld AIandClaude Opus 4.5 a6e030c9d9 feat: add draft mode to Seedance Pro 1.5 node with auto-populate task ID
- Add draft mode toggle for generating 480p preview videos at reduced cost
- Add draft_task_id input/output for rendering full resolution from draft
- Implement OUTPUT_NODE pattern to send task ID to frontend via ui data
- Add JavaScript listener to auto-populate draft_task_id after draft generation
- Add submit_and_get_result_with_task_id to API handler for returning task IDs
- Support draft parameter in video generation API calls

Draft workflow:
1. Enable draft_mode=true to generate 480p preview
2. Task ID auto-fills after completion
3. Set draft_mode=false to render full resolution using stored draft

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 16:50:33 -06:00

99 lines
3.3 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
// Listen for executed event to auto-populate draft_task_id
// This works because SeedancePro15VideoNode has OUTPUT_NODE = True and returns ui data
api.addEventListener("executed", ({ detail }) => {
const { node: nodeId, output } = detail;
// Check if this execution has a draft_task_id in ui output
if (output && output.draft_task_id && output.draft_task_id[0]) {
const draftTaskId = output.draft_task_id[0];
// Find the node in the graph
const node = app.graph.getNodeById(nodeId);
if (!node || node.type !== "SeedancePro15Video") {
return;
}
console.log("[SeedAPI] Received draft_task_id from node:", draftTaskId);
// Check if draft_mode widget is enabled
const draftModeWidget = node.widgets?.find(w => w.name === "draft_mode");
if (!draftModeWidget || !draftModeWidget.value) {
return; // Only auto-populate when draft_mode was True
}
// Find and update the draft_task_id widget
const draftTaskIdWidget = node.widgets?.find(w => w.name === "draft_task_id");
if (draftTaskIdWidget && draftTaskId) {
console.log(`[SeedAPI] Auto-populating draft_task_id: ${draftTaskId}`);
draftTaskIdWidget.value = draftTaskId;
// Mark the graph as changed so it can be saved
app.graph.setDirtyCanvas(true, true);
}
}
});
app.registerExtension({
name: "SeedAPI.DynamicInputs",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "Seedream4Unified") {
console.log("Registering Seedream4Unified dynamic inputs");
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info) {
console.log("onConnectionsChange called:", { type, index, connected, link_info });
const stackTrace = new Error().stack;
// Don't modify during graph loading or pasting
if (stackTrace.includes('loadGraphData') || stackTrace.includes('pasteFromClipboard')) {
console.log("Skipping due to loadGraphData or pasteFromClipboard");
return;
}
if (!link_info) {
console.log("No link_info, returning");
return;
}
// Only handle input connections (type 1), ignore output connections (type 2)
if (type === 1) {
// Remove disconnected inputs
if (!connected && this.inputs.length > 1) {
if (!stackTrace.includes('LGraphNode.prototype.connect') &&
!stackTrace.includes('LGraphNode.connect') &&
!stackTrace.includes('loadGraphData')) {
console.log("Removing input at index:", index);
this.removeInput(index);
}
}
// Renumber only IMAGE type inputs to keep them sequential
let slot_i = 1;
for (let i = 0; i < this.inputs.length; i++) {
if (this.inputs[i].type === "IMAGE") {
this.inputs[i].name = `image${slot_i}`;
slot_i++;
}
}
console.log("After renumbering, next slot_i:", slot_i);
// When connecting, add a new input slot if we don't have 10 IMAGE inputs yet
if (connected && slot_i <= 10) {
console.log("Adding new input: image" + slot_i);
this.addInput(`image${slot_i}`, "IMAGE");
}
}
// Call the original function if it exists
if (onConnectionsChange) {
return onConnectionsChange.apply(this, arguments);
}
};
}
}
});