feat: add delete all chats button to sidebar (#1267)

This commit is contained in:
josh 2025-10-09 18:51:17 +01:00 committed by GitHub
parent 9987b25964
commit ab402620df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 160 additions and 45 deletions

View file

@ -1,6 +1,6 @@
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { auth } from "@/app/(auth)/auth"; import { auth } from "@/app/(auth)/auth";
import { getChatsByUserId } from "@/lib/db/queries"; import { getChatsByUserId, deleteAllChatsByUserId } from "@/lib/db/queries";
import { ChatSDKError } from "@/lib/errors"; import { ChatSDKError } from "@/lib/errors";
export async function GET(request: NextRequest) { export async function GET(request: NextRequest) {
@ -32,3 +32,15 @@ export async function GET(request: NextRequest) {
return Response.json(chats); return Response.json(chats);
} }
export async function DELETE() {
const session = await auth();
if (!session?.user) {
return new ChatSDKError("unauthorized:chat").toResponse();
}
const result = await deleteAllChatsByUserId({ userId: session.user.id });
return Response.json(result, { status: 200 });
}

View file

@ -3,8 +3,12 @@
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import type { User } from "next-auth"; import type { User } from "next-auth";
import { PlusIcon } from "@/components/icons"; import { useState } from "react";
import { SidebarHistory } from "@/components/sidebar-history"; import { toast } from "sonner";
import { useSWRConfig } from "swr";
import { unstable_serialize } from "swr/infinite";
import { PlusIcon, TrashIcon } from "@/components/icons";
import { SidebarHistory, getChatHistoryPaginationKey } from "@/components/sidebar-history";
import { SidebarUserNav } from "@/components/sidebar-user-nav"; import { SidebarUserNav } from "@/components/sidebar-user-nav";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
@ -16,53 +20,121 @@ import {
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "./ui/alert-dialog";
export function AppSidebar({ user }: { user: User | undefined }) { export function AppSidebar({ user }: { user: User | undefined }) {
const router = useRouter(); const router = useRouter();
const { setOpenMobile } = useSidebar(); const { setOpenMobile } = useSidebar();
const { mutate } = useSWRConfig();
const [showDeleteAllDialog, setShowDeleteAllDialog] = useState(false);
const handleDeleteAll = () => {
const deletePromise = fetch("/api/history", {
method: "DELETE",
});
toast.promise(deletePromise, {
loading: "Deleting all chats...",
success: () => {
mutate(unstable_serialize(getChatHistoryPaginationKey));
router.push("/");
setShowDeleteAllDialog(false);
return "All chats deleted successfully";
},
error: "Failed to delete all chats",
});
};
return ( return (
<Sidebar className="group-data-[side=left]:border-r-0"> <>
<SidebarHeader> <Sidebar className="group-data-[side=left]:border-r-0">
<SidebarMenu> <SidebarHeader>
<div className="flex flex-row items-center justify-between"> <SidebarMenu>
<Link <div className="flex flex-row items-center justify-between">
className="flex flex-row items-center gap-3" <Link
href="/" className="flex flex-row items-center gap-3"
onClick={() => { href="/"
setOpenMobile(false); onClick={() => {
}} setOpenMobile(false);
> }}
<span className="cursor-pointer rounded-md px-2 font-semibold text-lg hover:bg-muted"> >
Chatbot <span className="cursor-pointer rounded-md px-2 font-semibold text-lg hover:bg-muted">
</span> Chatbot
</Link> </span>
<Tooltip> </Link>
<TooltipTrigger asChild> <div className="flex flex-row gap-1">
<Button {user && (
className="h-8 p-1 md:h-fit md:p-2" <Tooltip>
onClick={() => { <TooltipTrigger asChild>
setOpenMobile(false); <Button
router.push("/"); className="h-8 p-1 md:h-fit md:p-2"
router.refresh(); onClick={() => setShowDeleteAllDialog(true)}
}} type="button"
type="button" variant="ghost"
variant="ghost" >
> <TrashIcon />
<PlusIcon /> </Button>
</Button> </TooltipTrigger>
</TooltipTrigger> <TooltipContent align="end" className="hidden md:block">
<TooltipContent align="end" className="hidden md:block"> Delete All Chats
New Chat </TooltipContent>
</TooltipContent> </Tooltip>
</Tooltip> )}
</div> <Tooltip>
</SidebarMenu> <TooltipTrigger asChild>
</SidebarHeader> <Button
<SidebarContent> className="h-8 p-1 md:h-fit md:p-2"
<SidebarHistory user={user} /> onClick={() => {
</SidebarContent> setOpenMobile(false);
<SidebarFooter>{user && <SidebarUserNav user={user} />}</SidebarFooter> router.push("/");
</Sidebar> router.refresh();
}}
type="button"
variant="ghost"
>
<PlusIcon />
</Button>
</TooltipTrigger>
<TooltipContent align="end" className="hidden md:block">
New Chat
</TooltipContent>
</Tooltip>
</div>
</div>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarHistory user={user} />
</SidebarContent>
<SidebarFooter>{user && <SidebarUserNav user={user} />}</SidebarFooter>
</Sidebar>
<AlertDialog onOpenChange={setShowDeleteAllDialog} open={showDeleteAllDialog}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete all chats?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This will permanently delete all your
chats and remove them from our servers.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDeleteAll}>
Delete All
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
); );
} }

View file

@ -123,6 +123,37 @@ export async function deleteChatById({ id }: { id: string }) {
} }
} }
export async function deleteAllChatsByUserId({ userId }: { userId: string }) {
try {
const userChats = await db
.select({ id: chat.id })
.from(chat)
.where(eq(chat.userId, userId));
if (userChats.length === 0) {
return { deletedCount: 0 };
}
const chatIds = userChats.map(c => c.id);
await db.delete(vote).where(inArray(vote.chatId, chatIds));
await db.delete(message).where(inArray(message.chatId, chatIds));
await db.delete(stream).where(inArray(stream.chatId, chatIds));
const deletedChats = await db
.delete(chat)
.where(eq(chat.userId, userId))
.returning();
return { deletedCount: deletedChats.length };
} catch (_error) {
throw new ChatSDKError(
"bad_request:database",
"Failed to delete all chats by user id"
);
}
}
export async function getChatsByUserId({ export async function getChatsByUserId({
id, id,
limit, limit,