diff --git a/apps/web/src/components/v2/actions-renderer.tsx b/apps/web/src/components/v2/actions-renderer.tsx index 48a6fca5..1d26e4d9 100644 --- a/apps/web/src/components/v2/actions-renderer.tsx +++ b/apps/web/src/components/v2/actions-renderer.tsx @@ -21,6 +21,7 @@ import { AcceptedPlanStep } from "../gen-ui/accepted-plan-step"; import { PlannerGraphState } from "@open-swe/shared/open-swe/planner/types"; import { GraphState, PlanItem } from "@open-swe/shared/open-swe/types"; import { HumanResponse } from "@langchain/langgraph/prebuilt"; +import { LoadingActionsCardContent } from "./thread-view-loading"; interface AcceptedPlanEventData { planTitle: string; @@ -105,6 +106,8 @@ export function ActionsRenderer({ const [customNodeEvents, setCustomNodeEvents] = useState( [], ); + const joinedRunId = useRef(undefined); + const [streamLoading, setStreamLoading] = useState(false); const stream = useStream({ apiUrl: process.env.NEXT_PUBLIC_API_URL, assistantId: graphId, @@ -167,15 +170,31 @@ export function ActionsRenderer({ }); }, [stream.messages]); - const streamJoined = useRef(false); + // Clear streamLoading as soon as we get any content (agent has started running) useEffect(() => { - if (!streamJoined.current && runId) { - streamJoined.current = true; - // TODO: If the SDK changes go in, use this instead: - // stream.joinStream(runId, undefined, { streamMode: ["values", "messages", "custom"]}).catch(console.error); - stream.joinStream(runId).catch(console.error); + const hasContent = + (stream.messages && stream.messages.length > 0) || + customNodeEvents.length > 0; + + if (hasContent && streamLoading) { + setStreamLoading(false); } - }, [runId]); + }, [stream.messages, customNodeEvents, streamLoading]); + + // TODO: If the SDK changes go in, use this instead: + // stream.joinStream(runId, undefined, { streamMode: ["values", "messages", "custom"]}).catch(console.error); + useEffect(() => { + if (runId && runId !== joinedRunId.current) { + joinedRunId.current = runId; + setStreamLoading(true); + stream + .joinStream(runId) + .catch(console.error) + .finally(() => setStreamLoading(false)); + } else if (!runId) { + joinedRunId.current = undefined; + } + }, [runId, stream]); useEffect(() => { if (stream.isLoading) { @@ -214,6 +233,10 @@ export function ActionsRenderer({ } }, [stream.values, graphId]); + if (streamLoading) { + return ; + } + return (
{initializeEvents.length > 0 && steps.length > 0 && ( diff --git a/apps/web/src/components/v2/thread-view-loading.tsx b/apps/web/src/components/v2/thread-view-loading.tsx index 3c354f09..79259b97 100644 --- a/apps/web/src/components/v2/thread-view-loading.tsx +++ b/apps/web/src/components/v2/thread-view-loading.tsx @@ -8,9 +8,51 @@ interface ThreadViewLoadingProps { onBackToHome?: () => void; } -const isEven = (num: number) => num % 2 === 0; +function isEven(n: number) { + return n % 2 === 0; +} -export function LoadingActionsSection() { +export function LoadingActionsCardContent() { + return ( +
+ {Array.from({ length: 8 }, (_, i) => ( +
+ {isEven(i) ? ( +
+
+
+
+ ) : ( +
+
+
+
+
+
+
+
+
+
+ )} +
+ ))} +
+
+
+
+ +
+
+
+
+
+ ); +} + +export function LoadingActionsCard() { return ( @@ -24,41 +66,7 @@ export function LoadingActionsSection() {
- - {Array.from({ length: 8 }, (_, i) => ( -
- {isEven(i) ? ( -
-
-
-
- ) : ( -
-
-
-
-
-
-
-
-
-
- )} -
- ))} -
-
-
-
- -
-
-
-
-
+ ); } @@ -143,7 +151,7 @@ export function ThreadViewLoading({ onBackToHome }: ThreadViewLoadingProps) {
- +