@ -33,6 +33,7 @@ import {
OutputGalleryNode ,
OutputGalleryNode ,
ImageCompareNode ,
ImageCompareNode ,
VideoStitchNode ,
VideoStitchNode ,
EaseCurveNode ,
} from "./nodes" ;
} from "./nodes" ;
import { EditableEdge , ReferenceEdge } from "./edges" ;
import { EditableEdge , ReferenceEdge } from "./edges" ;
import { ConnectionDropMenu , MenuAction } from "./ConnectionDropMenu" ;
import { ConnectionDropMenu , MenuAction } from "./ConnectionDropMenu" ;
@ -63,6 +64,7 @@ const nodeTypes: NodeTypes = {
outputGallery : OutputGalleryNode ,
outputGallery : OutputGalleryNode ,
imageCompare : ImageCompareNode ,
imageCompare : ImageCompareNode ,
videoStitch : VideoStitchNode ,
videoStitch : VideoStitchNode ,
easeCurve : EaseCurveNode ,
} ;
} ;
const edgeTypes : EdgeTypes = {
const edgeTypes : EdgeTypes = {
@ -118,6 +120,8 @@ const getNodeHandles = (nodeType: string): { inputs: string[]; outputs: string[]
return { inputs : [ "image" ] , outputs : [ ] } ;
return { inputs : [ "image" ] , outputs : [ ] } ;
case "videoStitch" :
case "videoStitch" :
return { inputs : [ "video" , "audio" ] , outputs : [ "video" ] } ;
return { inputs : [ "video" , "audio" ] , outputs : [ "video" ] } ;
case "easeCurve" :
return { inputs : [ "video" ] , outputs : [ "video" ] } ;
default :
default :
return { inputs : [ ] , outputs : [ ] } ;
return { inputs : [ ] , outputs : [ ] } ;
}
}
@ -300,7 +304,7 @@ export function WorkflowCanvas() {
if ( ! targetNode ) return false ;
if ( ! targetNode ) return false ;
const targetNodeType = targetNode . type ;
const targetNodeType = targetNode . type ;
if ( targetNodeType === "generateVideo" || targetNodeType === "videoStitch" || targetNodeType === "output" ) {
if ( targetNodeType === "generateVideo" || targetNodeType === "videoStitch" || targetNodeType === "easeCurve" || targetNodeType === " output" ) {
// For output node, we allow video even though its handle is typed as "image"
// For output node, we allow video even though its handle is typed as "image"
// because output node can display both images and videos
// because output node can display both images and videos
return true ;
return true ;
@ -758,6 +762,10 @@ export function WorkflowCanvas() {
// VideoStitch has dynamic video-N inputs and a video output
// VideoStitch has dynamic video-N inputs and a video output
targetHandleId = "video-0" ;
targetHandleId = "video-0" ;
sourceHandleIdForNewNode = "video" ;
sourceHandleIdForNewNode = "video" ;
} else if ( nodeType === "easeCurve" ) {
// EaseCurve accepts video input and outputs video
targetHandleId = "video" ;
sourceHandleIdForNewNode = "video" ;
} else if ( nodeType === "generateVideo" ) {
} else if ( nodeType === "generateVideo" ) {
// GenerateVideo outputs video
// GenerateVideo outputs video
sourceHandleIdForNewNode = "video" ;
sourceHandleIdForNewNode = "video" ;
@ -1535,6 +1543,8 @@ export function WorkflowCanvas() {
return "#14b8a6" ;
return "#14b8a6" ;
case "videoStitch" :
case "videoStitch" :
return "#f97316" ;
return "#f97316" ;
case "easeCurve" :
return "#f59e0b" ; // amber-500
default :
default :
return "#94a3b8" ;
return "#94a3b8" ;
}
}