open-swe/ui/src/features/agents/components/messages/ThinkingSpinner.tsx
Adam Moussa 58941d1edd
fix: normalize dashboard label rendering (#1732)
* fix: normalize tool call rendering

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* fix: simplify tool call labels

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

* fix: capitalize dashboard UI labels

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>

---------

Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com>
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
(cherry picked from commit d714586ce6928e4848214a8fce78cc4597ff2684)

Co-authored-by: Ramon Nogueira <ramon.nogueira@langchain.dev>
2026-07-17 16:22:38 -04:00

95 lines
3 KiB
TypeScript

import { useEffect, useRef, useState } from "react";
import { formatElapsed } from "@/lib/utils";
const BUSY_TEXTS: Array<{ present: string; past: string }> = [
{ present: "Vibing...", past: "Vibed" },
{ present: "Noodling...", past: "Noodled" },
{ present: "Pondering...", past: "Pondered" },
{ present: "Thinking really hard...", past: "Thought really hard" },
{ present: "Spinning up...", past: "Spun up" },
{ present: "Connecting the dots...", past: "Connected the dots" },
{ present: "Brewing ideas...", past: "Brewed ideas" },
{ present: "Cooking...", past: "Cooked" },
{ present: "Crunching...", past: "Crunched" },
{ present: "Scheming...", past: "Schemed" },
{ present: "Processing...", past: "Processed" },
];
const THINKING_SETTLE_MS = 300;
export function ThinkingSpinner({
isActive,
settingUpSandbox = false,
}: {
isActive: boolean;
settingUpSandbox?: boolean;
}) {
const [textIdx, setTextIdx] = useState(0);
const [done, setDone] = useState<{ past: string; elapsed: string } | null>(null);
const [settledActive, setSettledActive] = useState(isActive);
const startTimeRef = useRef(0);
const sessionActiveRef = useRef(false);
const textIdxRef = useRef(textIdx);
const settingUpSandboxRef = useRef(settingUpSandbox);
textIdxRef.current = textIdx;
useEffect(() => {
settingUpSandboxRef.current = settingUpSandbox;
}, [settingUpSandbox]);
useEffect(() => {
if (isActive) {
setSettledActive(true);
return;
}
const id = window.setTimeout(() => setSettledActive(false), THINKING_SETTLE_MS);
return () => window.clearTimeout(id);
}, [isActive]);
useEffect(() => {
if (settledActive) {
if (!sessionActiveRef.current) {
sessionActiveRef.current = true;
startTimeRef.current = Date.now();
setTextIdx(Math.floor(Math.random() * BUSY_TEXTS.length));
setDone(null);
}
return;
}
if (!sessionActiveRef.current) return;
sessionActiveRef.current = false;
setDone({
past: settingUpSandboxRef.current
? "Set up sandbox"
: BUSY_TEXTS[textIdxRef.current]?.past ?? "",
elapsed: formatElapsed(Date.now() - startTimeRef.current),
});
}, [settledActive]);
useEffect(() => {
if (!settledActive || settingUpSandbox) return;
const BUSY_TEXT_ROTATE_INTERVAL_MS = 12000;
const id = setInterval(() => setTextIdx((i) => (i + 1) % BUSY_TEXTS.length), BUSY_TEXT_ROTATE_INTERVAL_MS);
return () => clearInterval(id);
}, [settledActive, settingUpSandbox]);
const showActive = isActive || settledActive;
if (!showActive && !done) return null;
if (done && !showActive) {
return (
<div className="my-2 flex items-center gap-2">
<span className="text-xs text-[color:var(--ui-text-dim)]">{done.past} for {done.elapsed}</span>
</div>
);
}
return (
<div className="my-2 flex items-center gap-2">
<span className="shimmer-text text-xs">
{settingUpSandbox ? "Setting up sandbox..." : BUSY_TEXTS[textIdx]?.present ?? ""}
</span>
</div>
);
}