feat: add confirmation dialog when archiving chat with workspace (#22524)

When archiving a chat that has an attached workspace, a dialog now pops
up asking whether to also delete the associated workspace.

## Changes

### New file: `ArchiveAgentDialog.tsx`
A Radix-based dialog component that appears when archiving a chat that
has a `workspace_id`. It provides:
- A checkbox to opt into deleting the associated workspace
- **Cancel** — closes without archiving
- **Archive only** — archives the chat, leaves the workspace intact
- **Archive & Delete Workspace** — archives the chat and triggers
workspace deletion (enabled only when checkbox is checked)

### Modified: `AgentsPage.tsx`
- Extracted archive logic into a `performArchive` helper
- `requestArchiveAgent` now checks if the chat has a `workspace_id`:
  - If yes, opens the `ArchiveAgentDialog`
  - If no, proceeds with archiving directly (existing behavior)
- Added `handleArchiveOnly`, `handleArchiveAndDeleteWorkspace`, and
`handleCloseArchiveDialog` handlers
- Renders the `<ArchiveAgentDialog>` at the page level

Chats without a workspace are archived immediately as before — no UX
change for those.
This commit is contained in:
Kyle Carberry
2026-03-02 18:52:51 -05:00
committed by GitHub
parent ddfe630757
commit b8a74a4fcb
6 changed files with 100 additions and 5 deletions
@@ -43,6 +43,7 @@ const AgentDetailLayout: FC = () => {
setChatErrorReason: () => {},
clearChatErrorReason: () => {},
requestArchiveAgent: () => {},
requestArchiveAndDeleteWorkspace: () => {},
isSidebarCollapsed: false,
onToggleSidebarCollapsed: () => {},
} satisfies AgentsOutletContext
+18
View File
@@ -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<typeof useChatStore>["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 (
<div className="relative flex h-full min-h-0 min-w-0 flex-1 flex-col">
@@ -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}
@@ -28,6 +28,7 @@ const defaultProps = {
onViewWorkspace: () => {},
},
onArchiveAgent: () => {},
onArchiveAndDeleteWorkspace: () => {},
isSidebarCollapsed: false,
onToggleSidebarCollapsed: () => {},
} satisfies React.ComponentProps<typeof AgentDetailTopBar>;
@@ -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<AgentDetailTopBarProps> = ({
diff,
workspace,
onArchiveAgent,
onArchiveAndDeleteWorkspace,
hasWorkspace,
isArchived,
isSidebarCollapsed,
onToggleSidebarCollapsed,
@@ -208,7 +213,16 @@ export const AgentDetailTopBar: FC<AgentDetailTopBarProps> = ({
Archive Agent
</DropdownMenuItem>
)}
</DropdownMenuContent>
{!isArchived && hasWorkspace && (
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
onSelect={onArchiveAndDeleteWorkspace}
>
<Trash2Icon className="h-3.5 w-3.5" />
Archive & Delete Workspace
</DropdownMenuItem>
)}
</DropdownMenuContent>{" "}
</DropdownMenu>
<WebPushButton />
</div>
+47 -3
View File
@@ -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<string | null>(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}
+18 -1
View File
@@ -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<ChatTreeContextValue | null>(null);
@@ -295,6 +298,7 @@ const ChatTreeNode = memo<ChatTreeNodeProps>(({ 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<ChatTreeNodeProps>(({ chat, isChildNode }) => {
<ArchiveIcon className="h-3.5 w-3.5" />
Archive agent
</DropdownMenuItem>
</DropdownMenuContent>
{chat.workspace_id && (
<DropdownMenuItem
className="text-content-destructive focus:text-content-destructive"
disabled={isArchiving}
onSelect={() => onArchiveAndDeleteWorkspace(chat.id)}
>
<Trash2Icon className="h-3.5 w-3.5" />
Archive & delete workspace
</DropdownMenuItem>
)}
</DropdownMenuContent>{" "}
</DropdownMenu>
</div>
</div>
@@ -498,6 +512,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
modelConfigs,
logoUrl,
onArchiveAgent,
onArchiveAndDeleteWorkspace,
onNewAgent,
isCreating,
isArchiving = false,
@@ -576,6 +591,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
archivingChatId,
toggleExpanded,
onArchiveAgent,
onArchiveAndDeleteWorkspace,
}),
[
chatTree,
@@ -590,6 +606,7 @@ export const AgentsSidebar: FC<AgentsSidebarProps> = (props) => {
archivingChatId,
toggleExpanded,
onArchiveAgent,
onArchiveAndDeleteWorkspace,
],
);