Skip to content

Commit 4e4bf51

Browse files
authored
Delete chat (dyad-sh#77)
1 parent b9dc2cc commit 4e4bf51

4 files changed

Lines changed: 103 additions & 25 deletions

File tree

src/components/ChatList.tsx

Lines changed: 78 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import { useEffect } from "react";
22
import { useNavigate, useRouterState } from "@tanstack/react-router";
33
import type { ChatSummary } from "@/lib/schemas";
44
import { formatDistanceToNow } from "date-fns";
5-
import { PlusCircle } from "lucide-react";
5+
import { PlusCircle, MoreVertical, Trash2 } from "lucide-react";
66
import { useAtom } from "jotai";
77
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
88
import { selectedAppIdAtom } from "@/atoms/appAtoms";
99
import { IpcClient } from "@/ipc/ipc_client";
10-
import { showError } from "@/lib/toast";
10+
import { showError, showSuccess } from "@/lib/toast";
1111
import {
1212
SidebarGroup,
1313
SidebarGroupContent,
@@ -16,6 +16,12 @@ import {
1616
SidebarMenuItem,
1717
} from "@/components/ui/sidebar";
1818
import { Button } from "@/components/ui/button";
19+
import {
20+
DropdownMenu,
21+
DropdownMenuContent,
22+
DropdownMenuItem,
23+
DropdownMenuTrigger,
24+
} from "@/components/ui/dropdown-menu";
1925
import { useChats } from "@/hooks/useChats";
2026

2127
export function ChatList({ show }: { show?: boolean }) {
@@ -82,6 +88,28 @@ export function ChatList({ show }: { show?: boolean }) {
8288
}
8389
};
8490

91+
const handleDeleteChat = async (chatId: number) => {
92+
try {
93+
const result = await IpcClient.getInstance().deleteChat(chatId);
94+
if (!result.success) {
95+
showError("Failed to delete chat");
96+
return;
97+
}
98+
showSuccess("Chat deleted successfully");
99+
100+
// If the deleted chat was selected, navigate to home
101+
if (selectedChatId === chatId) {
102+
setSelectedChatId(null);
103+
navigate({ to: "/chat" });
104+
}
105+
106+
// Refresh the chat list
107+
await refreshChats();
108+
} catch (error) {
109+
showError(`Failed to delete chat: ${(error as any).toString()}`);
110+
}
111+
};
112+
85113
return (
86114
<SidebarGroup className="overflow-y-auto h-[calc(100vh-112px)]">
87115
<SidebarGroupLabel>Recent Chats</SidebarGroupLabel>
@@ -108,28 +136,54 @@ export function ChatList({ show }: { show?: boolean }) {
108136
<SidebarMenu className="space-y-1">
109137
{chats.map((chat) => (
110138
<SidebarMenuItem key={chat.id} className="mb-1">
111-
<Button
112-
variant="ghost"
113-
onClick={() =>
114-
handleChatClick({ chatId: chat.id, appId: chat.appId })
115-
}
116-
className={`justify-start w-full text-left py-3 hover:bg-sidebar-accent/80 ${
117-
selectedChatId === chat.id
118-
? "bg-sidebar-accent text-sidebar-accent-foreground"
119-
: ""
120-
}`}
121-
>
122-
<div className="flex flex-col w-full">
123-
<span className="truncate">
124-
{chat.title || "New Chat"}
125-
</span>
126-
<span className="text-xs text-gray-500">
127-
{formatDistanceToNow(new Date(chat.createdAt), {
128-
addSuffix: true,
129-
})}
130-
</span>
131-
</div>
132-
</Button>
139+
<div className="flex w-[185px] items-center">
140+
<Button
141+
variant="ghost"
142+
onClick={() =>
143+
handleChatClick({ chatId: chat.id, appId: chat.appId })
144+
}
145+
className={`justify-start w-full text-left py-3 pr-1 hover:bg-sidebar-accent/80 ${
146+
selectedChatId === chat.id
147+
? "bg-sidebar-accent text-sidebar-accent-foreground"
148+
: ""
149+
}`}
150+
>
151+
<div className="flex flex-col w-full">
152+
<span className="truncate">
153+
{chat.title || "New Chat"}
154+
</span>
155+
<span className="text-xs text-gray-500">
156+
{formatDistanceToNow(new Date(chat.createdAt), {
157+
addSuffix: true,
158+
})}
159+
</span>
160+
</div>
161+
</Button>
162+
163+
{selectedChatId === chat.id && (
164+
<DropdownMenu>
165+
<DropdownMenuTrigger asChild>
166+
<Button
167+
variant="ghost"
168+
size="icon"
169+
className="ml-1 w-4"
170+
onClick={(e) => e.stopPropagation()}
171+
>
172+
<MoreVertical className="h-4 w-4" />
173+
</Button>
174+
</DropdownMenuTrigger>
175+
<DropdownMenuContent align="end">
176+
<DropdownMenuItem
177+
variant="destructive"
178+
onClick={() => handleDeleteChat(chat.id)}
179+
>
180+
<Trash2 className="mr-2 h-4 w-4" />
181+
<span>Delete Chat</span>
182+
</DropdownMenuItem>
183+
</DropdownMenuContent>
184+
</DropdownMenu>
185+
)}
186+
</div>
133187
</SidebarMenuItem>
134188
))}
135189
</SidebarMenu>

src/ipc/handlers/chat_handlers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,15 @@ export function registerChatHandlers() {
6363
return allChats;
6464
}
6565
);
66+
67+
ipcMain.handle("delete-chat", async (_, chatId: number) => {
68+
try {
69+
// Delete the chat and its associated messages
70+
await db.delete(chats).where(eq(chats.id, chatId));
71+
return { success: true };
72+
} catch (error) {
73+
console.error("Error deleting chat:", error);
74+
return { success: false, error: (error as Error).message };
75+
}
76+
});
6677
}

src/ipc/ipc_client.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,19 @@ export class IpcClient {
277277
public async createChat(appId: number): Promise<number> {
278278
try {
279279
const chatId = await this.ipcRenderer.invoke("create-chat", appId);
280-
return chatId;
280+
return chatId as number;
281+
} catch (error) {
282+
showError(error);
283+
throw error;
284+
}
285+
}
286+
287+
public async deleteChat(chatId: number): Promise<{ success: boolean }> {
288+
try {
289+
const result = (await this.ipcRenderer.invoke("delete-chat", chatId)) as {
290+
success: boolean;
291+
};
292+
return result;
281293
} catch (error) {
282294
showError(error);
283295
throw error;

src/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const validInvokeChannels = [
5656
"window:close",
5757
"window:get-platform",
5858
"upload-to-signed-url",
59+
"delete-chat",
5960
] as const;
6061

6162
// Add valid receive channels

0 commit comments

Comments
 (0)