thepopebot 1.2.25 → 1.2.27

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.
Files changed (35) hide show
  1. package/lib/chat/actions.js +33 -0
  2. package/lib/chat/components/app-sidebar.js +1 -33
  3. package/lib/chat/components/app-sidebar.jsx +1 -41
  4. package/lib/chat/components/chat.js +32 -15
  5. package/lib/chat/components/chat.jsx +36 -14
  6. package/lib/chat/components/chats-page.js +139 -27
  7. package/lib/chat/components/chats-page.jsx +133 -26
  8. package/lib/chat/components/crons-page.js +40 -16
  9. package/lib/chat/components/crons-page.jsx +49 -17
  10. package/lib/chat/components/greeting.js +1 -1
  11. package/lib/chat/components/greeting.jsx +1 -1
  12. package/lib/chat/components/icons.js +76 -0
  13. package/lib/chat/components/icons.jsx +74 -0
  14. package/lib/chat/components/notifications-page.js +3 -2
  15. package/lib/chat/components/notifications-page.jsx +6 -3
  16. package/lib/chat/components/settings-layout.js +3 -1
  17. package/lib/chat/components/settings-layout.jsx +3 -1
  18. package/lib/chat/components/sidebar-history-item.js +117 -22
  19. package/lib/chat/components/sidebar-history-item.jsx +117 -29
  20. package/lib/chat/components/sidebar-history.js +26 -3
  21. package/lib/chat/components/sidebar-history.jsx +27 -2
  22. package/lib/chat/components/triggers-page.js +42 -16
  23. package/lib/chat/components/triggers-page.jsx +51 -17
  24. package/lib/db/chats.js +16 -0
  25. package/lib/db/index.js +5 -0
  26. package/lib/db/schema.js +1 -0
  27. package/package.json +2 -2
  28. package/setup/setup.mjs +9 -0
  29. package/templates/.env.example +5 -2
  30. package/templates/app/crons/page.js +3 -5
  31. package/templates/app/globals.css +4 -0
  32. package/templates/app/settings/crons/page.js +5 -0
  33. package/templates/app/settings/page.js +1 -1
  34. package/templates/app/settings/triggers/page.js +5 -0
  35. package/templates/app/triggers/page.js +3 -5
@@ -7,6 +7,8 @@ import {
7
7
  getMessagesByChatId,
8
8
  deleteChat as dbDeleteChat,
9
9
  deleteAllChatsByUser,
10
+ updateChatTitle,
11
+ toggleChatStarred,
10
12
  } from '../db/chats.js';
11
13
  import {
12
14
  getNotifications as dbGetNotifications,
@@ -83,6 +85,37 @@ export async function deleteChat(chatId) {
83
85
  return { success: true };
84
86
  }
85
87
 
88
+ /**
89
+ * Rename a chat (with ownership check).
90
+ * @param {string} chatId
91
+ * @param {string} title
92
+ * @returns {Promise<{success: boolean}>}
93
+ */
94
+ export async function renameChat(chatId, title) {
95
+ const user = await requireAuth();
96
+ const chat = getChatById(chatId);
97
+ if (!chat || chat.userId !== user.id) {
98
+ return { success: false };
99
+ }
100
+ updateChatTitle(chatId, title);
101
+ return { success: true };
102
+ }
103
+
104
+ /**
105
+ * Toggle a chat's starred status (with ownership check).
106
+ * @param {string} chatId
107
+ * @returns {Promise<{success: boolean, starred?: number}>}
108
+ */
109
+ export async function starChat(chatId) {
110
+ const user = await requireAuth();
111
+ const chat = getChatById(chatId);
112
+ if (!chat || chat.userId !== user.id) {
113
+ return { success: false };
114
+ }
115
+ const starred = toggleChatStarred(chatId);
116
+ return { success: true, starred };
117
+ }
118
+
86
119
  /**
87
120
  * Delete all chats for the authenticated user.
88
121
  * @returns {Promise<{success: boolean}>}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from "react";
4
- import { SquarePenIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ClockIcon, ZapIcon } from "./icons.js";
4
+ import { SquarePenIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon } from "./icons.js";
5
5
  import { getUnreadNotificationCount } from "../actions.js";
6
6
  import { SidebarHistory } from "./sidebar-history.js";
7
7
  import { SidebarUserNav } from "./sidebar-user-nav.js";
@@ -113,38 +113,6 @@ function AppSidebar({ user }) {
113
113
  }
114
114
  ) }),
115
115
  collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Swarm" })
116
- ] }) }),
117
- /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
118
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
119
- SidebarMenuButton,
120
- {
121
- className: collapsed ? "justify-center" : "",
122
- onClick: () => {
123
- window.location.href = "/crons";
124
- },
125
- children: [
126
- /* @__PURE__ */ jsx(ClockIcon, { size: 16 }),
127
- !collapsed && /* @__PURE__ */ jsx("span", { children: "Cron Jobs" })
128
- ]
129
- }
130
- ) }),
131
- collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Cron Jobs" })
132
- ] }) }),
133
- /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
134
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
135
- SidebarMenuButton,
136
- {
137
- className: collapsed ? "justify-center" : "",
138
- onClick: () => {
139
- window.location.href = "/triggers";
140
- },
141
- children: [
142
- /* @__PURE__ */ jsx(ZapIcon, { size: 16 }),
143
- !collapsed && /* @__PURE__ */ jsx("span", { children: "Triggers" })
144
- ]
145
- }
146
- ) }),
147
- collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Triggers" })
148
116
  ] }) })
