From adab5d48ea39e7cc95f78d6768c4e460b09313fd Mon Sep 17 00:00:00 2001 From: shrimbly Date: Wed, 18 Feb 2026 08:57:06 +1300 Subject: [PATCH] fix: audio element leak and extract shared ProviderBadge component Fix GenerateAudioNode useEffect to properly clean up and recreate audio elements when outputAudio changes. Extract ProviderBadge into a shared component used by GenerateImageNode, GenerateVideoNode, and GenerateAudioNode. Co-Authored-By: Claude Opus 4.6 --- src/components/nodes/GenerateAudioNode.tsx | 83 ++++++++-------------- src/components/nodes/GenerateImageNode.tsx | 36 +--------- src/components/nodes/GenerateVideoNode.tsx | 36 +--------- src/components/nodes/ProviderBadge.tsx | 37 ++++++++++ 4 files changed, 68 insertions(+), 124 deletions(-) create mode 100644 src/components/nodes/ProviderBadge.tsx diff --git a/src/components/nodes/GenerateAudioNode.tsx b/src/components/nodes/GenerateAudioNode.tsx index f0290671..5bf5d90d 100644 --- a/src/components/nodes/GenerateAudioNode.tsx +++ b/src/components/nodes/GenerateAudioNode.tsx @@ -12,37 +12,6 @@ import { ProviderModel, ModelCapability } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useAudioVisualization } from "@/hooks/useAudioVisualization"; -// Provider badge component -function ProviderBadge({ provider }: { provider: ProviderType }) { - const providerName = provider === "kie" ? "Kie.ai" : provider === "replicate" ? "Replicate" : provider === "wavespeed" ? "WaveSpeed" : "fal.ai"; - - return ( - - {provider === "kie" ? ( - - - - ) : provider === "replicate" ? ( - - - - - - ) : provider === "wavespeed" ? ( - - - - - - ) : ( - - - - )} - - ); -} - // Audio generation capabilities const AUDIO_CAPABILITIES: ModelCapability[] = ["text-to-audio"]; @@ -105,34 +74,40 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps { - if (nodeData.outputAudio && !audioRef.current) { - const audio = new Audio(nodeData.outputAudio); - audioRef.current = audio; + if (!nodeData.outputAudio) { + if (audioRef.current) { + audioRef.current.pause(); + audioRef.current = null; + } + setIsPlaying(false); + setCurrentTime(0); + return; + } - const handleEnded = () => { - setIsPlaying(false); - setCurrentTime(0); - }; - const handleTimeUpdate = () => { - setCurrentTime(audio.currentTime); - }; + // Clean up previous audio element + if (audioRef.current) { + audioRef.current.pause(); + } - audio.addEventListener("ended", handleEnded); - audio.addEventListener("timeupdate", handleTimeUpdate); + const audio = new Audio(nodeData.outputAudio); + audioRef.current = audio; - return () => { - audio.removeEventListener("ended", handleEnded); - audio.removeEventListener("timeupdate", handleTimeUpdate); - audio.pause(); - audioRef.current = null; - }; - } + const handleEnded = () => { + setIsPlaying(false); + setCurrentTime(0); + }; + const handleTimeUpdate = () => { + setCurrentTime(audio.currentTime); + }; + + audio.addEventListener("ended", handleEnded); + audio.addEventListener("timeupdate", handleTimeUpdate); return () => { - if (audioRef.current) { - audioRef.current.pause(); - audioRef.current = null; - } + audio.removeEventListener("ended", handleEnded); + audio.removeEventListener("timeupdate", handleTimeUpdate); + audio.pause(); + audioRef.current = null; }; }, [nodeData.outputAudio]); diff --git a/src/components/nodes/GenerateImageNode.tsx b/src/components/nodes/GenerateImageNode.tsx index 686afe93..6fcddc2e 100644 --- a/src/components/nodes/GenerateImageNode.tsx +++ b/src/components/nodes/GenerateImageNode.tsx @@ -12,41 +12,7 @@ import { ProviderModel, ModelCapability } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useToast } from "@/components/Toast"; import { getImageDimensions, calculateNodeSizePreservingHeight } from "@/utils/nodeDimensions"; - -// Provider badge component - shows provider icon for all providers -function ProviderBadge({ provider }: { provider: ProviderType }) { - const providerName = provider === "gemini" ? "Gemini" : provider === "replicate" ? "Replicate" : provider === "kie" ? "Kie.ai" : provider === "wavespeed" ? "WaveSpeed" : "fal.ai"; - - return ( - - {provider === "gemini" ? ( - - - - ) : provider === "replicate" ? ( - - - - - - ) : provider === "kie" ? ( - - - - ) : provider === "wavespeed" ? ( - - - - - - ) : ( - - - - )} - - ); -} +import { ProviderBadge } from "./ProviderBadge"; // All 10 aspect ratios supported by both models const ASPECT_RATIOS: AspectRatio[] = ["1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"]; diff --git a/src/components/nodes/GenerateVideoNode.tsx b/src/components/nodes/GenerateVideoNode.tsx index 19f71b99..6eef7ac2 100644 --- a/src/components/nodes/GenerateVideoNode.tsx +++ b/src/components/nodes/GenerateVideoNode.tsx @@ -12,41 +12,7 @@ import { ProviderModel, ModelCapability } from "@/lib/providers/types"; import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog"; import { useToast } from "@/components/Toast"; import { getVideoDimensions, calculateNodeSizePreservingHeight } from "@/utils/nodeDimensions"; - -// Provider badge component - shows provider icon for all providers -function ProviderBadge({ provider }: { provider: ProviderType }) { - const providerName = provider === "gemini" ? "Gemini" : provider === "replicate" ? "Replicate" : provider === "kie" ? "Kie.ai" : provider === "wavespeed" ? "WaveSpeed" : "fal.ai"; - - return ( - - {provider === "gemini" ? ( - - - - ) : provider === "replicate" ? ( - - - - - - ) : provider === "kie" ? ( - - - - ) : provider === "wavespeed" ? ( - - - - - - ) : ( - - - - )} - - ); -} +import { ProviderBadge } from "./ProviderBadge"; // Video generation capabilities const VIDEO_CAPABILITIES: ModelCapability[] = ["text-to-video", "image-to-video"]; diff --git a/src/components/nodes/ProviderBadge.tsx b/src/components/nodes/ProviderBadge.tsx new file mode 100644 index 00000000..af165f55 --- /dev/null +++ b/src/components/nodes/ProviderBadge.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { ProviderType } from "@/types"; + +/** Provider badge component - shows provider icon for all providers */ +export function ProviderBadge({ provider }: { provider: ProviderType }) { + const providerName = provider === "gemini" ? "Gemini" : provider === "replicate" ? "Replicate" : provider === "kie" ? "Kie.ai" : provider === "wavespeed" ? "WaveSpeed" : "fal.ai"; + + return ( + + {provider === "gemini" ? ( + + + + ) : provider === "replicate" ? ( + + + + + + ) : provider === "kie" ? ( + + + + ) : provider === "wavespeed" ? ( + + + + + + ) : ( + + + + )} + + ); +}