diff --git a/site/src/pages/AgentsPage/AgentDetail.stories.tsx b/site/src/pages/AgentsPage/AgentDetail.stories.tsx index 83d43be700..08964e6b33 100644 --- a/site/src/pages/AgentsPage/AgentDetail.stories.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.stories.tsx @@ -43,6 +43,7 @@ const AgentDetailLayout: FC = () => { setChatErrorReason: () => {}, clearChatErrorReason: () => {}, requestArchiveAgent: () => {}, + requestArchiveAndDeleteWorkspace: () => {}, isSidebarCollapsed: false, onToggleSidebarCollapsed: () => {}, } satisfies AgentsOutletContext diff --git a/site/src/pages/AgentsPage/AgentDetail.tsx b/site/src/pages/AgentsPage/AgentDetail.tsx index 54de270475..63b271c479 100644 --- a/site/src/pages/AgentsPage/AgentDetail.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.tsx @@ -74,6 +74,8 @@ const noopClearChatErrorReason: AgentsOutletContext["clearChatErrorReason"] = () => {}; const noopRequestArchiveAgent: AgentsOutletContext["requestArchiveAgent"] = () => {}; +const noopRequestArchiveAndDeleteWorkspace: AgentsOutletContext["requestArchiveAndDeleteWorkspace"] = + () => {}; const lastModelConfigIDStorageKey = "agents.last-model-config-id"; type ChatStoreHandle = ReturnType["store"]; @@ -489,6 +491,9 @@ const AgentDetail: FC = () => { outletContext?.clearChatErrorReason ?? noopClearChatErrorReason; const requestArchiveAgent = outletContext?.requestArchiveAgent ?? noopRequestArchiveAgent; + const requestArchiveAndDeleteWorkspace = + outletContext?.requestArchiveAndDeleteWorkspace ?? + noopRequestArchiveAndDeleteWorkspace; const isSidebarCollapsed = outletContext?.isSidebarCollapsed ?? false; const onToggleSidebarCollapsed = outletContext?.onToggleSidebarCollapsed ?? (() => {}); @@ -870,6 +875,13 @@ const AgentDetail: FC = () => { requestArchiveAgent(agentId); }; + const handleArchiveAndDeleteWorkspaceAction = () => { + if (!agentId || isArchived) { + return; + } + requestArchiveAndDeleteWorkspace(agentId); + }; + if (chatQuery.isLoading) { return (
@@ -888,6 +900,8 @@ const AgentDetail: FC = () => { }} onOpenParentChat={() => {}} onArchiveAgent={() => {}} + onArchiveAndDeleteWorkspace={() => {}} + hasWorkspace={false} isSidebarCollapsed={isSidebarCollapsed} onToggleSidebarCollapsed={onToggleSidebarCollapsed} /> @@ -957,6 +971,8 @@ const AgentDetail: FC = () => { }} onOpenParentChat={() => {}} onArchiveAgent={() => {}} + onArchiveAndDeleteWorkspace={() => {}} + hasWorkspace={false} isSidebarCollapsed={isSidebarCollapsed} onToggleSidebarCollapsed={onToggleSidebarCollapsed} /> @@ -995,6 +1011,8 @@ const AgentDetail: FC = () => { onViewWorkspace: handleViewWorkspace, }} onArchiveAgent={handleArchiveAgentAction} + onArchiveAndDeleteWorkspace={handleArchiveAndDeleteWorkspaceAction} + hasWorkspace={Boolean(workspaceId)} isArchived={isArchived} isSidebarCollapsed={isSidebarCollapsed} onToggleSidebarCollapsed={onToggleSidebarCollapsed} diff --git a/site/src/pages/AgentsPage/AgentDetail/TopBar.stories.tsx b/site/src/pages/AgentsPage/AgentDetail/TopBar.stories.tsx index 06b84bcd8b..7848d6e590 100644 --- a/site/src/pages/AgentsPage/AgentDetail/TopBar.stories.tsx +++ b/site/src/pages/AgentsPage/AgentDetail/TopBar.stories.tsx @@ -28,6 +28,7 @@ const defaultProps = { onViewWorkspace: () => {}, }, onArchiveAgent: () => {}, + onArchiveAndDeleteWorkspace: () => {}, isSidebarCollapsed: false, onToggleSidebarCollapsed: () => {}, } satisfies React.ComponentProps; diff --git a/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx b/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx index 9cdffdc7b3..37ba256687 100644 --- a/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx +++ b/site/src/pages/AgentsPage/AgentDetail/TopBar.tsx @@ -18,6 +18,7 @@ import { PanelLeftIcon, PanelRightCloseIcon, PanelRightOpenIcon, + Trash2Icon, } from "lucide-react"; import { UserDropdown } from "modules/dashboard/Navbar/UserDropdown/UserDropdown"; import { useDashboard } from "modules/dashboard/useDashboard"; @@ -81,6 +82,8 @@ type AgentDetailTopBarProps = { diff: DiffPanelState; workspace: WorkspaceActions; onArchiveAgent: () => void; + onArchiveAndDeleteWorkspace: () => void; + hasWorkspace?: boolean; isArchived?: boolean; isSidebarCollapsed: boolean; onToggleSidebarCollapsed: () => void; @@ -93,6 +96,8 @@ export const AgentDetailTopBar: FC = ({ diff, workspace, onArchiveAgent, + onArchiveAndDeleteWorkspace, + hasWorkspace, isArchived, isSidebarCollapsed, onToggleSidebarCollapsed, @@ -208,7 +213,16 @@ export const AgentDetailTopBar: FC = ({ Archive Agent )} - + {!isArchived && hasWorkspace && ( + + + Archive & Delete Workspace + + )} + {" "}
diff --git a/site/src/pages/AgentsPage/AgentsPage.tsx b/site/src/pages/AgentsPage/AgentsPage.tsx index c801a00529..0d426eb9f2 100644 --- a/site/src/pages/AgentsPage/AgentsPage.tsx +++ b/site/src/pages/AgentsPage/AgentsPage.tsx @@ -1,4 +1,4 @@ -import { watchChats } from "api/api"; +import { API, watchChats } from "api/api"; import { getErrorMessage } from "api/errors"; import { archiveChat, @@ -88,6 +88,7 @@ export interface AgentsOutletContext { setChatErrorReason: (chatId: string, reason: string) => void; clearChatErrorReason: (chatId: string) => void; requestArchiveAgent: (chatId: string) => void; + requestArchiveAndDeleteWorkspace: (chatId: string) => void; isSidebarCollapsed: boolean; onToggleSidebarCollapsed: () => void; } @@ -151,7 +152,11 @@ const AgentsPage: FC = () => { const chatModelConfigsQuery = useQuery(chatModelConfigs()); const createMutation = useMutation(createChat(queryClient)); const archiveMutation = useMutation(archiveChat(queryClient)); + const deleteWorkspaceMutation = useMutation({ + mutationFn: (workspaceId: string) => API.deleteWorkspace(workspaceId), + }); const [archivingChatId, setArchivingChatId] = useState(null); + const [isConfigureAgentsDialogOpen, setConfigureAgentsDialogOpen] = useState(false); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); @@ -225,8 +230,6 @@ const AgentsPage: FC = () => { try { await archiveMutation.mutateAsync(chatId); clearChatErrorReason(chatId); - // Invalidate the individual chat query so the detail view - // picks up the archived flag without a redirect. await queryClient.invalidateQueries({ queryKey: chatKey(chatId) }); toast.success("Agent archived."); } catch (error) { @@ -237,6 +240,44 @@ const AgentsPage: FC = () => { }, [archiveMutation, queryClient, clearChatErrorReason], ); + const requestArchiveAndDeleteWorkspace = useCallback( + async (chatId: string) => { + if (archiveMutation.isPending || deleteWorkspaceMutation.isPending) { + return; + } + + setArchivingChatId(chatId); + + try { + await archiveMutation.mutateAsync(chatId); + clearChatErrorReason(chatId); + await queryClient.invalidateQueries({ queryKey: chatKey(chatId) }); + toast.success("Agent archived."); + } catch (error) { + toast.error(getErrorMessage(error, "Failed to archive agent.")); + return; + } finally { + setArchivingChatId(null); + } + + const chat = chatList.find((c) => c.id === chatId); + if (chat?.workspace_id) { + try { + await deleteWorkspaceMutation.mutateAsync(chat.workspace_id); + toast.success("Workspace deletion initiated."); + } catch (error) { + toast.error(getErrorMessage(error, "Failed to delete workspace.")); + } + } + }, + [ + archiveMutation, + deleteWorkspaceMutation, + queryClient, + clearChatErrorReason, + chatList, + ], + ); const handleToggleSidebarCollapsed = useCallback( () => setIsSidebarCollapsed((prev) => !prev), [], @@ -247,6 +288,7 @@ const AgentsPage: FC = () => { setChatErrorReason, clearChatErrorReason, requestArchiveAgent, + requestArchiveAndDeleteWorkspace, isSidebarCollapsed, onToggleSidebarCollapsed: handleToggleSidebarCollapsed, }), @@ -255,6 +297,7 @@ const AgentsPage: FC = () => { setChatErrorReason, clearChatErrorReason, requestArchiveAgent, + requestArchiveAndDeleteWorkspace, isSidebarCollapsed, handleToggleSidebarCollapsed, ], @@ -411,6 +454,7 @@ const AgentsPage: FC = () => { modelConfigs={chatModelConfigsQuery.data ?? []} logoUrl={appearance.logo_url} onArchiveAgent={requestArchiveAgent} + onArchiveAndDeleteWorkspace={requestArchiveAndDeleteWorkspace} onNewAgent={handleNewAgent} isCreating={createMutation.isPending} isArchiving={archiveMutation.isPending} diff --git a/site/src/pages/AgentsPage/AgentsSidebar.tsx b/site/src/pages/AgentsPage/AgentsSidebar.tsx index 965337b0f5..0651428b59 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.tsx @@ -29,6 +29,7 @@ import { PanelLeftCloseIcon, PauseIcon, SearchIcon, + Trash2Icon, } from "lucide-react"; import { createContext, @@ -51,6 +52,7 @@ interface AgentsSidebarProps { modelConfigs: readonly ChatModelConfig[]; logoUrl?: string; onArchiveAgent: (chatId: string) => void; + onArchiveAndDeleteWorkspace: (chatId: string) => void; onNewAgent: () => void; isCreating: boolean; isArchiving?: boolean; @@ -264,6 +266,7 @@ interface ChatTreeContextValue { readonly archivingChatId: string | null; readonly toggleExpanded: (chatID: string) => void; readonly onArchiveAgent: (chatId: string) => void; + readonly onArchiveAndDeleteWorkspace: (chatId: string) => void; } const ChatTreeContext = createContext(null); @@ -295,6 +298,7 @@ const ChatTreeNode = memo(({ chat, isChildNode }) => { archivingChatId, toggleExpanded, onArchiveAgent, + onArchiveAndDeleteWorkspace, } = useChatTree(); const chatID = chat.id; const childIDs = (chatTree.childrenById.get(chatID) ?? []).filter((childID) => @@ -469,7 +473,17 @@ const ChatTreeNode = memo(({ chat, isChildNode }) => { Archive agent - + {chat.workspace_id && ( + onArchiveAndDeleteWorkspace(chat.id)} + > + + Archive & delete workspace + + )} + {" "} @@ -498,6 +512,7 @@ export const AgentsSidebar: FC = (props) => { modelConfigs, logoUrl, onArchiveAgent, + onArchiveAndDeleteWorkspace, onNewAgent, isCreating, isArchiving = false, @@ -576,6 +591,7 @@ export const AgentsSidebar: FC = (props) => { archivingChatId, toggleExpanded, onArchiveAgent, + onArchiveAndDeleteWorkspace, }), [ chatTree, @@ -590,6 +606,7 @@ export const AgentsSidebar: FC = (props) => { archivingChatId, toggleExpanded, onArchiveAgent, + onArchiveAndDeleteWorkspace, ], );