Add Veo 3 and Veo 3 Fast models (text-to-video + image-to-video) with
custom API integration using Kie's /api/v1/veo/ endpoints and numeric
successFlag polling. Also document Kie model integration SOP in CLAUDE.md.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Stabilize thumbnail extraction deps with a string key derived from clip
edge IDs, preventing excessive re-runs on unrelated node changes
- Use ref-based cache for thumbnails and add cleanup abort for pending
extractions
- Fix clipOrder sync triggering extra render by tracking last written order
- Add WaveSpeed case to ProviderBadge in GenerateImageNode and
GenerateVideoNode
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Refactor executeSingleNode error paths to throw instead of setting global
isRunning/currentNodeIds state, which corrupted sibling nodes running in
parallel via Promise.allSettled
- Validate WaveSpeed providedPollUrl with SSRF check before use
- Sanitize WaveSpeed taskId in provider with path-traversal validation
- Revoke blob URLs (URL.revokeObjectURL) before replacing outputVideo to
prevent memory leaks from large video files
- Move pause edge cleanup to outer executeWorkflow loop
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace RegExp constructor with replaceAll() for @variable substitution
in promptConstructor to prevent regex injection via variable names
- Wrap annotation, prompt, and promptConstructor execution cases in
try/catch to gracefully handle errors (log + set node error, continue)
- Fix MediaBunny resource cleanup type safety with closeable type guard
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add validateMediaUrl() utility to block private IPs, localhost, and
non-HTTP protocols before server-side fetches (with tests)
- URL-encode Kie taskId in poll URL to prevent injection
- Validate WaveSpeed modelId against path traversal in both route.ts
and wavespeed.ts provider
- Add 500MB Content-Length check before downloading media at all 3
fetch sites (Kie, WaveSpeed route, WaveSpeed provider)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AudioInputNode: use named event handlers with removeEventListener in cleanup
- deduplicatedFetch: track setTimeout IDs and clear them in clearFetchCache()
- waitForPendingImageSyncs: clear timeout in finally block after Promise.race
- decodeWithMediaBunny: wrap in try/finally to close sink, input, blobSource
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update stale test mocks from currentNodeId to currentNodeIds in
AudioInputNode, EaseCurveNode, and VideoStitchNode tests. Replace
hard 401 return for missing FAL API key with console.warn, allowing
requests to proceed without auth (rate-limited) since
generateWithFal() already handles null keys gracefully.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The presets overlay was being clipped by BaseNode's overflow-hidden container.
Render it through createPortal to document.body with position tracking via
requestAnimationFrame, matching the existing pattern used for comment tooltips.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add nano-banana-pro model to Kie provider with schema and image input key
- Reorder Kie generation to process dynamicInputs before fallback image
uploads, preventing double-uploads when schema-mapped connections exist
- Update test mocks for deduplicatedFetch, useProviderApiKeys, and static
handle changes across GenerateImageNode, GenerateVideoNode,
ModelParameters, and ModelSearchDialog tests
- Add test commands to CLAUDE.md
- Fix SplitGridSettingsModal grid preview count (5 → 6 options)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handleNodeDragStop used a hardcoded 300x280 fallback for node dimensions,
causing miscalculated centers for non-standard nodes (easeCurve 340x480,
videoStitch 400x280, audioInput 300x200). Now uses the same 3-level
fallback (measured → style → defaultNodeDimensions) as createGroup.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use fixed-width editor, absolute-positioned video to fill node, and
uniform gap-based spacing for controls below the curve editor.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Offset segmentBaseTime by frameInterval so the first frame starts at
timestamp 0 instead of -frameInterval, which the encoder rejects.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Split AudioInputNode waveform effect to avoid ResizeObserver churn on every animation tick
- Add missing kieApiKey/wavespeedApiKey deps to ModelParameters useEffect
- Clean up VideoSampleSource/Output on error in useApplySpeedCurve
- Fix off-by-one in useStitchVideos duplicate-frame skip (<= to <) and add try/finally for resource cleanup
- Replace leftover currentNodeId: null with currentNodeIds: [] in workflowStore
- Store full response metadata (status, headers, body) in deduplicatedFetch cache
- Add in-flight guard to imageStorage to prevent duplicate concurrent saves
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove 36 unsupported Kie.ai models (14 image, 22 video) and add 2 new
ones (kling-2.6/motion-control, topaz/video-upscale), bringing the total
to 22 models. Fix parameter schemas to match official Kie.ai docs:
seedream quality param, flux-2 input_urls, grok-imagine video mode/duration,
kling sound param, wan resolution param, and singular vs array image keys.
Remove dead Veo/Kontext/Runway helper functions and endpoint branching —
all remaining models use the standard createTask endpoint.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add ~38 new image/video models (Imagen 4, FLUX.2, Flux Kontext,
Grok Imagine, Ideogram, Qwen, Runway, Hailuo, Seedance variants,
Kling older, Wan older) with schemas and endpoint routing for
dedicated APIs (Flux Kontext, Runway). Add pageUrl to all Kie
models and update ModelSearchDialog to use it.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Cover 11 new source files with 280 specs across pure function libs,
React components, and async hook logic. Key areas: easing functions,
bezier presets, speed curve warping, video encoding config,
AudioInputNode, EaseCurveNode, CubicBezierEditor, audio mixing,
audio visualization, video stitching, and speed curve application.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added validation to ensure fal.ai API key is provided in requests, returning a 401 status with an appropriate error message if not configured.
- Updated related tests to reflect the new API key requirement and adjusted assertions for expected responses.
- Modified documentation to clarify that fal.ai API key is now mandatory for model access.
Merge feature/wavespeed branch, adding WaveSpeed as a new provider alongside
Kie. Fix shared integration bugs in both providers: standardize API key headers
to X-Kie-Key/X-WaveSpeed-Key, add both to handleSaveProviders, ModelParameters
headers, CostDialog filter, and ModelSearchDialog badges. Replace per-provider
icon buttons in FloatingActionBar with a single "Browse models" button.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Node height adjusts between 480px (editor) and 320px (video) on tab switch
- Default to video tab when settings are inherited from parent
- Rename "Ease In"/"Ease Out" handle labels to "Settings"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
HTML5 drag-and-drop doesn't work inside React Flow nodes because
React Flow captures pointer events at a higher level. Switch to
pointer-event-based reorder with visual feedback (dragged item dims,
hover target highlights with blue ring).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add nodrag class to thumbnail items so React Flow doesn't capture
drag events, allowing the HTML drag-and-drop reorder to work.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Extract thumbnails from easeCurve and videoStitch source nodes (not
just generateVideo)
- Add content-start to grid container so rows pack to top instead of
stretching to fill available space
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace 2 video handles with 4 labeled handles (Video In/Out at 35%, Ease In/Out at 75%)
- Add lime-300 color for easeCurve handles matching node accent
- Compute inheritance state from edges (useMemo)
- Add inheritance overlay on Editor tab: semi-transparent dark bg with informational text
- Add "Control manually" button that removes easeCurve edge and clears inheritedFrom
- Disable CubicBezierEditor, Presets button, and Apply button when inherited
- Video tab unaffected by inheritance state
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace horizontal scroll filmstrip with 4-column CSS grid layout
- Use aspect-video for thumbnails that scale with grid cells
- Add blue "Video N" labels to each video input handle
- Add blue "Output" label to video output handle
- Audio handle label unchanged (purple)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add videoStitch handle resolution in handleConnect batch loop (both original source and other selected nodes)
- Add batchUsed parameter to findCompatibleHandle for batch-aware handle lookup
- Add videoStitch resolution in ConnectionDropMenu batch handler
- Prevents all bulk connections from routing to the same video-0 handle
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add inheritedFrom field to EaseCurveNodeData interface
- Add easeCurve to HandleType union
- Update getHandleType, getNodeHandles, isValidConnection for easeCurve
- Add easeCurve extraction in getConnectedInputs
- Propagate parent bezierHandles/easingPreset in both execution paths
- Fix validation.ts default data for easeCurve node
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
All Easing Functions presets were not updating the editor because
handleSelectEasing fell back to the same DEFAULT_CUSTOM_BEZIER for every
non-preset easing. Added EASING_BEZIER_MAP with correct cubic-bezier
values from easings.net and a getEasingBezier() lookup. Also corrected
three PRESET_BEZIERS entries (easeInOutExpo, easeInOutCubic, easeInOutSine)
that diverged from the easings.net source.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Import and register EaseCurveNode in nodeTypes
- Add easeCurve case to getNodeHandles (video in, video out)
- Add easeCurve to isValidConnection video target list
- Add easeCurve video handle mapping in handleMenuSelect
- Add amber minimap color (#f59e0b) for easeCurve nodes
- Add Ease Curve to VIDEO_TARGET_OPTIONS and VIDEO_SOURCE_OPTIONS
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add EaseCurveNodeData to type imports
- Add easeCurve as video source in getConnectedInputs
- Add easeCurve case to executeWorkflow with speed curve processing
- Add easeCurve case to regenerateNode for individual node execution
- Dynamic imports for applySpeedCurveAsync and createBezierEasing
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add easeCurve (340x480) to WorkflowCanvas keyboard shortcut dimensions
- Add easeCurve (340x480) to quickstart validation DEFAULT_DIMENSIONS
- Add easeCurve case to createDefaultNodeData switch (fixes exhaustive return)
- Required for NodeType union exhaustiveness after adding easeCurve type
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Two tabs: Editor (curve editor) and Video (processed result)
- CubicBezierEditor integration with nodrag isolation
- Preset popover with visual SVG thumbnails of all 24 easing functions
- 5 Bezier presets with exact handles, plus 19 sampled polyline thumbnails
- Duration control (0.1-30 sec) with nodrag input
- Video preview with auto-play, loop, click-to-clear
- Auto-switch to Video tab when processing completes
- Encoder detection on mount with unsupported browser fallback
- Processing overlay with spinner and progress percentage
- Error display below tabs
- Run button via BaseNode onRun
- Exported from src/components/nodes/index.ts
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- useApplySpeedCurve hook with MediaBunny encoding pipeline (no frame blending, no preview mode)
- Standalone applySpeedCurveAsync export for store/API usage
- SpeedCurveProgress type for progress tracking
- EaseCurveNodeData interface with bezierHandles, easingPreset, outputDuration, status fields
- "easeCurve" added to NodeType union and WorkflowNodeData
- Default dimensions (340x480) and node data (easeInOutSine preset) registered
- Shared applySpeedCurveCore extracts common logic between hook and standalone function
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Multiple node instances (GenerateImageNode, GenerateVideoNode, etc.) were
each making their own /api/models requests when mounted, causing N duplicate
network requests when N nodes existed on the canvas.
This fix:
- Adds deduplicatedFetch utility that shares in-flight requests to the same URL
- Adds useProviderApiKeys() hook with shallow equality for stable API key refs
- Updates all components to use primitive dependencies instead of object refs
- Uses deduplicatedFetch for all model-fetching operations
Expected result: 1 request instead of N when multiple nodes need the same data.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add fetchWithTimeout utility for image save requests (30s timeout)
- Process node images in parallel batches (3 at a time) for faster saves
- Add timeout to waitForPendingImageSyncs (60s) to prevent infinite hangs
- Move isSaving flag reset to finally block for consistent state cleanup
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
After all video frames are processed (phase 1), the final video duration
is known from highestWrittenTimestamp. Audio is now trimmed to that
exact duration before being encoded into the MP4 (phase 2). Prevents
audio tracks from extending beyond the video.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When videoDuration is 0 (unknown at call time), prepareAudioAsync was
computing targetDuration as max(0.1, 0) = 0.1 seconds, truncating the
audio to 100ms. Now computes the actual decoded audio duration from
buffers and uses that when videoDuration <= 0.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use MediaBunny as the primary decoder following the documented pattern:
getPrimaryAudioTrack() + canDecode() check + AudioBufferSink.buffers().
Falls back to Web Audio API's decodeAudioData if MediaBunny cannot parse
the format (e.g. unusual codecs). This ensures MediaBunny handles
container formats it supports while Web Audio API catches the rest.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MediaBunny's Input/BlobSource is designed for video containers and cannot
parse standalone audio files (MP3, WAV, etc.), causing "unsupported format"
errors at getAudioTracks(). Replace with Web Audio API's decodeAudioData
which natively handles all browser-supported audio formats.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add "video" to Output node's getNodeHandles inputs list
- Consistent with isValidConnection and findCompatibleHandle video->output handling
- Output node now properly accepts VideoStitch connections via all code paths
- Add video handle mapping for videoStitch, generateVideo, and output
- Add audio handle mapping for audioInput and videoStitch
- Connection drop now auto-connects for all handle types