Merge pull request #61 from getpaseo/fix/agent-archive-loading-state
Fix archive UX with shared pending state and agent-screen overlay
This commit is contained in:
@@ -86,13 +86,15 @@ export function useAllAgentsList(options?: {
|
||||
const snapshot = entry.agent;
|
||||
const normalized = normalizeAgentSnapshot(snapshot, serverId);
|
||||
const live = liveAgents?.get(snapshot.id);
|
||||
list.push(
|
||||
toAggregatedAgent({
|
||||
source: live ?? normalized,
|
||||
serverId,
|
||||
serverLabel,
|
||||
})
|
||||
);
|
||||
const aggregated = toAggregatedAgent({
|
||||
source: live ?? normalized,
|
||||
serverId,
|
||||
serverLabel,
|
||||
});
|
||||
if (aggregated.archivedAt) {
|
||||
continue;
|
||||
}
|
||||
list.push(aggregated);
|
||||
}
|
||||
|
||||
list.sort((left, right) => {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { __private__ } from "./use-archive-agent";
|
||||
|
||||
describe("useArchiveAgent", () => {
|
||||
it("tracks pending archive state in shared react-query cache", () => {
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
expect(
|
||||
__private__.isAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-1",
|
||||
})
|
||||
).toBe(false);
|
||||
|
||||
__private__.setAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-1",
|
||||
isArchiving: true,
|
||||
});
|
||||
|
||||
expect(
|
||||
__private__.isAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-1",
|
||||
})
|
||||
).toBe(true);
|
||||
expect(
|
||||
__private__.isAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-2",
|
||||
})
|
||||
).toBe(false);
|
||||
|
||||
__private__.setAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-1",
|
||||
isArchiving: false,
|
||||
});
|
||||
|
||||
expect(
|
||||
__private__.isAgentArchiving({
|
||||
queryClient,
|
||||
serverId: "server-a",
|
||||
agentId: "agent-1",
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,157 @@
|
||||
import { useCallback } from "react";
|
||||
import { useMutation, useQuery, useQueryClient, type QueryClient } from "@tanstack/react-query";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
|
||||
export const ARCHIVE_AGENT_PENDING_QUERY_KEY = ["archive-agent-pending"] as const;
|
||||
|
||||
export interface ArchiveAgentInput {
|
||||
serverId: string;
|
||||
agentId: string;
|
||||
}
|
||||
|
||||
type ArchiveAgentPendingState = Record<string, true>;
|
||||
|
||||
interface SetAgentArchivingInput extends ArchiveAgentInput {
|
||||
queryClient: QueryClient;
|
||||
isArchiving: boolean;
|
||||
}
|
||||
|
||||
interface IsAgentArchivingInput extends ArchiveAgentInput {
|
||||
queryClient: QueryClient;
|
||||
}
|
||||
|
||||
function toArchiveKey(input: ArchiveAgentInput): string {
|
||||
const serverId = input.serverId.trim();
|
||||
const agentId = input.agentId.trim();
|
||||
if (!serverId || !agentId) {
|
||||
return "";
|
||||
}
|
||||
return `${serverId}:${agentId}`;
|
||||
}
|
||||
|
||||
function readPendingState(queryClient: QueryClient): ArchiveAgentPendingState {
|
||||
return (
|
||||
queryClient.getQueryData<ArchiveAgentPendingState>(
|
||||
ARCHIVE_AGENT_PENDING_QUERY_KEY
|
||||
) ?? {}
|
||||
);
|
||||
}
|
||||
|
||||
function setAgentArchiving(input: SetAgentArchivingInput): void {
|
||||
const key = toArchiveKey(input);
|
||||
if (!key) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.queryClient.setQueryData<ArchiveAgentPendingState>(
|
||||
ARCHIVE_AGENT_PENDING_QUERY_KEY,
|
||||
(current) => {
|
||||
const state = current ?? {};
|
||||
if (input.isArchiving) {
|
||||
if (state[key]) {
|
||||
return state;
|
||||
}
|
||||
return { ...state, [key]: true };
|
||||
}
|
||||
|
||||
if (!state[key]) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const next = { ...state };
|
||||
delete next[key];
|
||||
return next;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function isAgentArchiving(input: IsAgentArchivingInput): boolean {
|
||||
const key = toArchiveKey(input);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
return Boolean(readPendingState(input.queryClient)[key]);
|
||||
}
|
||||
|
||||
export function clearArchiveAgentPending(input: IsAgentArchivingInput): void {
|
||||
setAgentArchiving({
|
||||
...input,
|
||||
isArchiving: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function useArchiveAgent() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const pendingQuery = useQuery({
|
||||
queryKey: ARCHIVE_AGENT_PENDING_QUERY_KEY,
|
||||
queryFn: async (): Promise<ArchiveAgentPendingState> => ({}),
|
||||
initialData: {} as ArchiveAgentPendingState,
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
});
|
||||
|
||||
const archiveMutation = useMutation({
|
||||
mutationFn: async (input: ArchiveAgentInput) => {
|
||||
const client = useSessionStore.getState().sessions[input.serverId]?.client ?? null;
|
||||
if (!client) {
|
||||
throw new Error("Daemon client not available");
|
||||
}
|
||||
await client.archiveAgent(input.agentId);
|
||||
},
|
||||
onMutate: (input) => {
|
||||
setAgentArchiving({
|
||||
queryClient,
|
||||
serverId: input.serverId,
|
||||
agentId: input.agentId,
|
||||
isArchiving: true,
|
||||
});
|
||||
},
|
||||
onError: (_error, input) => {
|
||||
clearArchiveAgentPending({
|
||||
queryClient,
|
||||
serverId: input.serverId,
|
||||
agentId: input.agentId,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const archiveAgent = useCallback(
|
||||
async (input: ArchiveAgentInput): Promise<void> => {
|
||||
if (
|
||||
isAgentArchiving({
|
||||
queryClient,
|
||||
serverId: input.serverId,
|
||||
agentId: input.agentId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await archiveMutation.mutateAsync(input);
|
||||
},
|
||||
[archiveMutation, queryClient]
|
||||
);
|
||||
|
||||
const isArchivingAgent = useCallback(
|
||||
(input: ArchiveAgentInput): boolean => {
|
||||
const key = toArchiveKey(input);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
return Boolean((pendingQuery.data ?? {})[key]);
|
||||
},
|
||||
[pendingQuery.data]
|
||||
);
|
||||
|
||||
return {
|
||||
archiveAgent,
|
||||
isArchivingAgent,
|
||||
};
|
||||
}
|
||||
|
||||
export const __private__ = {
|
||||
toArchiveKey,
|
||||
readPendingState,
|
||||
setAgentArchiving,
|
||||
isAgentArchiving,
|
||||
};
|
||||
Reference in New Issue
Block a user