mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-03 22:33:19 +00:00
Part of the domain-reorg adoption (build plan step C6): fork content,
upstream layout. git mv's all 80 pure-rename files from the move-map
(agent-scoped components/lib -> ui/src/features/{agents,automations,
reviews,settings}/...), including ported/ splitting into
features/agents/experiments/ (still-unintegrated desktop-host ports)
and features/agents/components/chat/ (CloudPromptBar, CodeBlock,
DiffView, Logo, Markdown, ReplyCard, ShellCommand, ToolExecution --
files actually wired into the live dashboard). Fork-diverged files
(PlanReview.tsx, WorkflowApprovalCard.tsx, AgentsSidebar.tsx,
SidebarFilterMenu.tsx, AgentGitPanel.tsx, AutomationEditor.tsx,
DiffView.tsx, CloudPromptBar.tsx) keep fork content -- verified via
diff that only import lines changed.
Rewrote @/components/agents and @/lib/agents imports across all 54
importer files plus the moved files' own internal imports (grep-driven,
85-entry alias map covering every old->new path pair). Two hazards
caught only by the build gate (not tsc, since both files sit under the
new experiments/** tsconfig exclude): ui/src/lib/notifications.ts had
a relative `./agents/types` import (no `@/` alias) that the grep missed;
and features/agents/experiments/index.ts's barrel re-export of the
messages module needed to switch from an `@/` alias to a relative
import (`../components/messages`) after landing inside the newly
tsconfig-excluded experiments/ directory -- vite-tsconfig-paths failed
to resolve it at build time even though tsc stayed silent.
AgentPromptBar.tsx (the 2-line CloudPromptBar re-export shim) moves to
features/agents/components/ with its export path retargeted at
CloudPromptBar's new chat/ location -- a D/A pair, not a content loss,
since the import-path edit drops it below git's rename-similarity
threshold. ported/index.ts is the same D/A story for the same reason.
Swapped the 8-file ported/ exclude lists in tsconfig.json and
eslint.config.js for the single `src/features/agents/experiments/**`
glob (upstream's simplification, no behavior change). Deleted
ui/pnpm-workspace.yaml (lockfile hygiene -- bun stays the toolchain).
Gates: bunx tsc --noEmit (clean), bun run build (clean after the two
notifications.ts / experiments-index.ts fixes above), bun run test
(33/33), and the full Playwright E2E suite against the real langgraph
dev server + built dashboard (9/9).
95 lines
3 KiB
TypeScript
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>
|
|
);
|
|
}
|