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.
@@ -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 [showMenu, setShowMenu] = useState(false);
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: () => setShowMenu(true),
183
- onMouseLeave: () => setShowMenu(false),
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
- showMenu && !editing && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
220
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
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
- "shrink-0 rounded-md p-1.5",
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
- onDelete(chat.id);
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 [showMenu, setShowMenu] = useState(false);
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={() => setShowMenu(true)}
227
- onMouseLeave={() => setShowMenu(false)}
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
- {showMenu && !editing && (
262
- <DropdownMenu>
263
- <DropdownMenuTrigger asChild>
264
- <button
265
- className={cn(
266
- 'shrink-0 rounded-md p-1.5',
267
- 'text-muted-foreground hover:text-foreground hover:bg-muted'
268
- )}
269
- onClick={(e) => e.stopPropagation()}
270
- aria-label="Chat options"
271
- >
272
- <MoreHorizontalIcon size={14} />
273
- </button>
274
- </DropdownMenuTrigger>
275
- <DropdownMenuContent align="end" side="bottom">
276
- <DropdownMenuItem
277
- onClick={(e) => {
278
- e.stopPropagation();
279
- onStar(chat.id);
280
- }}
281
- >
282
- {chat.starred ? <StarFilledIcon size={14} /> : <StarIcon size={14} />}
283
- {chat.starred ? 'Unstar' : 'Star'}
284
- </DropdownMenuItem>
285
- <DropdownMenuItem
286
- onClick={(e) => {
287
- e.stopPropagation();
288
- startRename();
289
- }}
290
- >
291
- <PencilIcon size={14} />
292
- Rename
293
- </DropdownMenuItem>
294
- <DropdownMenuSeparator />
295
- <DropdownMenuItem
296
- className="text-destructive hover:text-destructive"
297
- onClick={(e) => {
298
- e.stopPropagation();
299
- onDelete(chat.id);
300
- }}
301
- >
302
- <TrashIcon size={14} />
303
- Delete
304
- </DropdownMenuItem>
305
- </DropdownMenuContent>
306
- </DropdownMenu>
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 [showMenu, setShowMenu] = useState(false);
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__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(
38
- "div",
39
- {
40
- className: "relative group",
41
- onMouseEnter: () => setShowMenu(true),
42
- onMouseLeave: () => setShowMenu(false),
43
- children: [
44
- editing ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2 py-1", children: [
45
- /* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
46
- /* @__PURE__ */ jsx(
47
- "input",
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
- ref: inputRef,
50
- type: "text",
51
- value: editTitle,
52
- onChange: (e) => setEditTitle(e.target.value),
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
- onBlur: saveRename,
58
- 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"
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
- ] }) : /* @__PURE__ */ jsxs(
62
- SidebarMenuButton,
63
- {
64
- isActive,
65
- onClick: () => {
66
- navigateToChat(chat.id);
67
- setOpenMobile(false);
68
- },
69
- children: [
70
- /* @__PURE__ */ jsx(MessageIcon, { size: 14 }),
71
- /* @__PURE__ */ jsx(
72
- "span",
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
- className: "truncate flex-1",
75
- onDoubleClick: (e) => {
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: chat.title
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
- onClick: (e) => e.stopPropagation(),
95
- "aria-label": "Chat options",
96
- children: /* @__PURE__ */ jsx(MoreHorizontalIcon, { size: 14 })
97
- }
98
- ) }),
99
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", side: "bottom", children: [
100
- /* @__PURE__ */ jsxs(
101
- DropdownMenuItem,
102
- {
103
- onClick: (e) => {
104
- e.stopPropagation();
105
- onStar(chat.id);
106
- },
107
- children: [
108
- chat.starred ? /* @__PURE__ */ jsx(StarFilledIcon, { size: 14 }) : /* @__PURE__ */ jsx(StarIcon, { size: 14 }),
109
- chat.starred ? "Unstar" : "Star"
110
- ]
111
- }
112
- ),
113
- /* @__PURE__ */ jsxs(
114
- DropdownMenuItem,
115
- {
116
- onClick: (e) => {
117
- e.stopPropagation();
118
- startRename();
119
- },
120
- children: [
121
- /* @__PURE__ */ jsx(PencilIcon, { size: 14 }),
122
- "Rename"
123
- ]
124
- }
125
- ),
126
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
127
- /* @__PURE__ */ jsxs(
128
- DropdownMenuItem,
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 [showMenu, setShowMenu] = useState(false);
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={() => setShowMenu(true)}
48
- onMouseLeave={() => setShowMenu(false)}
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
- {showMenu && !editing && (
89
- <DropdownMenu>
90
- <DropdownMenuTrigger asChild>
91
- <button
92
- className={cn(
93
- 'absolute right-1 top-1/2 -translate-y-1/2 rounded-md p-1',
94
- 'text-muted-foreground hover:text-foreground hover:bg-muted'
95
- )}
96
- onClick={(e) => e.stopPropagation()}
97
- aria-label="Chat options"
98
- >
99
- <MoreHorizontalIcon size={14} />
100
- </button>
101
- </DropdownMenuTrigger>
102
- <DropdownMenuContent align="end" side="bottom">
103
- <DropdownMenuItem
104
- onClick={(e) => {
105
- e.stopPropagation();
106
- onStar(chat.id);
107
- }}
108
- >
109
- {chat.starred ? <StarFilledIcon size={14} /> : <StarIcon size={14} />}
110
- {chat.starred ? 'Unstar' : 'Star'}
111
- </DropdownMenuItem>
112
- <DropdownMenuItem
113
- onClick={(e) => {
114
- e.stopPropagation();
115
- startRename();
116
- }}
117
- >
118
- <PencilIcon size={14} />
119
- Rename
120
- </DropdownMenuItem>
121
- <DropdownMenuSeparator />
122
- <DropdownMenuItem
123
- className="text-destructive hover:text-destructive"
124
- onClick={(e) => {
125
- e.stopPropagation();
126
- onDelete(chat.id);
127
- }}
128
- >
129
- <TrashIcon size={14} />
130
- Delete
131
- </DropdownMenuItem>
132
- </DropdownMenuContent>
133
- </DropdownMenu>
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.27",
3
+ "version": "1.2.28",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {