feat(web): show grid spinner on agent tab while session is working (#836)
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user