thepopebot 1.2.27 → 1.2.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/chat/components/chats-page.js +29 -9
- package/lib/chat/components/chats-page.jsx +69 -49
- package/lib/chat/components/sidebar-history-item.js +123 -101
- package/lib/chat/components/sidebar-history-item.jsx +69 -49
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { PageLayout } from "./page-layout.js";
|
|
|
5
5
|
import { MessageIcon, TrashIcon, SearchIcon, PlusIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from "./icons.js";
|
|
6
6
|
import { getChats, deleteChat, renameChat, starChat } from "../actions.js";
|
|
7
7
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
|
|
8
|
+
import { ConfirmDialog } from "./ui/confirm-dialog.js";
|
|
8
9
|
import { cn } from "../utils.js";
|
|
9
10
|
function groupChatsByDate(chats) {
|
|
10
11
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -150,10 +151,13 @@ function ChatsPage({ session }) {
|
|
|
150
151
|
] });
|
|
151
152
|
}
|
|
152
153
|
function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
153
|
-
const [
|
|
154
|
+
const [hovered, setHovered] = useState(false);
|
|
155
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
156
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
154
157
|
const [editing, setEditing] = useState(false);
|
|
155
158
|
const [editTitle, setEditTitle] = useState(chat.title || "");
|
|
156
159
|
const inputRef = useRef(null);
|
|
160
|
+
const showMenu = hovered || dropdownOpen;
|
|
157
161
|
useEffect(() => {
|
|
158
162
|
if (editing && inputRef.current) {
|
|
159
163
|
inputRef.current.focus();
|
|
@@ -179,8 +183,8 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
179
183
|
"div",
|
|
180
184
|
{
|
|
181
185
|
className: "relative group flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-muted/50 rounded-md",
|
|
182
|
-
onMouseEnter: () =>
|
|
183
|
-
onMouseLeave: () =>
|
|
186
|
+
onMouseEnter: () => setHovered(true),
|
|
187
|
+
onMouseLeave: () => setHovered(false),
|
|
184
188
|
onClick: () => !editing && onNavigate(chat.id),
|
|
185
189
|
children: [
|
|
186
190
|
/* @__PURE__ */ jsx(MessageIcon, { size: 16 }),
|
|
@@ -216,15 +220,17 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
216
220
|
timeAgo(chat.updatedAt)
|
|
217
221
|
] })
|
|
218
222
|
] }),
|
|
219
|
-
|
|
220
|
-
|
|
223
|
+
!editing && /* @__PURE__ */ jsx("div", { className: cn(
|
|
224
|
+
"shrink-0",
|
|
225
|
+
showMenu ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
226
|
+
), children: /* @__PURE__ */ jsxs(DropdownMenu, { open: dropdownOpen, onOpenChange: setDropdownOpen, children: [
|
|
227
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { children: /* @__PURE__ */ jsx(
|
|
221
228
|
"button",
|
|
222
229
|
{
|
|
223
230
|
className: cn(
|
|
224
|
-
"
|
|
231
|
+
"rounded-md p-1.5",
|
|
225
232
|
"text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
226
233
|
),
|
|
227
|
-
onClick: (e) => e.stopPropagation(),
|
|
228
234
|
"aria-label": "Chat options",
|
|
229
235
|
children: /* @__PURE__ */ jsx(MoreHorizontalIcon, { size: 14 })
|
|
230
236
|
}
|
|
@@ -263,7 +269,7 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
263
269
|
className: "text-destructive hover:text-destructive",
|
|
264
270
|
onClick: (e) => {
|
|
265
271
|
e.stopPropagation();
|
|
266
|
-
|
|
272
|
+
setConfirmDelete(true);
|
|
267
273
|
},
|
|
268
274
|
children: [
|
|
269
275
|
/* @__PURE__ */ jsx(TrashIcon, { size: 14 }),
|
|
@@ -272,7 +278,21 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
272
278
|
}
|
|
273
279
|
)
|
|
274
280
|
] })
|
|
275
|
-
] })
|
|
281
|
+
] }) }),
|
|
282
|
+
/* @__PURE__ */ jsx(
|
|
283
|
+
ConfirmDialog,
|
|
284
|
+
{
|
|
285
|
+
open: confirmDelete,
|
|
286
|
+
title: "Delete chat?",
|
|
287
|
+
description: "This will permanently delete this chat and all its messages.",
|
|
288
|
+
confirmLabel: "Delete",
|
|
289
|
+
onConfirm: () => {
|
|
290
|
+
setConfirmDelete(false);
|
|
291
|
+
onDelete(chat.id);
|
|
292
|
+
},
|
|
293
|
+
onCancel: () => setConfirmDelete(false)
|
|
294
|
+
}
|
|
295
|
+
)
|
|
276
296
|
]
|
|
277
297
|
}
|
|
278
298
|
);
|
|
@@ -5,6 +5,7 @@ import { PageLayout } from './page-layout.js';
|
|
|
5
5
|
import { MessageIcon, TrashIcon, SearchIcon, PlusIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from './icons.js';
|
|
6
6
|
import { getChats, deleteChat, renameChat, starChat } from '../actions.js';
|
|
7
7
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './ui/dropdown-menu.js';
|
|
8
|
+
import { ConfirmDialog } from './ui/confirm-dialog.js';
|
|
8
9
|
import { cn } from '../utils.js';
|
|
9
10
|
|
|
10
11
|
function groupChatsByDate(chats) {
|
|
@@ -190,11 +191,15 @@ export function ChatsPage({ session }) {
|
|
|
190
191
|
}
|
|
191
192
|
|
|
192
193
|
function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
193
|
-
const [
|
|
194
|
+
const [hovered, setHovered] = useState(false);
|
|
195
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
196
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
194
197
|
const [editing, setEditing] = useState(false);
|
|
195
198
|
const [editTitle, setEditTitle] = useState(chat.title || '');
|
|
196
199
|
const inputRef = useRef(null);
|
|
197
200
|
|
|
201
|
+
const showMenu = hovered || dropdownOpen;
|
|
202
|
+
|
|
198
203
|
useEffect(() => {
|
|
199
204
|
if (editing && inputRef.current) {
|
|
200
205
|
inputRef.current.focus();
|
|
@@ -223,8 +228,8 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
223
228
|
return (
|
|
224
229
|
<div
|
|
225
230
|
className="relative group flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-muted/50 rounded-md"
|
|
226
|
-
onMouseEnter={() =>
|
|
227
|
-
onMouseLeave={() =>
|
|
231
|
+
onMouseEnter={() => setHovered(true)}
|
|
232
|
+
onMouseLeave={() => setHovered(false)}
|
|
228
233
|
onClick={() => !editing && onNavigate(chat.id)}
|
|
229
234
|
>
|
|
230
235
|
<MessageIcon size={16} />
|
|
@@ -258,53 +263,68 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
258
263
|
Last message {timeAgo(chat.updatedAt)}
|
|
259
264
|
</span>
|
|
260
265
|
</div>
|
|
261
|
-
{
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
266
|
+
{!editing && (
|
|
267
|
+
<div className={cn(
|
|
268
|
+
'shrink-0',
|
|
269
|
+
showMenu ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
270
|
+
)}>
|
|
271
|
+
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
|
272
|
+
<DropdownMenuTrigger>
|
|
273
|
+
<button
|
|
274
|
+
className={cn(
|
|
275
|
+
'rounded-md p-1.5',
|
|
276
|
+
'text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
277
|
+
)}
|
|
278
|
+
aria-label="Chat options"
|
|
279
|
+
>
|
|
280
|
+
<MoreHorizontalIcon size={14} />
|
|
281
|
+
</button>
|
|
282
|
+
</DropdownMenuTrigger>
|
|
283
|
+
<DropdownMenuContent align="end" side="bottom">
|
|
284
|
+
<DropdownMenuItem
|
|
285
|
+
onClick={(e) => {
|
|
286
|
+
e.stopPropagation();
|
|
287
|
+
onStar(chat.id);
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
{chat.starred ? <StarFilledIcon size={14} /> : <StarIcon size={14} />}
|
|
291
|
+
{chat.starred ? 'Unstar' : 'Star'}
|
|
292
|
+
</DropdownMenuItem>
|
|
293
|
+
<DropdownMenuItem
|
|
294
|
+
onClick={(e) => {
|
|
295
|
+
e.stopPropagation();
|
|
296
|
+
startRename();
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
<PencilIcon size={14} />
|
|
300
|
+
Rename
|
|
301
|
+
</DropdownMenuItem>
|
|
302
|
+
<DropdownMenuSeparator />
|
|
303
|
+
<DropdownMenuItem
|
|
304
|
+
className="text-destructive hover:text-destructive"
|
|
305
|
+
onClick={(e) => {
|
|
306
|
+
e.stopPropagation();
|
|
307
|
+
setConfirmDelete(true);
|
|
308
|
+
}}
|
|
309
|
+
>
|
|
310
|
+
<TrashIcon size={14} />
|
|
311
|
+
Delete
|
|
312
|
+
</DropdownMenuItem>
|
|
313
|
+
</DropdownMenuContent>
|
|
314
|
+
</DropdownMenu>
|
|
315
|
+
</div>
|
|
307
316
|
)}
|
|
317
|
+
<ConfirmDialog
|
|
318
|
+
open={confirmDelete}
|
|
319
|
+
title="Delete chat?"
|
|
320
|
+
description="This will permanently delete this chat and all its messages."
|
|
321
|
+
confirmLabel="Delete"
|
|
322
|
+
onConfirm={() => {
|
|
323
|
+
setConfirmDelete(false);
|
|
324
|
+
onDelete(chat.id);
|
|
325
|
+
}}
|
|
326
|
+
onCancel={() => setConfirmDelete(false)}
|
|
327
|
+
/>
|
|
308
328
|
</div>
|
|
309
329
|
);
|
|
310
330
|
}
|
|
@@ -4,15 +4,19 @@ import { useState, useEffect, useRef } from "react";
|
|
|
4
4
|
import { MessageIcon, TrashIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from "./icons.js";
|
|
5
5
|
import { SidebarMenuButton, SidebarMenuItem, useSidebar } from "./ui/sidebar.js";
|
|
6
6
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
|
|
7
|
+
import { ConfirmDialog } from "./ui/confirm-dialog.js";
|
|
7
8
|
import { useChatNav } from "./chat-nav-context.js";
|
|
8
9
|
import { cn } from "../utils.js";
|
|
9
10
|
function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
|
|
10
11
|
const { navigateToChat } = useChatNav();
|
|
11
12
|
const { setOpenMobile } = useSidebar();
|
|
12
|
-
const [
|
|
13
|
+
const [hovered, setHovered] = useState(false);
|
|
14
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
15
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
13
16
|
const [editing, setEditing] = useState(false);
|
|
14
17
|
const [editTitle, setEditTitle] = useState(chat.title || "");
|
|
15
18
|
const inputRef = useRef(null);
|
|
19
|
+
const showMenu = hovered || dropdownOpen;
|
|
16
20
|
useEffect(() => {
|
|
17
21
|
if (editing && inputRef.current) {
|
|
18
22
|
inputRef.current.focus();
|
|
@@ -34,115 +38,133 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
|
|
|
34
38
|
setEditing(false);
|
|
35
39
|
setEditTitle(chat.title || "");
|
|
36
40
|
};
|
|
37
|
-
return /* @__PURE__ */
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
/* @__PURE__ */
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
return /* @__PURE__ */ jsxs(SidebarMenuItem, { children: [
|
|
42
|
+
/* @__PURE__ */ jsxs(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
className: "relative group",
|
|
46
|
+
onMouseEnter: () => setHovered(true),
|
|
47
|
+
onMouseLeave: () => setHovered(false),
|
|
48
|
+
children: [
|
|
49
|
+
editing ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2 py-1", children: [
|
|
50
|
+
/* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
|
|
51
|
+
/* @__PURE__ */ jsx(
|
|
52
|
+
"input",
|
|
53
|
+
{
|
|
54
|
+
ref: inputRef,
|
|
55
|
+
type: "text",
|
|
56
|
+
value: editTitle,
|
|
57
|
+
onChange: (e) => setEditTitle(e.target.value),
|
|
58
|
+
onKeyDown: (e) => {
|
|
59
|
+
if (e.key === "Enter") saveRename();
|
|
60
|
+
if (e.key === "Escape") cancelRename();
|
|
61
|
+
},
|
|
62
|
+
onBlur: saveRename,
|
|
63
|
+
className: "flex-1 min-w-0 text-sm bg-background border border-input rounded px-1.5 py-0.5 focus:outline-none focus:ring-2 focus:ring-ring"
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
67
|
+
SidebarMenuButton,
|
|
48
68
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
onKeyDown: (e) => {
|
|
54
|
-
if (e.key === "Enter") saveRename();
|
|
55
|
-
if (e.key === "Escape") cancelRename();
|
|
69
|
+
isActive,
|
|
70
|
+
onClick: () => {
|
|
71
|
+
navigateToChat(chat.id);
|
|
72
|
+
setOpenMobile(false);
|
|
56
73
|
},
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
children: [
|
|
75
|
+
/* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
"span",
|
|
78
|
+
{
|
|
79
|
+
className: "truncate flex-1",
|
|
80
|
+
onDoubleClick: (e) => {
|
|
81
|
+
e.stopPropagation();
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
startRename();
|
|
84
|
+
},
|
|
85
|
+
children: chat.title
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
]
|
|
59
89
|
}
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
90
|
+
),
|
|
91
|
+
!editing && /* @__PURE__ */ jsx("div", { className: cn(
|
|
92
|
+
"absolute right-1 top-1/2 -translate-y-1/2 z-10",
|
|
93
|
+
showMenu ? "opacity-100" : "opacity-0 pointer-events-none"
|
|
94
|
+
), children: /* @__PURE__ */ jsxs(DropdownMenu, { open: dropdownOpen, onOpenChange: setDropdownOpen, children: [
|
|
95
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { children: /* @__PURE__ */ jsx(
|
|
96
|
+
"button",
|
|
97
|
+
{
|
|
98
|
+
className: cn(
|
|
99
|
+
"rounded-md p-1",
|
|
100
|
+
"text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
101
|
+
),
|
|
102
|
+
"aria-label": "Chat options",
|
|
103
|
+
children: /* @__PURE__ */ jsx(MoreHorizontalIcon, { size: 14 })
|
|
104
|
+
}
|
|
105
|
+
) }),
|
|
106
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", side: "bottom", children: [
|
|
107
|
+
/* @__PURE__ */ jsxs(
|
|
108
|
+
DropdownMenuItem,
|
|
73
109
|
{
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
onClick: (e) => {
|
|
111
|
+
e.stopPropagation();
|
|
112
|
+
onStar(chat.id);
|
|
113
|
+
},
|
|
114
|
+
children: [
|
|
115
|
+
chat.starred ? /* @__PURE__ */ jsx(StarFilledIcon, { size: 14 }) : /* @__PURE__ */ jsx(StarIcon, { size: 14 }),
|
|
116
|
+
chat.starred ? "Unstar" : "Star"
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
/* @__PURE__ */ jsxs(
|
|
121
|
+
DropdownMenuItem,
|
|
122
|
+
{
|
|
123
|
+
onClick: (e) => {
|
|
76
124
|
e.stopPropagation();
|
|
77
|
-
e.preventDefault();
|
|
78
125
|
startRename();
|
|
79
126
|
},
|
|
80
|
-
children:
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ jsx(PencilIcon, { size: 14 }),
|
|
129
|
+
"Rename"
|
|
130
|
+
]
|
|
81
131
|
}
|
|
82
|
-
)
|
|
83
|
-
]
|
|
84
|
-
}
|
|
85
|
-
),
|
|
86
|
-
showMenu && !editing && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
87
|
-
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
88
|
-
"button",
|
|
89
|
-
{
|
|
90
|
-
className: cn(
|
|
91
|
-
"absolute right-1 top-1/2 -translate-y-1/2 rounded-md p-1",
|
|
92
|
-
"text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
93
132
|
),
|
|
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
|
-
className: "text-destructive hover:text-destructive",
|
|
131
|
-
onClick: (e) => {
|
|
132
|
-
e.stopPropagation();
|
|
133
|
-
onDelete(chat.id);
|
|
134
|
-
},
|
|
135
|
-
children: [
|
|
136
|
-
/* @__PURE__ */ jsx(TrashIcon, { size: 14 }),
|
|
137
|
-
"Delete"
|
|
138
|
-
]
|
|
139
|
-
}
|
|
140
|
-
)
|
|
141
|
-
] })
|
|
142
|
-
] })
|
|
143
|
-
]
|
|
144
|
-
}
|
|
145
|
-
) });
|
|
133
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
134
|
+
/* @__PURE__ */ jsxs(
|
|
135
|
+
DropdownMenuItem,
|
|
136
|
+
{
|
|
137
|
+
className: "text-destructive hover:text-destructive",
|
|
138
|
+
onClick: (e) => {
|
|
139
|
+
e.stopPropagation();
|
|
140
|
+
setConfirmDelete(true);
|
|
141
|
+
},
|
|
142
|
+
children: [
|
|
143
|
+
/* @__PURE__ */ jsx(TrashIcon, { size: 14 }),
|
|
144
|
+
"Delete"
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
] })
|
|
149
|
+
] }) })
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
),
|
|
153
|
+
/* @__PURE__ */ jsx(
|
|
154
|
+
ConfirmDialog,
|
|
155
|
+
{
|
|
156
|
+
open: confirmDelete,
|
|
157
|
+
title: "Delete chat?",
|
|
158
|
+
description: "This will permanently delete this chat and all its messages.",
|
|
159
|
+
confirmLabel: "Delete",
|
|
160
|
+
onConfirm: () => {
|
|
161
|
+
setConfirmDelete(false);
|
|
162
|
+
onDelete(chat.id);
|
|
163
|
+
},
|
|
164
|
+
onCancel: () => setConfirmDelete(false)
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
] });
|
|
146
168
|
}
|
|
147
169
|
export {
|
|
148
170
|
SidebarHistoryItem
|
|
@@ -4,17 +4,22 @@ import { useState, useEffect, useRef } from 'react';
|
|
|
4
4
|
import { MessageIcon, TrashIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from './icons.js';
|
|
5
5
|
import { SidebarMenuButton, SidebarMenuItem, useSidebar } from './ui/sidebar.js';
|
|
6
6
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from './ui/dropdown-menu.js';
|
|
7
|
+
import { ConfirmDialog } from './ui/confirm-dialog.js';
|
|
7
8
|
import { useChatNav } from './chat-nav-context.js';
|
|
8
9
|
import { cn } from '../utils.js';
|
|
9
10
|
|
|
10
11
|
export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
|
|
11
12
|
const { navigateToChat } = useChatNav();
|
|
12
13
|
const { setOpenMobile } = useSidebar();
|
|
13
|
-
const [
|
|
14
|
+
const [hovered, setHovered] = useState(false);
|
|
15
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
16
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
14
17
|
const [editing, setEditing] = useState(false);
|
|
15
18
|
const [editTitle, setEditTitle] = useState(chat.title || '');
|
|
16
19
|
const inputRef = useRef(null);
|
|
17
20
|
|
|
21
|
+
const showMenu = hovered || dropdownOpen;
|
|
22
|
+
|
|
18
23
|
useEffect(() => {
|
|
19
24
|
if (editing && inputRef.current) {
|
|
20
25
|
inputRef.current.focus();
|
|
@@ -44,8 +49,8 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
|
|
|
44
49
|
<SidebarMenuItem>
|
|
45
50
|
<div
|
|
46
51
|
className="relative group"
|
|
47
|
-
onMouseEnter={() =>
|
|
48
|
-
onMouseLeave={() =>
|
|
52
|
+
onMouseEnter={() => setHovered(true)}
|
|
53
|
+
onMouseLeave={() => setHovered(false)}
|
|
49
54
|
>
|
|
50
55
|
{editing ? (
|
|
51
56
|
<div className="flex items-center gap-2 px-2 py-1">
|
|
@@ -85,54 +90,69 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
|
|
|
85
90
|
</SidebarMenuButton>
|
|
86
91
|
)}
|
|
87
92
|
|
|
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
|
-
|
|
93
|
+
{!editing && (
|
|
94
|
+
<div className={cn(
|
|
95
|
+
'absolute right-1 top-1/2 -translate-y-1/2 z-10',
|
|
96
|
+
showMenu ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
97
|
+
)}>
|
|
98
|
+
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
|
99
|
+
<DropdownMenuTrigger>
|
|
100
|
+
<button
|
|
101
|
+
className={cn(
|
|
102
|
+
'rounded-md p-1',
|
|
103
|
+
'text-muted-foreground hover:text-foreground hover:bg-muted'
|
|
104
|
+
)}
|
|
105
|
+
aria-label="Chat options"
|
|
106
|
+
>
|
|
107
|
+
<MoreHorizontalIcon size={14} />
|
|
108
|
+
</button>
|
|
109
|
+
</DropdownMenuTrigger>
|
|
110
|
+
<DropdownMenuContent align="end" side="bottom">
|
|
111
|
+
<DropdownMenuItem
|
|
112
|
+
onClick={(e) => {
|
|
113
|
+
e.stopPropagation();
|
|
114
|
+
onStar(chat.id);
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
{chat.starred ? <StarFilledIcon size={14} /> : <StarIcon size={14} />}
|
|
118
|
+
{chat.starred ? 'Unstar' : 'Star'}
|
|
119
|
+
</DropdownMenuItem>
|
|
120
|
+
<DropdownMenuItem
|
|
121
|
+
onClick={(e) => {
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
startRename();
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<PencilIcon size={14} />
|
|
127
|
+
Rename
|
|
128
|
+
</DropdownMenuItem>
|
|
129
|
+
<DropdownMenuSeparator />
|
|
130
|
+
<DropdownMenuItem
|
|
131
|
+
className="text-destructive hover:text-destructive"
|
|
132
|
+
onClick={(e) => {
|
|
133
|
+
e.stopPropagation();
|
|
134
|
+
setConfirmDelete(true);
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
<TrashIcon size={14} />
|
|
138
|
+
Delete
|
|
139
|
+
</DropdownMenuItem>
|
|
140
|
+
</DropdownMenuContent>
|
|
141
|
+
</DropdownMenu>
|
|
142
|
+
</div>
|
|
134
143
|
)}
|
|
135
144
|
</div>
|
|
145
|
+
<ConfirmDialog
|
|
146
|
+
open={confirmDelete}
|
|
147
|
+
title="Delete chat?"
|
|
148
|
+
description="This will permanently delete this chat and all its messages."
|
|
149
|
+
confirmLabel="Delete"
|
|
150
|
+
onConfirm={() => {
|
|
151
|
+
setConfirmDelete(false);
|
|
152
|
+
onDelete(chat.id);
|
|
153
|
+
}}
|
|
154
|
+
onCancel={() => setConfirmDelete(false)}
|
|
155
|
+
/>
|
|
136
156
|
</SidebarMenuItem>
|
|
137
157
|
);
|
|
138
158
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { cn } from "../../utils.js";
|
|
5
|
+
function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel = "Delete", cancelLabel = "Cancel", variant = "destructive" }) {
|
|
6
|
+
const cancelRef = useRef(null);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (open && cancelRef.current) {
|
|
9
|
+
cancelRef.current.focus();
|
|
10
|
+
}
|
|
11
|
+
}, [open]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!open) return;
|
|
14
|
+
const handleEsc = (e) => {
|
|
15
|
+
if (e.key === "Escape") onCancel();
|
|
16
|
+
};
|
|
17
|
+
document.addEventListener("keydown", handleEsc);
|
|
18
|
+
return () => document.removeEventListener("keydown", handleEsc);
|
|
19
|
+
}, [open, onCancel]);
|
|
20
|
+
if (!open) return null;
|
|
21
|
+
return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
22
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black/50", onClick: onCancel }),
|
|
23
|
+
/* @__PURE__ */ jsxs("div", { className: "relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg", onClick: (e) => e.stopPropagation(), children: [
|
|
24
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
|
|
25
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: description }),
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
27
|
+
/* @__PURE__ */ jsx(
|
|
28
|
+
"button",
|
|
29
|
+
{
|
|
30
|
+
ref: cancelRef,
|
|
31
|
+
onClick: onCancel,
|
|
32
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted",
|
|
33
|
+
children: cancelLabel
|
|
34
|
+
}
|
|
35
|
+
),
|
|
36
|
+
/* @__PURE__ */ jsx(
|
|
37
|
+
"button",
|
|
38
|
+
{
|
|
39
|
+
onClick: onConfirm,
|
|
40
|
+
className: cn(
|
|
41
|
+
"rounded-md px-3 py-1.5 text-sm font-medium text-white",
|
|
42
|
+
variant === "destructive" ? "bg-destructive hover:bg-destructive/90" : "bg-foreground hover:bg-foreground/90"
|
|
43
|
+
),
|
|
44
|
+
children: confirmLabel
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
] })
|
|
48
|
+
] })
|
|
49
|
+
] });
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
ConfirmDialog
|
|
53
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
|
|
6
|
+
export function ConfirmDialog({ open, onConfirm, onCancel, title, description, confirmLabel = 'Delete', cancelLabel = 'Cancel', variant = 'destructive' }) {
|
|
7
|
+
const cancelRef = useRef(null);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (open && cancelRef.current) {
|
|
11
|
+
cancelRef.current.focus();
|
|
12
|
+
}
|
|
13
|
+
}, [open]);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!open) return;
|
|
17
|
+
const handleEsc = (e) => {
|
|
18
|
+
if (e.key === 'Escape') onCancel();
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener('keydown', handleEsc);
|
|
21
|
+
return () => document.removeEventListener('keydown', handleEsc);
|
|
22
|
+
}, [open, onCancel]);
|
|
23
|
+
|
|
24
|
+
if (!open) return null;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
28
|
+
<div className="fixed inset-0 bg-black/50" onClick={onCancel} />
|
|
29
|
+
<div className="relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
|
|
30
|
+
<h3 className="text-lg font-semibold">{title}</h3>
|
|
31
|
+
{description && (
|
|
32
|
+
<p className="mt-2 text-sm text-muted-foreground">{description}</p>
|
|
33
|
+
)}
|
|
34
|
+
<div className="mt-4 flex justify-end gap-2">
|
|
35
|
+
<button
|
|
36
|
+
ref={cancelRef}
|
|
37
|
+
onClick={onCancel}
|
|
38
|
+
className="rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted"
|
|
39
|
+
>
|
|
40
|
+
{cancelLabel}
|
|
41
|
+
</button>
|
|
42
|
+
<button
|
|
43
|
+
onClick={onConfirm}
|
|
44
|
+
className={cn(
|
|
45
|
+
'rounded-md px-3 py-1.5 text-sm font-medium text-white',
|
|
46
|
+
variant === 'destructive'
|
|
47
|
+
? 'bg-destructive hover:bg-destructive/90'
|
|
48
|
+
: 'bg-foreground hover:bg-foreground/90'
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
{confirmLabel}
|
|
52
|
+
</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|