feat(web): show grid spinner on agent tab while session is working (#836)

This commit is contained in:
Carlos Florêncio
2026-05-06 20:25:34 +01:00
committed by GitHub
parent 7ec639bf2f
commit 7642b984c6
4 changed files with 43 additions and 13 deletions
@@ -4,6 +4,7 @@ import { useCallback, useMemo } from "react";
import { IconLoader2 } from "@tabler/icons-react";
import { Button } from "@kandev/ui/button";
import { AgentLogo } from "@/components/agent-logo";
import { GridSpinner } from "@/components/grid-spinner";
import { SessionTabs, type SessionTab } from "@/components/session-tabs";
import { useAppStore } from "@/components/state-provider";
import { useToast } from "@/components/toast-provider";
@@ -11,12 +12,13 @@ import { useSessionResumption } from "@/hooks/domains/session/use-session-resump
import { useTaskSessions } from "@/hooks/use-task-sessions";
import type { UseEnsureTaskSessionResult } from "@/hooks/domains/session/use-ensure-task-session";
import type { AgentProfileOption } from "@/lib/state/slices";
import type { TaskSession, TaskSessionState } from "@/lib/types/http";
import type { TaskSession } from "@/lib/types/http";
import { getWebSocketClient } from "@/lib/ws/connection";
import { PassthroughTerminal } from "./passthrough-terminal";
import { TaskChatPanel } from "./task-chat-panel";
import {
buildAgentLabelsById,
isSessionActive,
pickActiveSessionId,
resolveAgentLabelFor,
sortSessions,
@@ -174,12 +176,8 @@ function PreviewSessionBody({ session, taskId }: { session: TaskSession; taskId:
);
}
function isSessionActive(state: TaskSessionState): boolean {
return state === "RUNNING" || state === "STARTING";
}
function RunningSpinner() {
return <IconLoader2 className="h-3 w-3 shrink-0 text-blue-500 animate-spin" />;
return <GridSpinner className="text-muted-foreground shrink-0 text-[12px]" />;
}
function PreviewLoadingState({ label }: { label: string }) {
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import {
buildAgentLabelsById,
isSessionActive,
pickActiveSessionId,
resolveAgentLabelFor,
sortSessions,
@@ -83,3 +84,23 @@ describe("pickActiveSessionId", () => {
expect(pickActiveSessionId(sessions, null)).toBe("a");
});
});
describe("isSessionActive", () => {
it("is true while the agent is starting or running", () => {
expect(isSessionActive("RUNNING")).toBe(true);
expect(isSessionActive("STARTING")).toBe(true);
});
it("is false for terminal and idle states", () => {
expect(isSessionActive("WAITING_FOR_INPUT")).toBe(false);
expect(isSessionActive("COMPLETED")).toBe(false);
expect(isSessionActive("FAILED")).toBe(false);
expect(isSessionActive("CANCELLED")).toBe(false);
expect(isSessionActive("CREATED")).toBe(false);
});
it("is false for null/undefined", () => {
expect(isSessionActive(null)).toBe(false);
expect(isSessionActive(undefined)).toBe(false);
});
});
+4
View File
@@ -57,3 +57,7 @@ export function pickActiveSessionId(
const primary = sessions.find((s) => s.is_primary);
return (primary ?? sessions[0]).id;
}
export function isSessionActive(state: TaskSessionState | null | undefined): boolean {
return state === "RUNNING" || state === "STARTING";
}
+14 -7
View File
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react";
import { DockviewDefaultTab, type IDockviewPanelHeaderProps } from "dockview-react";
import { IconStar } from "@tabler/icons-react";
import { AgentLogo } from "@/components/agent-logo";
import { GridSpinner } from "@/components/grid-spinner";
import {
ContextMenu,
ContextMenuContent,
@@ -29,6 +30,7 @@ import {
isSessionResumable as isResumable,
} from "@/hooks/domains/session/use-session-actions";
import type { TaskSessionState } from "@/lib/types/http";
import { isSessionActive } from "./session-sort";
import { useTabMaximizeOnDoubleClick } from "./use-tab-maximize";
function useSessionTabState(sessionId: string | undefined) {
@@ -244,13 +246,18 @@ export function SessionTab(props: IDockviewPanelHeaderProps) {
{sessionNumber}
</span>
)}
{agentName && (
<AgentLogo
agentName={agentName}
size={14}
className={`ml-1.5 shrink-0${isActive ? "" : " opacity-50"}`}
/>
)}
{agentName &&
(isSessionActive(sessionState) ? (
<GridSpinner
className={`ml-1.5 shrink-0 text-[14px] text-muted-foreground${isActive ? "" : " opacity-50"}`}
/>
) : (
<AgentLogo
agentName={agentName}
size={14}
className={`ml-1.5 shrink-0${isActive ? "" : " opacity-50"}`}
/>
))}
<DockviewDefaultTab {...props} hideClose={sessionCount <= 1} />
</div>
</ContextMenuTrigger>