149
117
  ] })
150
118
  ] }),
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect } from 'react';
4
- import { SquarePenIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ClockIcon, ZapIcon } from './icons.js';
4
+ import { SquarePenIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon } from './icons.js';
5
5
  import { getUnreadNotificationCount } from '../actions.js';
6
6
  import { SidebarHistory } from './sidebar-history.js';
7
7
  import { SidebarUserNav } from './sidebar-user-nav.js';
@@ -151,46 +151,6 @@ export function AppSidebar({ user }) {
151
151
  </Tooltip>
152
152
  </SidebarMenuItem>
153
153
 
154
- {/* Cron Jobs */}
155
- <SidebarMenuItem>
156
- <Tooltip>
157
- <TooltipTrigger asChild>
158
- <SidebarMenuButton
159
- className={collapsed ? 'justify-center' : ''}
160
- onClick={() => {
161
- window.location.href = '/crons';
162
- }}
163
- >
164
- <ClockIcon size={16} />
165
- {!collapsed && <span>Cron Jobs</span>}
166
- </SidebarMenuButton>
167
- </TooltipTrigger>
168
- {collapsed && (
169
- <TooltipContent side="right">Cron Jobs</TooltipContent>
170
- )}
171
- </Tooltip>
172
- </SidebarMenuItem>
173
-
174
- {/* Triggers */}
175
- <SidebarMenuItem>
176
- <Tooltip>
177
- <TooltipTrigger asChild>
178
- <SidebarMenuButton
179
- className={collapsed ? 'justify-center' : ''}
180
- onClick={() => {
181
- window.location.href = '/triggers';
182
- }}
183
- >
184
- <ZapIcon size={16} />
185
- {!collapsed && <span>Triggers</span>}
186
- </SidebarMenuButton>
187
- </TooltipTrigger>
188
- {collapsed && (
189
- <TooltipContent side="right">Triggers</TooltipContent>
190
- )}
191
- </Tooltip>
192
- </SidebarMenuItem>
193
-
194
154
  </SidebarMenu>
195
155
  </SidebarHeader>
196
156
 
@@ -1,11 +1,12 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useChat } from "@ai-sdk/react";
4
4
  import { DefaultChatTransport } from "ai";
5
5
  import { useState, useEffect, useRef, useMemo } from "react";
6
6
  import { Messages } from "./messages.js";
7
7
  import { ChatInput } from "./chat-input.js";
8
8
  import { ChatHeader } from "./chat-header.js";
