Delete chat (#77)

This commit is contained in:
Will Chen
2025-05-02 15:43:40 -07:00
committed by GitHub
parent b9dc2cc0f9
commit 4e4bf51bba
4 changed files with 103 additions and 25 deletions

View File

@@ -2,12 +2,12 @@ import { useEffect } from "react";
import { useNavigate, useRouterState } from "@tanstack/react-router"; import { useNavigate, useRouterState } from "@tanstack/react-router";
import type { ChatSummary } from "@/lib/schemas"; import type { ChatSummary } from "@/lib/schemas";
import { formatDistanceToNow } from "date-fns"; import { formatDistanceToNow } from "date-fns";
import { PlusCircle } from "lucide-react"; import { PlusCircle, MoreVertical, Trash2 } from "lucide-react";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { selectedChatIdAtom } from "@/atoms/chatAtoms"; import { selectedChatIdAtom } from "@/atoms/chatAtoms";
import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { selectedAppIdAtom } from "@/atoms/appAtoms";
import { IpcClient } from "@/ipc/ipc_client"; import { IpcClient } from "@/ipc/ipc_client";
import { showError } from "@/lib/toast"; import { showError, showSuccess } from "@/lib/toast";
import { import {
SidebarGroup, SidebarGroup,
SidebarGroupContent, SidebarGroupContent,
@@ -16,6 +16,12 @@ import {
SidebarMenuItem, SidebarMenuItem,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useChats } from "@/hooks/useChats"; import { useChats } from "@/hooks/useChats";
export function ChatList({ show }: { show?: boolean }) { export function ChatList({ show }: { show?: boolean }) {
@@ -82,6 +88,28 @@ export function ChatList({ show }: { show?: boolean }) {
} }
}; };
const handleDeleteChat = async (chatId: number) => {
try {
const result = await IpcClient.getInstance().deleteChat(chatId);
if (!result.success) {
showError("Failed to delete chat");
return;
}
showSuccess("Chat deleted successfully");
// If the deleted chat was selected, navigate to home
if (selectedChatId === chatId) {
setSelectedChatId(null);
navigate({ to: "/chat" });
}
// Refresh the chat list
await refreshChats();
} catch (error) {
showError(`Failed to delete chat: ${(error as any).toString()}`);
}
};
return ( return (
<SidebarGroup className="overflow-y-auto h-[calc(100vh-112px)]"> <SidebarGroup className="overflow-y-auto h-[calc(100vh-112px)]">
<SidebarGroupLabel>Recent Chats</SidebarGroupLabel> <SidebarGroupLabel>Recent Chats</SidebarGroupLabel>
@@ -108,28 +136,54 @@ export function ChatList({ show }: { show?: boolean }) {
<SidebarMenu className="space-y-1"> <SidebarMenu className="space-y-1">
{chats.map((chat) => ( {chats.map((chat) => (
<SidebarMenuItem key={chat.id} className="mb-1"> <SidebarMenuItem key={chat.id} className="mb-1">
<Button <div className="flex w-[185px] items-center">
variant="ghost" <Button
onClick={() => variant="ghost"
handleChatClick({ chatId: chat.id, appId: chat.appId }) onClick={() =>
} handleChatClick({ chatId: chat.id, appId: chat.appId })
className={`justify-start w-full text-left py-3 hover:bg-sidebar-accent/80 ${ }
selectedChatId === chat.id className={`justify-start w-full text-left py-3 pr-1 hover:bg-sidebar-accent/80 ${
? "bg-sidebar-accent text-sidebar-accent-foreground" selectedChatId === chat.id
: "" ? "bg-sidebar-accent text-sidebar-accent-foreground"
}`} : ""
> }`}
<div className="flex flex-col w-full"> >
<span className="truncate"> <div className="flex flex-col w-full">
{chat.title || "New Chat"} <span className="truncate">
</span> {chat.title || "New Chat"}
<span className="text-xs text-gray-500"> </span>
{formatDistanceToNow(new Date(chat.createdAt), { <span className="text-xs text-gray-500">
addSuffix: true, {formatDistanceToNow(new Date(chat.createdAt), {
})} addSuffix: true,
</span> })}
</div> </span>
</Button> </div>
</Button>
{selectedChatId === chat.id && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="ml-1 w-4"
onClick={(e) => e.stopPropagation()}
>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
variant="destructive"
onClick={() => handleDeleteChat(chat.id)}
>
<Trash2 className="mr-2 h-4 w-4" />
<span>Delete Chat</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</SidebarMenuItem> </SidebarMenuItem>
))} ))}
</SidebarMenu> </SidebarMenu>

View File

@@ -63,4 +63,15 @@ export function registerChatHandlers() {
return allChats; return allChats;
} }
); );
ipcMain.handle("delete-chat", async (_, chatId: number) => {
try {
// Delete the chat and its associated messages
await db.delete(chats).where(eq(chats.id, chatId));
return { success: true };
} catch (error) {
console.error("Error deleting chat:", error);
return { success: false, error: (error as Error).message };
}
});
} }

View File

@@ -277,7 +277,19 @@ export class IpcClient {
public async createChat(appId: number): Promise<number> { public async createChat(appId: number): Promise<number> {
try { try {
const chatId = await this.ipcRenderer.invoke("create-chat", appId); const chatId = await this.ipcRenderer.invoke("create-chat", appId);
return chatId; return chatId as number;
} catch (error) {
showError(error);
throw error;
}
}
public async deleteChat(chatId: number): Promise<{ success: boolean }> {
try {
const result = (await this.ipcRenderer.invoke("delete-chat", chatId)) as {
success: boolean;
};
return result;
} catch (error) { } catch (error) {
showError(error); showError(error);
throw error; throw error;

View File

@@ -56,6 +56,7 @@ const validInvokeChannels = [
"window:close", "window:close",
"window:get-platform", "window:get-platform",
"upload-to-signed-url", "upload-to-signed-url",
"delete-chat",
] as const; ] as const;
// Add valid receive channels // Add valid receive channels