diff --git a/autogpt_platform/backend/backend/api/features/chat/routes.py b/autogpt_platform/backend/backend/api/features/chat/routes.py
index aa565ca891..d838520c98 100644
--- a/autogpt_platform/backend/backend/api/features/chat/routes.py
+++ b/autogpt_platform/backend/backend/api/features/chat/routes.py
@@ -23,6 +23,7 @@ from .model import (
ChatSession,
append_and_save_message,
create_chat_session,
+ delete_chat_session,
get_chat_session,
get_user_sessions,
)
@@ -211,6 +212,43 @@ async def create_session(
)
+@router.delete(
+ "/sessions/{session_id}",
+ dependencies=[Security(auth.requires_user)],
+ status_code=204,
+ responses={404: {"description": "Session not found or access denied"}},
+)
+async def delete_session(
+ session_id: str,
+ user_id: Annotated[str, Security(auth.get_user_id)],
+) -> Response:
+ """
+ Delete a chat session.
+
+ Permanently removes a chat session and all its messages.
+ Only the owner can delete their sessions.
+
+ Args:
+ session_id: The session ID to delete.
+ user_id: The authenticated user's ID.
+
+ Returns:
+ 204 No Content on success.
+
+ Raises:
+ HTTPException: 404 if session not found or not owned by user.
+ """
+ deleted = await delete_chat_session(session_id, user_id)
+
+ if not deleted:
+ raise HTTPException(
+ status_code=404,
+ detail=f"Session {session_id} not found or access denied",
+ )
+
+ return Response(status_code=204)
+
+
@router.get(
"/sessions/{session_id}",
)
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
index 0d403b1a79..35b34890ce 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx
@@ -1,6 +1,8 @@
"use client";
import { SidebarProvider } from "@/components/ui/sidebar";
+// TODO: Replace with modern Dialog component when available
+import DeleteConfirmDialog from "@/components/__legacy__/delete-confirm-dialog";
import { ChatContainer } from "./components/ChatContainer/ChatContainer";
import { ChatSidebar } from "./components/ChatSidebar/ChatSidebar";
import { MobileDrawer } from "./components/MobileDrawer/MobileDrawer";
@@ -31,6 +33,12 @@ export function CopilotPage() {
handleDrawerOpenChange,
handleSelectSession,
handleNewChat,
+ // Delete functionality
+ sessionToDelete,
+ isDeleting,
+ handleDeleteClick,
+ handleConfirmDelete,
+ handleCancelDelete,
} = useCopilotPage();
if (isUserLoading || !isLoggedIn) {
@@ -48,7 +56,19 @@ export function CopilotPage() {
>
{!isMobile && }
- {isMobile &&
}
+ {isMobile && (
+
{
+ const session = sessions.find((s) => s.id === sessionId);
+ if (session) {
+ handleDeleteClick(session.id, session.title);
+ }
+ }}
+ />
+ )}
)}
+ {/* Delete confirmation dialog - rendered at top level for proper z-index on mobile */}
+ {isMobile && (
+
!open && handleCancelDelete()}
+ onDoDelete={handleConfirmDelete}
+ />
+ )}
);
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx
index 6b7398b4ba..8e785dd9d3 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/ChatSidebar/ChatSidebar.tsx
@@ -1,8 +1,15 @@
"use client";
-import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
+import {
+ getGetV2ListSessionsQueryKey,
+ useDeleteV2DeleteSession,
+ useGetV2ListSessions,
+} from "@/app/api/__generated__/endpoints/chat/chat";
import { Button } from "@/components/atoms/Button/Button";
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
import { Text } from "@/components/atoms/Text/Text";
+import { toast } from "@/components/molecules/Toast/use-toast";
+// TODO: Replace with modern Dialog component when available
+import DeleteConfirmDialog from "@/components/__legacy__/delete-confirm-dialog";
import {
Sidebar,
SidebarContent,
@@ -12,18 +19,52 @@ import {
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
-import { PlusCircleIcon, PlusIcon } from "@phosphor-icons/react";
+import { PlusCircleIcon, PlusIcon, TrashIcon } from "@phosphor-icons/react";
+import { useQueryClient } from "@tanstack/react-query";
import { motion } from "framer-motion";
+import { useState } from "react";
import { parseAsString, useQueryState } from "nuqs";
export function ChatSidebar() {
const { state } = useSidebar();
const isCollapsed = state === "collapsed";
const [sessionId, setSessionId] = useQueryState("sessionId", parseAsString);
+ const [sessionToDelete, setSessionToDelete] = useState<{
+ id: string;
+ title: string | null | undefined;
+ } | null>(null);
+
+ const queryClient = useQueryClient();
const { data: sessionsResponse, isLoading: isLoadingSessions } =
useGetV2ListSessions({ limit: 50 });
+ const { mutate: deleteSession, isPending: isDeleting } =
+ useDeleteV2DeleteSession({
+ mutation: {
+ onSuccess: () => {
+ // Invalidate sessions list to refetch
+ queryClient.invalidateQueries({
+ queryKey: getGetV2ListSessionsQueryKey(),
+ });
+ // If we deleted the current session, clear selection
+ if (sessionToDelete?.id === sessionId) {
+ setSessionId(null);
+ }
+ setSessionToDelete(null);
+ },
+ onError: (error) => {
+ toast({
+ title: "Failed to delete chat",
+ description:
+ error instanceof Error ? error.message : "An error occurred",
+ variant: "destructive",
+ });
+ setSessionToDelete(null);
+ },
+ },
+ });
+
const sessions =
sessionsResponse?.status === 200 ? sessionsResponse.data.sessions : [];
@@ -35,6 +76,22 @@ export function ChatSidebar() {
setSessionId(id);
}
+ function handleDeleteClick(
+ e: React.MouseEvent,
+ id: string,
+ title: string | null | undefined,
+ ) {
+ e.stopPropagation(); // Prevent session selection
+ if (isDeleting) return; // Prevent double-click during deletion
+ setSessionToDelete({ id, title });
+ }
+
+ function handleConfirmDelete() {
+ if (sessionToDelete) {
+ deleteSession({ sessionId: sessionToDelete.id });
+ }
+ }
+
function formatDate(dateString: string) {
const date = new Date(dateString);
const now = new Date();
@@ -61,128 +118,152 @@ export function ChatSidebar() {
}
return (
-
- {isCollapsed && (
-
-
-
-
-
- )}
-
- {!isCollapsed && (
-
-
- Your chats
-
-
-
-
-
- )}
-
- {!isCollapsed && (
-
- {isLoadingSessions ? (
-
-
-
- ) : sessions.length === 0 ? (
-
- No conversations yet
-
- ) : (
- sessions.map((session) => (
-
- ))
+ <>
+
+ {isCollapsed && (
+
- )}
-
- {!isCollapsed && sessionId && (
-
-
- }
+
- New Chat
-
-
-
- )}
-
+
+
+
+ )}
+
+ {!isCollapsed && (
+
+
+ Your chats
+
+
+
+
+
+ )}
+
+ {!isCollapsed && (
+
+ {isLoadingSessions ? (
+
+
+
+ ) : sessions.length === 0 ? (
+
+ No conversations yet
+
+ ) : (
+ sessions.map((session) => (
+
+
+
+
+ ))
+ )}
+
+ )}
+
+ {!isCollapsed && sessionId && (
+
+
+ }
+ >
+ New Chat
+
+
+
+ )}
+
+
+ !open && setSessionToDelete(null)}
+ onDoDelete={handleConfirmDelete}
+ />
+ >
);
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx
index e0d6161744..b4b7636c81 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/MobileHeader/MobileHeader.tsx
@@ -1,22 +1,46 @@
import { Button } from "@/components/atoms/Button/Button";
import { NAVBAR_HEIGHT_PX } from "@/lib/constants";
-import { ListIcon } from "@phosphor-icons/react";
+import { ListIcon, TrashIcon } from "@phosphor-icons/react";
interface Props {
onOpenDrawer: () => void;
+ showDelete?: boolean;
+ isDeleting?: boolean;
+ onDelete?: () => void;
}
-export function MobileHeader({ onOpenDrawer }: Props) {
+export function MobileHeader({
+ onOpenDrawer,
+ showDelete,
+ isDeleting,
+ onDelete,
+}: Props) {
return (
-
+
+ {showDelete && onDelete && (
+
+ )}
+
);
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
index 28e9ba7cfb..444e745ec6 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
@@ -1,10 +1,15 @@
-import { useGetV2ListSessions } from "@/app/api/__generated__/endpoints/chat/chat";
+import {
+ getGetV2ListSessionsQueryKey,
+ useDeleteV2DeleteSession,
+ useGetV2ListSessions,
+} from "@/app/api/__generated__/endpoints/chat/chat";
import { toast } from "@/components/molecules/Toast/use-toast";
import { useBreakpoint } from "@/lib/hooks/useBreakpoint";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useChat } from "@ai-sdk/react";
+import { useQueryClient } from "@tanstack/react-query";
import { DefaultChatTransport } from "ai";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useChatSession } from "./useChatSession";
import { useLongRunningToolPolling } from "./hooks/useLongRunningToolPolling";
@@ -14,6 +19,11 @@ export function useCopilotPage() {
const { isUserLoading, isLoggedIn } = useSupabase();
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [pendingMessage, setPendingMessage] = useState(null);
+ const [sessionToDelete, setSessionToDelete] = useState<{
+ id: string;
+ title: string | null | undefined;
+ } | null>(null);
+ const queryClient = useQueryClient();
const {
sessionId,
@@ -24,6 +34,30 @@ export function useCopilotPage() {
isCreatingSession,
} = useChatSession();
+ const { mutate: deleteSessionMutation, isPending: isDeleting } =
+ useDeleteV2DeleteSession({
+ mutation: {
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: getGetV2ListSessionsQueryKey(),
+ });
+ if (sessionToDelete?.id === sessionId) {
+ setSessionId(null);
+ }
+ setSessionToDelete(null);
+ },
+ onError: (error) => {
+ toast({
+ title: "Failed to delete chat",
+ description:
+ error instanceof Error ? error.message : "An error occurred",
+ variant: "destructive",
+ });
+ setSessionToDelete(null);
+ },
+ },
+ });
+
const breakpoint = useBreakpoint();
const isMobile =
breakpoint === "base" || breakpoint === "sm" || breakpoint === "md";
@@ -143,6 +177,24 @@ export function useCopilotPage() {
if (isMobile) setIsDrawerOpen(false);
}
+ const handleDeleteClick = useCallback(
+ (id: string, title: string | null | undefined) => {
+ if (isDeleting) return;
+ setSessionToDelete({ id, title });
+ },
+ [isDeleting],
+ );
+
+ const handleConfirmDelete = useCallback(() => {
+ if (sessionToDelete) {
+ deleteSessionMutation({ sessionId: sessionToDelete.id });
+ }
+ }, [sessionToDelete, deleteSessionMutation]);
+
+ const handleCancelDelete = useCallback(() => {
+ setSessionToDelete(null);
+ }, []);
+
return {
sessionId,
messages,
@@ -165,5 +217,11 @@ export function useCopilotPage() {
handleDrawerOpenChange,
handleSelectSession,
handleNewChat,
+ // Delete functionality
+ sessionToDelete,
+ isDeleting,
+ handleDeleteClick,
+ handleConfirmDelete,
+ handleCancelDelete,
};
}
diff --git a/autogpt_platform/frontend/src/app/api/openapi.json b/autogpt_platform/frontend/src/app/api/openapi.json
index 63a8a856b9..feabc9b51d 100644
--- a/autogpt_platform/frontend/src/app/api/openapi.json
+++ b/autogpt_platform/frontend/src/app/api/openapi.json
@@ -1151,6 +1151,36 @@
}
},
"/api/chat/sessions/{session_id}": {
+ "delete": {
+ "tags": ["v2", "chat", "chat"],
+ "summary": "Delete Session",
+ "description": "Delete a chat session.\n\nPermanently removes a chat session and all its messages.\nOnly the owner can delete their sessions.\n\nArgs:\n session_id: The session ID to delete.\n user_id: The authenticated user's ID.\n\nReturns:\n 204 No Content on success.\n\nRaises:\n HTTPException: 404 if session not found or not owned by user.",
+ "operationId": "deleteV2DeleteSession",
+ "security": [{ "HTTPBearerJWT": [] }],
+ "parameters": [
+ {
+ "name": "session_id",
+ "in": "path",
+ "required": true,
+ "schema": { "type": "string", "title": "Session Id" }
+ }
+ ],
+ "responses": {
+ "204": { "description": "Successful Response" },
+ "401": {
+ "$ref": "#/components/responses/HTTP401NotAuthenticatedError"
+ },
+ "404": { "description": "Session not found or access denied" },
+ "422": {
+ "description": "Validation Error",
+ "content": {
+ "application/json": {
+ "schema": { "$ref": "#/components/schemas/HTTPValidationError" }
+ }
+ }
+ }
+ }
+ },
"get": {
"tags": ["v2", "chat", "chat"],
"summary": "Get Session",
diff --git a/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx b/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx
index 4ce998b6f6..10af4fa3c8 100644
--- a/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx
+++ b/autogpt_platform/frontend/src/components/__legacy__/ui/dialog.tsx
@@ -115,7 +115,7 @@ const DialogFooter = ({
}: React.HTMLAttributes) => (