mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(copilot): resolve dangling tool spinners when stream finishes
When the backend doesn't emit StreamToolOutputAvailable for all tool calls before StreamFinish (e.g. SDK built-in tools like WebSearch), the frontend spinners would spin forever. Add a useEffect that watches for the streaming→ready transition and marks any remaining input-available/input-streaming tool parts as output-available. Extract shared resolveInProgressTools helper used by both the stop handler (cancelled) and stream-end (completed).
This commit is contained in:
@@ -9,6 +9,7 @@ import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
|
||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import type { UIMessage } from "ai";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useChatSession } from "./useChatSession";
|
||||
@@ -16,6 +17,24 @@ import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
|
||||
|
||||
const STREAM_START_TIMEOUT_MS = 12_000;
|
||||
|
||||
/** Mark any in-progress tool parts as completed/errored so spinners stop. */
|
||||
function resolveInProgressTools(
|
||||
messages: UIMessage[],
|
||||
outcome: "completed" | "cancelled",
|
||||
): UIMessage[] {
|
||||
return messages.map((msg) => ({
|
||||
...msg,
|
||||
parts: msg.parts.map((part) =>
|
||||
"state" in part &&
|
||||
(part.state === "input-streaming" || part.state === "input-available")
|
||||
? outcome === "cancelled"
|
||||
? { ...part, state: "output-error" as const, errorText: "Cancelled" }
|
||||
: { ...part, state: "output-available" as const, output: "" }
|
||||
: part,
|
||||
),
|
||||
}));
|
||||
}
|
||||
|
||||
export function useCopilotPage() {
|
||||
const { isUserLoading, isLoggedIn } = useSupabase();
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
@@ -114,23 +133,7 @@ export function useCopilotPage() {
|
||||
// the cancel API to actually stop the executor and wait for confirmation.
|
||||
async function stop() {
|
||||
sdkStop();
|
||||
|
||||
// Mark any in-progress tool parts as errored so spinners stop.
|
||||
setMessages((prev) =>
|
||||
prev.map((msg) => ({
|
||||
...msg,
|
||||
parts: msg.parts.map((part) =>
|
||||
"state" in part &&
|
||||
(part.state === "input-streaming" || part.state === "input-available")
|
||||
? {
|
||||
...part,
|
||||
state: "output-error" as const,
|
||||
errorText: "Cancelled",
|
||||
}
|
||||
: part,
|
||||
),
|
||||
})),
|
||||
);
|
||||
setMessages((prev) => resolveInProgressTools(prev, "cancelled"));
|
||||
|
||||
if (!sessionId) return;
|
||||
try {
|
||||
@@ -199,6 +202,18 @@ export function useCopilotPage() {
|
||||
resumeStream();
|
||||
}, [hasActiveStream, sessionId, hydratedMessages, status, resumeStream]);
|
||||
|
||||
// When the stream finishes, resolve any tool parts still showing spinners.
|
||||
// This can happen if the backend didn't emit StreamToolOutputAvailable for
|
||||
// a tool call before sending StreamFinish (e.g. SDK built-in tools).
|
||||
const prevStatusRef = useRef(status);
|
||||
useEffect(() => {
|
||||
const prev = prevStatusRef.current;
|
||||
prevStatusRef.current = status;
|
||||
if (prev === "streaming" && status === "ready") {
|
||||
setMessages((msgs) => resolveInProgressTools(msgs, "completed"));
|
||||
}
|
||||
}, [status, setMessages]);
|
||||
|
||||
// Poll session endpoint when a long-running tool (create_agent, edit_agent)
|
||||
// is in progress. When the backend completes, the session data will contain
|
||||
// the final tool output — this hook detects the change and updates messages.
|
||||
|
||||
Reference in New Issue
Block a user