-
Notifications
You must be signed in to change notification settings - Fork 259
Expand file tree
/
Copy pathchatHistory.tsx
More file actions
175 lines (165 loc) · 7.55 KB
/
chatHistory.tsx
File metadata and controls
175 lines (165 loc) · 7.55 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
"use client";
import { ChatActionsDropdown } from "@/app/(app)/chat/components/chatActionsDropdown";
import { DeleteChatDialog } from "@/app/(app)/chat/components/deleteChatDialog";
import { DuplicateChatDialog } from "@/app/(app)/chat/components/duplicateChatDialog";
import { RenameChatDialog } from "@/app/(app)/chat/components/renameChatDialog";
import { useToast } from "@/components/hooks/use-toast";
import {
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar";
import { deleteChat, duplicateChat, updateChatName } from "@/features/chat/actions";
import { captureEvent } from "@/hooks/useCaptureEvent";
import { isServiceError } from "@/lib/utils";
import { EllipsisIcon, MessagesSquareIcon } from "lucide-react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import { useCallback, useState } from "react";
export interface ChatHistoryItem {
id: string;
name: string | null;
createdAt: Date;
}
interface ChatHistoryProps {
chatHistory: ChatHistoryItem[];
hasMore?: boolean;
}
export function ChatHistory({ chatHistory, hasMore }: ChatHistoryProps) {
const pathname = usePathname();
const router = useRouter();
const { toast } = useToast();
const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false);
const [chatIdToRename, setChatIdToRename] = useState<string | null>(null);
const [isDuplicateDialogOpen, setIsDuplicateDialogOpen] = useState(false);
const [chatIdToDuplicate, setChatIdToDuplicate] = useState<string | null>(null);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
const [chatIdToDelete, setChatIdToDelete] = useState<string | null>(null);
const onRenameChat = useCallback(async (name: string, chatId: string): Promise<boolean> => {
const response = await updateChatName({ chatId, name });
if (isServiceError(response)) {
toast({ description: `Failed to rename chat. Reason: ${response.message}` });
return false;
}
toast({ description: "Chat renamed successfully" });
captureEvent('wa_chat_renamed', { chatId });
router.refresh();
return true;
}, [router, toast]);
const onDeleteChat = useCallback(async (chatIdToDelete: string): Promise<boolean> => {
const response = await deleteChat({ chatId: chatIdToDelete });
if (isServiceError(response)) {
toast({ description: `Failed to delete chat. Reason: ${response.message}` });
return false;
}
toast({ description: "Chat deleted successfully" });
captureEvent('wa_chat_deleted', { chatId: chatIdToDelete });
if (pathname === `/chat/${chatIdToDelete}`) {
router.push("/chat");
} else {
router.refresh();
}
return true;
}, [pathname, router, toast]);
const onDuplicateChat = useCallback(async (newName: string, chatIdToDuplicate: string): Promise<string | null> => {
const response = await duplicateChat({ chatId: chatIdToDuplicate, newName });
if (isServiceError(response)) {
toast({ description: `Failed to duplicate chat. Reason: ${response.message}` });
return null;
}
toast({ description: "Chat duplicated successfully" });
captureEvent('wa_chat_duplicated', { chatId: chatIdToDuplicate });
router.push(`/chat/${response.id}`);
return response.id;
}, [router, toast]);
if (chatHistory.length === 0) {
return null;
}
return (
<>
<SidebarGroup className="group-data-[state=collapsed]:hidden">
<SidebarGroupLabel className="text-muted-foreground whitespace-nowrap">Recent Chats</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{chatHistory.map((chat) => (
<SidebarMenuItem key={chat.id} className="group/chat">
<SidebarMenuButton
asChild
isActive={pathname === `/chat/${chat.id}`}
>
<Link href={`/chat/${chat.id}`}>
<span>{chat.name ?? "Untitled chat"}</span>
</Link>
</SidebarMenuButton>
<ChatActionsDropdown
onRenameClick={() => {
setChatIdToRename(chat.id);
setIsRenameDialogOpen(true);
}}
onDuplicateClick={() => {
setChatIdToDuplicate(chat.id);
setIsDuplicateDialogOpen(true);
}}
onDeleteClick={() => {
setChatIdToDelete(chat.id);
setIsDeleteDialogOpen(true);
}}
>
<SidebarMenuAction showOnHover className="transition-opacity">
<EllipsisIcon className="w-4 h-4" />
</SidebarMenuAction>
</ChatActionsDropdown>
</SidebarMenuItem>
))}
{hasMore && (
<SidebarMenuItem>
<SidebarMenuButton asChild>
<Link href="/chats">
<MessagesSquareIcon className="h-4 w-4" />
<span>All chats</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
)}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
<RenameChatDialog
isOpen={isRenameDialogOpen}
onOpenChange={setIsRenameDialogOpen}
onRename={async (name) => {
if (chatIdToRename) {
return await onRenameChat(name, chatIdToRename);
}
return false;
}}
currentName={chatHistory.find((chat) => chat.id === chatIdToRename)?.name ?? "Untitled chat"}
/>
<DeleteChatDialog
isOpen={isDeleteDialogOpen}
onOpenChange={setIsDeleteDialogOpen}
onDelete={async () => {
if (chatIdToDelete) {
return await onDeleteChat(chatIdToDelete);
}
return false;
}}
/>
<DuplicateChatDialog
isOpen={isDuplicateDialogOpen}
onOpenChange={setIsDuplicateDialogOpen}
onDuplicate={async (newName) => {
if (chatIdToDuplicate) {
return await onDuplicateChat(newName, chatIdToDuplicate);
}
return null;
}}
currentName={chatHistory.find((chat) => chat.id === chatIdToDuplicate)?.name ?? "Untitled chat"}
/>
</>
);
}