Files
tutti/packages/agent/gui/agent-gui/agentGuiNode/controller/useAgentGUIConversationBatchDeletion.ts
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

289 lines
9.4 KiB
TypeScript

import {
useCallback,
type Dispatch,
type RefObject,
type SetStateAction
} from "react";
import { flushSync } from "react-dom";
import type { AgentGUIRuntime } from "../../../agentActivityRuntime";
import type { useAgentHostApi } from "../../../agentActivityHost";
import type { AgentSessionViewRef } from "../../../contexts/workspace/presentation/renderer/agentSessions/useAgentSessionPagingState";
import type { AgentGUINodeData } from "../../../types";
import type { AgentGUIConversationSummary } from "../model/agentGuiConversationModel";
import type {
AgentComposerDraft,
SubmittedDraftSnapshot
} from "../model/agentGuiNodeTypes";
import { resolveAgentComposerDraftScopeKey } from "../model/agentComposerDraftScope";
import { deleteSubmittedDraftSnapshotsForScopes } from "./agentGuiController.draftMessageHelpers";
import { getAgentGUIErrorMessage } from "./agentGuiController.errors";
import { omitConversationLocalState } from "./agentGuiController.interactiveHelpers";
import {
reportAgentGUIRuntimeError,
showAgentGUIControllerErrorToast
} from "./agentGuiController.reporting";
import { type ConversationIntent } from "./useAgentConversationSelection";
export interface UseAgentGUIConversationBatchDeletionInput {
isDeletingProjectConversations: boolean;
conversationsRef: RefObject<AgentGUIConversationSummary[]>;
setDetailError: Dispatch<SetStateAction<string | null>>;
setListError: Dispatch<SetStateAction<string | null>>;
deleteAgentSessionView: (ref: AgentSessionViewRef) => void;
sessionViewRef: (agentSessionId: string | null | undefined) => {
workspaceId: string;
agentSessionId: string | null | undefined;
origin: string;
};
setDraftByScopeKey: Dispatch<
SetStateAction<Record<string, AgentComposerDraft>>
>;
submittedDraftSnapshotsRef: RefObject<Record<string, SubmittedDraftSnapshot>>;
activeConversationIdRef: RefObject<string | null>;
markSelectedConversationDetailPending: (
agentSessionId: string
) => string | null;
setIntent: Dispatch<SetStateAction<ConversationIntent>>;
setIsLoadingMessages: Dispatch<SetStateAction<boolean>>;
setActiveConversationId: Dispatch<SetStateAction<string | null>>;
persistActiveConversation: (agentSessionId: string | null) => void;
removeConversations: (agentSessionIds: readonly string[]) => void;
workspaceId: string;
setIsDeletingProjectConversations: Dispatch<SetStateAction<boolean>>;
agentActivityRuntime: AgentGUIRuntime;
dataRef: RefObject<AgentGUINodeData>;
agentHostApi: ReturnType<typeof useAgentHostApi>;
}
export function useAgentGUIConversationBatchDeletion(
input: UseAgentGUIConversationBatchDeletionInput
) {
const {
isDeletingProjectConversations,
conversationsRef,
setDetailError,
setListError,
deleteAgentSessionView,
sessionViewRef,
setDraftByScopeKey,
submittedDraftSnapshotsRef,
activeConversationIdRef,
markSelectedConversationDetailPending,
setIntent,
setIsLoadingMessages,
setActiveConversationId,
persistActiveConversation,
removeConversations,
workspaceId,
setIsDeletingProjectConversations,
agentActivityRuntime,
dataRef,
agentHostApi
} = input;
const finalizeConversationBatchDeletion = useCallback(
(targetIds: Set<string>) => {
for (const id of targetIds) {
deleteAgentSessionView(sessionViewRef(id));
}
const deletedScopeKeys = new Set(
[...targetIds].map((agentSessionId) =>
resolveAgentComposerDraftScopeKey({ agentSessionId })
)
);
setDraftByScopeKey((current) =>
omitConversationLocalState(current, deletedScopeKeys)
);
deleteSubmittedDraftSnapshotsForScopes({
snapshots: submittedDraftSnapshotsRef.current,
scopeKeys: deletedScopeKeys,
targetAgentSessionIds: targetIds
});
},
[
deleteAgentSessionView,
sessionViewRef,
setDraftByScopeKey,
submittedDraftSnapshotsRef
]
);
const confirmDeleteProjectConversations = useCallback(
async (
sectionKey?: string,
agentTargetId?: string | null
): Promise<string[]> => {
const normalizedSectionKey = sectionKey?.trim() ?? "";
const listDeletionCandidates =
agentActivityRuntime.listSessionSectionDeletionCandidates;
if (
!normalizedSectionKey ||
isDeletingProjectConversations ||
!listDeletionCandidates
) {
return [];
}
setDetailError(null);
setListError(null);
try {
const candidates = await listDeletionCandidates({
agentTargetId: agentTargetId?.trim() || undefined,
excludePinned: true,
sectionKey: normalizedSectionKey,
workspaceId
});
const sessionIds = candidates.sessionIds
.map((id) => id.trim())
.filter(Boolean);
if (sessionIds.length === 0) {
void agentActivityRuntime.load(workspaceId).catch(() => undefined);
}
return [...new Set(sessionIds)];
} catch (error) {
const message = getAgentGUIErrorMessage(error);
reportAgentGUIRuntimeError({
error,
phase: "delete_conversation",
provider: dataRef.current.provider,
runtime: agentActivityRuntime,
workspaceId,
context: { sectionKey: normalizedSectionKey }
});
setListError(message);
showAgentGUIControllerErrorToast(agentHostApi.toast, message);
return [];
}
},
[
agentActivityRuntime,
isDeletingProjectConversations,
agentHostApi.toast,
workspaceId
]
);
const confirmDeleteConversations = useCallback(
async (agentSessionIds: string[]): Promise<boolean> => {
if (isDeletingProjectConversations) {
return false;
}
const targetIds = new Set(
agentSessionIds.map((id) => id.trim()).filter((id) => id !== "")
);
const deleteSessionsBatch = agentActivityRuntime.deleteSessionsBatch;
if (targetIds.size === 0 || !deleteSessionsBatch) {
return targetIds.size === 0;
}
setIsDeletingProjectConversations(true);
setDetailError(null);
setListError(null);
try {
const result = await deleteSessionsBatch({
sessionIds: [...targetIds],
workspaceId
});
const activeDeletedConversationId = activeConversationIdRef.current;
if (
activeDeletedConversationId &&
targetIds.has(activeDeletedConversationId)
) {
const nextActive =
conversationsRef.current.find(
(conversation) => !targetIds.has(conversation.id)
)?.id ?? null;
if (nextActive) {
markSelectedConversationDetailPending(nextActive);
}
activeConversationIdRef.current = nextActive;
flushSync(() => {
if (nextActive) {
setIntent({ tag: "active", id: nextActive });
} else {
setIsLoadingMessages(false);
setIntent({ tag: "home" });
}
setActiveConversationId(nextActive);
});
persistActiveConversation(nextActive);
}
if (result.cleanupFailedSessionIds.length > 0) {
try {
void Promise.resolve(
agentActivityRuntime.reportDiagnostic?.({
details: {
cleanupFailedSessionIds: result.cleanupFailedSessionIds
},
event: "agent.gui.conversation_delete.cleanup_failed",
level: "warn",
source: "agent-gui",
workspaceId
})
).catch((error) => {
console.error(
"[agent-gui-cleanup-diagnostic]",
JSON.stringify({
error: getAgentGUIErrorMessage(error),
workspaceId
})
);
});
} catch (error) {
console.error(
"[agent-gui-cleanup-diagnostic]",
JSON.stringify({
error: getAgentGUIErrorMessage(error),
workspaceId
})
);
}
}
const removedIds = new Set([...targetIds, ...result.removedSessionIds]);
removeConversations([...removedIds]);
finalizeConversationBatchDeletion(removedIds);
return true;
} catch (error) {
const message = getAgentGUIErrorMessage(error);
reportAgentGUIRuntimeError({
error,
phase: "delete_conversation",
provider: dataRef.current.provider,
runtime: agentActivityRuntime,
workspaceId,
context: {
conversationCount: targetIds.size
}
});
setListError(message);
showAgentGUIControllerErrorToast(agentHostApi.toast, message);
return false;
} finally {
setIsDeletingProjectConversations(false);
}
},
[
activeConversationIdRef,
agentActivityRuntime,
agentHostApi.toast,
conversationsRef,
dataRef,
finalizeConversationBatchDeletion,
isDeletingProjectConversations,
markSelectedConversationDetailPending,
persistActiveConversation,
removeConversations,
setActiveConversationId,
setDetailError,
setIntent,
setIsDeletingProjectConversations,
setIsLoadingMessages,
setListError,
workspaceId
]
);
return {
confirmDeleteProjectConversations,
confirmDeleteConversations
};
}