9
+ import { Greeting } from "./greeting.js";
9
10
  function Chat({ chatId, initialMessages = [] }) {
10
11
  const [input, setInput] = useState("");
11
12
  const [files, setFiles] = useState([]);
@@ -57,20 +58,36 @@ function Chat({ chatId, initialMessages = [] }) {
57
58
  };
58
59
  return /* @__PURE__ */ jsxs("div", { className: "flex h-svh flex-col", children: [
59
60
  /* @__PURE__ */ jsx(ChatHeader, { chatId }),
60
- /* @__PURE__ */ jsx(Messages, { messages, status }),
61
- error && /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-2 md:px-4", children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive", children: error.message || "Something went wrong. Please try again." }) }),
62
- /* @__PURE__ */ jsx(
63
- ChatInput,
64
- {
65
- input,
66
- setInput,
67
- onSubmit: handleSend,
68
- status,
69
- stop,
70
- files,
71
- setFiles
72
- }
73
- )
61
+ messages.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col items-center justify-center px-2 md:px-4", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-4xl", children: [
62
+ /* @__PURE__ */ jsx(Greeting, {}),
63
+ /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(
64
+ ChatInput,
65
+ {
66
+ input,
67
+ setInput,
68
+ onSubmit: handleSend,
69
+ status,
70
+ stop,
71
+ files,
72
+ setFiles
73
+ }
74
+ ) })
75
+ ] }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
76
+ /* @__PURE__ */ jsx(Messages, { messages, status }),
77
+ error && /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-2 md:px-4", children: /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive", children: error.message || "Something went wrong. Please try again." }) }),
78
+ /* @__PURE__ */ jsx(
79
+ ChatInput,
80
+ {
81
+ input,
82
+ setInput,
83
+ onSubmit: handleSend,
84
+ status,
85
+ stop,
86
+ files,
87
+ setFiles
88
+ }
89
+ )
90
+ ] })
74
91
  ] });
75
92
  }
