diff --git a/frontend/src/ui/pages/CanvasWorkspace/canvas/hooks/useGenerationStream.ts b/frontend/src/ui/pages/CanvasWorkspace/canvas/hooks/useGenerationStream.ts index f6748df..4bc6d2c 100644 --- a/frontend/src/ui/pages/CanvasWorkspace/canvas/hooks/useGenerationStream.ts +++ b/frontend/src/ui/pages/CanvasWorkspace/canvas/hooks/useGenerationStream.ts @@ -34,10 +34,10 @@ export function useGenerationStream({ const toUserMessageText = useUserMessage(); const closeAgentEventsRef = useRef<(() => void) | null>(null); const closeAgentProjectPollRef = useRef<(() => void) | null>(null); - const closeImageGeneratorEventsRef = useRef<(() => void) | null>(null); + const closeImageGeneratorEventsRef = useRef void>>(new Map()); const closeCanvasActionEventsRef = useRef void>>(new Map()); const agentStreamingProjectIdRef = useRef(null); - const imageGeneratorStreamingProjectIdRef = useRef(null); + const imageGeneratorStreamingProjectIdRef = useRef>(new Set()); const canvasActionStreamingProjectIdRef = useRef>(new Set()); const completeCanvasActionStream = useCallback( @@ -49,10 +49,19 @@ export function useGenerationStream({ [setCanvasActionGenerating] ); + const completeImageGeneratorStream = useCallback( + (streamKey: string) => { + closeImageGeneratorEventsRef.current.delete(streamKey); + imageGeneratorStreamingProjectIdRef.current.delete(streamKey); + setImageGeneratorGenerating(closeImageGeneratorEventsRef.current.size > 0); + }, + [setImageGeneratorGenerating] + ); + const completeChannel = useCallback( (channel: GenerationChannel) => { if (channel === "image-generator") { - setImageGeneratorGenerating(false); + setImageGeneratorGenerating(closeImageGeneratorEventsRef.current.size > 0); return; } setAgentGenerating(false); @@ -66,12 +75,12 @@ export function useGenerationStream({ if (!threadId) return; if (channel === "canvas-action") { - if (closeCanvasActionEventsRef.current.has(streamKey)) return; + closeCanvasActionEventsRef.current.get(streamKey)?.(); setCanvasActionGenerating(true); canvasActionStreamingProjectIdRef.current.add(streamKey); } else { - closeImageGeneratorEventsRef.current?.(); - imageGeneratorStreamingProjectIdRef.current = streamKey; + closeImageGeneratorEventsRef.current.get(streamKey)?.(); + imageGeneratorStreamingProjectIdRef.current.add(streamKey); setImageGeneratorGenerating(true); } @@ -90,9 +99,7 @@ export function useGenerationStream({ completeCanvasActionStream(streamKey); return; } - completeChannel(channel); - imageGeneratorStreamingProjectIdRef.current = null; - closeImageGeneratorEventsRef.current = null; + completeImageGeneratorStream(streamKey); }; const poll = () => { void (async () => { @@ -128,11 +135,11 @@ export function useGenerationStream({ if (channel === "canvas-action") { closeCanvasActionEventsRef.current.set(streamKey, close); } else { - closeImageGeneratorEventsRef.current = close; + closeImageGeneratorEventsRef.current.set(streamKey, close); } poll(); }, - [applyProject, completeCanvasActionStream, completeChannel, toUserMessageText, messages.generateError, setCanvasActionGenerating, setError, setImageGeneratorGenerating] + [applyProject, completeCanvasActionStream, completeImageGeneratorStream, toUserMessageText, messages.generateError, setCanvasActionGenerating, setError, setImageGeneratorGenerating] ); const startAgentProjectPolling = useCallback( @@ -253,14 +260,24 @@ export function useGenerationStream({ (nextProjectId: string, payload: AgentChatPayload, channel: GenerationChannel = "agent") => { const streamKey = generationStreamKey(nextProjectId, payload.threadId); if (channel !== "agent") { - const closeRef = generationCloseRef(channel, closeAgentEventsRef, closeImageGeneratorEventsRef, closeCanvasActionEventsRef); - const streamingRef = generationStreamingRef(channel, agentStreamingProjectIdRef, imageGeneratorStreamingProjectIdRef, canvasActionStreamingProjectIdRef); - closeRef.current?.(); + if (channel === "canvas-action") { + closeCanvasActionEventsRef.current.get(streamKey)?.(); + setCanvasActionGenerating(true); + canvasActionStreamingProjectIdRef.current.add(streamKey); + } else { + closeImageGeneratorEventsRef.current.get(streamKey)?.(); + setImageGeneratorGenerating(true); + imageGeneratorStreamingProjectIdRef.current.add(streamKey); + } let closed = false; - closeRef.current = () => { + const close = () => { closed = true; }; - streamingRef.current = streamKey; + if (channel === "canvas-action") { + closeCanvasActionEventsRef.current.set(streamKey, close); + } else { + closeImageGeneratorEventsRef.current.set(streamKey, close); + } void designGateway .agentChat(nextProjectId, payload) .then((response) => { @@ -270,10 +287,12 @@ export function useGenerationStream({ }) .catch((error: unknown) => { if (closed) return; - completeChannel(channel); + if (channel === "canvas-action") { + completeCanvasActionStream(streamKey); + } else { + completeImageGeneratorStream(streamKey); + } setError(toUserMessageText(error, "generateError")); - streamingRef.current = null; - closeRef.current = null; }); return; } @@ -319,7 +338,7 @@ export function useGenerationStream({ } }); }, - [appendThinkingMessage, applyProject, completeChannel, toUserMessageText, messages.generateError, messages.generateTimeout, setError, startAgentProjectPolling, startGeneratorTaskPolling] + [appendThinkingMessage, applyProject, completeCanvasActionStream, completeChannel, completeImageGeneratorStream, toUserMessageText, messages.generateError, messages.generateTimeout, setCanvasActionGenerating, setError, setImageGeneratorGenerating, startAgentProjectPolling, startGeneratorTaskPolling] ); const stopGenerationStream = useCallback( @@ -331,6 +350,13 @@ export function useGenerationStream({ setCanvasActionGenerating(false); return; } + if (channel === "image-generator") { + closeImageGeneratorEventsRef.current.forEach((close) => close()); + closeImageGeneratorEventsRef.current.clear(); + imageGeneratorStreamingProjectIdRef.current.clear(); + setImageGeneratorGenerating(false); + return; + } const closeRef = generationCloseRef(channel, closeAgentEventsRef, closeImageGeneratorEventsRef, closeCanvasActionEventsRef); const streamingRef = generationStreamingRef(channel, agentStreamingProjectIdRef, imageGeneratorStreamingProjectIdRef, canvasActionStreamingProjectIdRef); closeRef.current?.(); @@ -342,21 +368,21 @@ export function useGenerationStream({ streamingRef.current = null; completeChannel(channel); }, - [completeChannel, setCanvasActionGenerating] + [completeChannel, setCanvasActionGenerating, setImageGeneratorGenerating] ); useEffect(() => { return () => { closeAgentEventsRef.current?.(); closeAgentProjectPollRef.current?.(); - closeImageGeneratorEventsRef.current?.(); + closeImageGeneratorEventsRef.current.forEach((close) => close()); closeCanvasActionEventsRef.current.forEach((close) => close()); closeAgentEventsRef.current = null; closeAgentProjectPollRef.current = null; - closeImageGeneratorEventsRef.current = null; + closeImageGeneratorEventsRef.current.clear(); closeCanvasActionEventsRef.current.clear(); agentStreamingProjectIdRef.current = null; - imageGeneratorStreamingProjectIdRef.current = null; + imageGeneratorStreamingProjectIdRef.current.clear(); canvasActionStreamingProjectIdRef.current.clear(); }; }, []); @@ -375,20 +401,18 @@ export function useGenerationStream({ function generationCloseRef( channel: GenerationChannel, agentRef: RefObject<(() => void) | null>, - imageGeneratorRef: RefObject<(() => void) | null>, + _imageGeneratorRef: RefObject void>>, _canvasActionRef: RefObject void>> ) { - if (channel === "image-generator") return imageGeneratorRef; return agentRef; } function generationStreamingRef( channel: GenerationChannel, agentRef: RefObject, - imageGeneratorRef: RefObject, + _imageGeneratorRef: RefObject>, _canvasActionRef: RefObject> ) { - if (channel === "image-generator") return imageGeneratorRef; return agentRef; }