refactor(canvas): support multiple concurrent image generator streams
Track image-generator streams in a keyed Map/Set instead of a single ref so several placeholders can generate at once without cancelling each other. Generating state is derived from the number of open streams. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<Map<string, () => void>>(new Map());
|
||||
const closeCanvasActionEventsRef = useRef<Map<string, () => void>>(new Map());
|
||||
const agentStreamingProjectIdRef = useRef<string | null>(null);
|
||||
const imageGeneratorStreamingProjectIdRef = useRef<string | null>(null);
|
||||
const imageGeneratorStreamingProjectIdRef = useRef<Set<string>>(new Set());
|
||||
const canvasActionStreamingProjectIdRef = useRef<Set<string>>(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<Map<string, () => void>>,
|
||||
_canvasActionRef: RefObject<Map<string, () => void>>
|
||||
) {
|
||||
if (channel === "image-generator") return imageGeneratorRef;
|
||||
return agentRef;
|
||||
}
|
||||
|
||||
function generationStreamingRef(
|
||||
channel: GenerationChannel,
|
||||
agentRef: RefObject<string | null>,
|
||||
imageGeneratorRef: RefObject<string | null>,
|
||||
_imageGeneratorRef: RefObject<Set<string>>,
|
||||
_canvasActionRef: RefObject<Set<string>>
|
||||
) {
|
||||
if (channel === "image-generator") return imageGeneratorRef;
|
||||
return agentRef;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user