76
93
  export {
@@ -6,6 +6,7 @@ import { useState, useEffect, useRef, useMemo } from 'react';
6
6
  import { Messages } from './messages.js';
7
7
  import { ChatInput } from './chat-input.js';
8
8
  import { ChatHeader } from './chat-header.js';
9
+ import { Greeting } from './greeting.js';
9
10
 
10
11
  export function Chat({ chatId, initialMessages = [] }) {
11
12
  const [input, setInput] = useState('');
@@ -69,23 +70,44 @@ export function Chat({ chatId, initialMessages = [] }) {
69
70
  return (
70
71
  <div className="flex h-svh flex-col">
71
72
  <ChatHeader chatId={chatId} />
72
- <Messages messages={messages} status={status} />
73
- {error && (
74
- <div className="mx-auto w-full max-w-4xl px-2 md:px-4">
75
- <div className="rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive">
76
- {error.message || 'Something went wrong. Please try again.'}
73
+ {messages.length === 0 ? (
74
+ <div className="flex flex-1 flex-col items-center justify-center px-2 md:px-4">
75
+ <div className="w-full max-w-4xl">
76
+ <Greeting />
77
+ <div className="mt-4">
78
+ <ChatInput
79
+ input={input}
80
+ setInput={setInput}
81
+ onSubmit={handleSend}
82
+ status={status}
83
+ stop={stop}
84
+ files={files}
85
+ setFiles={setFiles}
86
+ />
87
+ </div>
77
88
  </div>
78
89
  </div>
90
+ ) : (
91
+ <>
92
+ <Messages messages={messages} status={status} />
93
+ {error && (
94
+ <div className="mx-auto w-full max-w-4xl px-2 md:px-4">
95
+ <div className="rounded-lg border border-destructive/50 bg-destructive/10 px-4 py-2 text-sm text-destructive">
96
+ {error.message || 'Something went wrong. Please try again.'}
97
+ </div>
98
+ </div>
99
+ )}
100
+ <ChatInput
101
+ input={input}
102
+ setInput={setInput}
103
+ onSubmit={handleSend}
104
+ status={status}
105
+ stop={stop}
106
+ files={files}
107
+ setFiles={setFiles}
108
+ />
109
+ </>
79
110
  )}
80
- <ChatInput
81
- input={input}
82
- setInput={setInput}
83
- onSubmit={handleSend}
84
- status={status}
85
- stop={stop}
86
- files={files}
87
- setFiles={setFiles}
88
- />
89
111
  </div>
90
112
  );
91
113
  }
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useState, useEffect } from "react";
3
+ import { useState, useEffect, useRef } from "react";
4
4
  import { PageLayout } from "./page-layout.js";
5
- import { MessageIcon, TrashIcon, SearchIcon, PlusIcon } from "./icons.js";
6
- import { getChats, deleteChat } from "../actions.js";
5
+ import { MessageIcon, TrashIcon, SearchIcon, PlusIcon, MoreHorizontalIcon, StarIcon, StarFilledIcon, PencilIcon } from "./icons.js";
6
+ import { getChats, deleteChat, renameChat, starChat } from "../actions.js";
7
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "./ui/dropdown-menu.js";
7
8
  import { cn } from "../utils.js";
8
9
  function groupChatsByDate(chats) {
9
10
  const now = /* @__PURE__ */ new Date();
@@ -12,6 +13,7 @@ function groupChatsByDate(chats) {
12
13
  const last7Days = new Date(today.getTime() - 7 * 864e5);
13
14
  const last30Days = new Date(today.getTime() - 30 * 864e5);
14
15
  const groups = {
16
+ Starred: [],
15
17
  Today: [],
16
18
  Yesterday: [],
17
19
  "Last 7 Days": [],
@@ -19,6 +21,10 @@ function groupChatsByDate(chats) {
19
21
  Older: []
20
22
  };
21
23
  for (const chat of chats) {
24
+ if (chat.starred) {
25
+ groups.Starred.push(chat);
26
+ continue;
27
+ }
22
28
  const date = new Date(chat.updatedAt);
23
29
  if (date >= today) {
24
30
  groups.Today.push(chat);
@@ -72,10 +78,23 @@ function ChatsPage({ session }) {
72
78
  return () => window.removeEventListener("chatsupdated", handler);
73
79
  }, []);
74
80
  const handleDelete = async (chatId) => {
81
+ setChats((prev) => prev.filter((c) => c.id !== chatId));
75
82
  const { success } = await deleteChat(chatId);
76
- if (success) {
77
- setChats((prev) => prev.filter((c) => c.id !== chatId));
78
- }
83
+ if (!success) loadChats();
84
+ };
85
+ const handleStar = async (chatId) => {
86
+ setChats(
87
+ (prev) => prev.map((c) => c.id === chatId ? { ...c, starred: c.starred ? 0 : 1 } : c)
88
+ );
89
+ const { success } = await starChat(chatId);
90
+ if (!success) loadChats();
91
+ };
92
+ const handleRename = async (chatId, title) => {
93
+ setChats(
94
+ (prev) => prev.map((c) => c.id === chatId ? { ...c, title } : c)
95
+ );
96
+ const { success } = await renameChat(chatId, title);
97
+ if (!success) loadChats();
79
98
  };
80
99
  const filtered = query ? chats.filter((c) => c.title?.toLowerCase().includes(query.toLowerCase())) : chats;
81
100
  const grouped = groupChatsByDate(filtered);
@@ -120,7 +139,9 @@ function ChatsPage({ session }) {
120
139
  {
121
140
  chat,
122
141
  onNavigate: navigateToChat,
123
- onDelete: handleDelete
142
+ onDelete: handleDelete,
143
+ onStar: handleStar,
144
+ onRename: handleRename
124
145
  },
125
146
  chat.id
126
147
  )) })
@@ -128,39 +149,130 @@ function ChatsPage({ session }) {
128
149
  ) })
129
150
  ] });
130
151
  }
131
- function ChatRow({ chat, onNavigate, onDelete }) {
132
- const [showDelete, setShowDelete] = useState(false);
152
+ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
153
+ const [showMenu, setShowMenu] = useState(false);
154
+ const [editing, setEditing] = useState(false);
155
+ const [editTitle, setEditTitle] = useState(chat.title || "");
156
+ const inputRef = useRef(null);
157
+ useEffect(() => {
158
+ if (editing && inputRef.current) {
159
+ inputRef.current.focus();
160
+ inputRef.current.select();
161
+ }
162
+ }, [editing]);
163
+ const startRename = () => {
164
+ setEditTitle(chat.title || "");
165
+ setEditing(true);
166
+ };
167
+ const saveRename = () => {
168
+ const trimmed = editTitle.trim();
169
+ if (trimmed && trimmed !== chat.title) {
170
+ onRename(chat.id, trimmed);
171
+ }
172
+ setEditing(false);
173
+ };
174
+ const cancelRename = () => {
175
+ setEditing(false);
176
+ setEditTitle(chat.title || "");
177
+ };
133
178
  return /* @__PURE__ */ jsxs(
134
179
  "div",
135
180
  {
136
181
  className: "relative group flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-muted/50 rounded-md",
137
- onMouseEnter: () => setShowDelete(true),
138
- onMouseLeave: () => setShowDelete(false),
139
- onClick: () => onNavigate(chat.id),
182
+ onMouseEnter: () => setShowMenu(true),
183
+ onMouseLeave: () => setShowMenu(false),
184
+ onClick: () => !editing && onNavigate(chat.id),
140
185
  children: [
141
186
  /* @__PURE__ */ jsx(MessageIcon, { size: 16 }),
142
187
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
143
- /* @__PURE__ */ jsx("span", { className: "text-sm truncate block", children: chat.title || "New Chat" }),
188
+ editing ? /* @__PURE__ */ jsx(
189
+ "input",
190
+ {
191
+ ref: inputRef,
192
+ type: "text",
193
+ value: editTitle,
194
+ onChange: (e) => setEditTitle(e.target.value),
195
+ onKeyDown: (e) => {
196
+ if (e.key === "Enter") saveRename();
197
+ if (e.key === "Escape") cancelRename();
198
+ },
199
+ onBlur: saveRename,
200
+ onClick: (e) => e.stopPropagation(),
201
+ className: "w-full text-sm bg-background border border-input rounded px-1.5 py-0.5 focus:outline-none focus:ring-2 focus:ring-ring"
202
+ }
203
+ ) : /* @__PURE__ */ jsx(
204
+ "span",
205
+ {
206
+ className: "text-sm truncate block",
207
+ onDoubleClick: (e) => {
208
+ e.stopPropagation();
209
+ startRename();
210
+ },
211
+ children: chat.title || "New Chat"
212
+ }
213
+ ),
144
214
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
145
215
  "Last message ",
146
216
  timeAgo(chat.updatedAt)
147
217
  ] })
148
218
  ] }),
149
- showDelete && /* @__PURE__ */ jsx(
150
- "button",
151
- {
152
- className: cn(
153
- "shrink-0 rounded-md p-1.5",
154
- "text-muted-foreground hover:text-destructive hover:bg-muted"
219
+ showMenu && !editing && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
220
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
221
+ "button",
222
+ {
223
+ className: cn(
224
+ "shrink-0 rounded-md p-1.5",
225
+ "text-muted-foreground hover:text-foreground hover:bg-muted"
226
+ ),
227
+ onClick: (e) => e.stopPropagation(),
228
+ "aria-label": "Chat options",
229
+ children: /* @__PURE__ */ jsx(MoreHorizontalIcon, { size: 14 })
230
+ }
231
+ ) }),
232
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", side: "bottom", children: [
233
+ /* @__PURE__ */ jsxs(
234
+ DropdownMenuItem,
235
+ {
236
+ onClick: (e) => {
237
+ e.stopPropagation();
238
+ onStar(chat.id);
239
+ },
240
+ children: [
241
+ chat.starred ? /* @__PURE__ */ jsx(StarFilledIcon, { size: 14 }) : /* @__PURE__ */ jsx(StarIcon, { size: 14 }),
242
+ chat.starred ? "Unstar" : "Star"
243
+ ]
244
+ }
245
+ ),
246
+ /* @__PURE__ */ jsxs(
247
+ DropdownMenuItem,
248
+ {
249
+ onClick: (e) => {
250
+ e.stopPropagation();
251
+ startRename();
252
+ },
253
+ children: [
254
+ /* @__PURE__ */ jsx(PencilIcon, { size: 14 }),
255
+ "Rename"
256
+ ]
257
+ }
155
258
  ),
156
- onClick: (e) => {
157
- e.stopPropagation();
158
- onDelete(chat.id);
159
- },
160
- "aria-label": "Delete chat",
161
- children: /* @__PURE__ */ jsx(TrashIcon, { size: 14 })
162
- }
163
- )
259
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
260
+ /* @__PURE__ */ jsxs(
261
+ DropdownMenuItem,
262
+ {
263
+ className: "text-destructive hover:text-destructive",
264
+ onClick: (e) => {
265
+ e.stopPropagation();
266
+ onDelete(chat.id);
267
+ },
268
+ children: [
269
+ /* @__PURE__ */ jsx(TrashIcon, { size: 14 }),
270
+ "Delete"
271
+ ]
272
+ }
273
+ )
274
+ ] })
275
+ ] })
164
276
  ]
165
277
  }
166
278
  );