thepopebot 1.2.26 → 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.
- package/lib/chat/actions.js +33 -0
- package/lib/chat/components/app-sidebar.js +1 -33
- package/lib/chat/components/app-sidebar.jsx +1 -41
- package/lib/chat/components/chats-page.js +139 -27
- package/lib/chat/components/chats-page.jsx +133 -26
- package/lib/chat/components/crons-page.js +40 -16
- package/lib/chat/components/crons-page.jsx +49 -17
- package/lib/chat/components/icons.js +76 -0
- package/lib/chat/components/icons.jsx +74 -0
- package/lib/chat/components/notifications-page.js +3 -2
- package/lib/chat/components/notifications-page.jsx +6 -3
- package/lib/chat/components/settings-layout.js +3 -1
- package/lib/chat/components/settings-layout.jsx +3 -1
- package/lib/chat/components/sidebar-history-item.js +117 -22
- package/lib/chat/components/sidebar-history-item.jsx +117 -29
- package/lib/chat/components/sidebar-history.js +26 -3
- package/lib/chat/components/sidebar-history.jsx +27 -2
- package/lib/chat/components/triggers-page.js +42 -16
- package/lib/chat/components/triggers-page.jsx +51 -17
- package/lib/db/chats.js +16 -0
- package/lib/db/index.js +5 -0
- package/lib/db/schema.js +1 -0
- package/package.json +2 -2
- package/templates/.env.example +5 -2
- package/templates/app/crons/page.js +3 -5
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/page.js +1 -1
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/triggers/page.js +3 -5
package/lib/chat/actions.js
CHANGED
|
@@ -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
|
|
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
|
|
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,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
|
-
|
|
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 [
|
|
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: () =>
|
|
138
|
-
onMouseLeave: () =>
|
|
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(
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
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
|
|
|
9
10
|
function groupChatsByDate(chats) {
|
|
@@ -14,6 +15,7 @@ function groupChatsByDate(chats) {
|
|
|
14
15
|
const last30Days = new Date(today.getTime() - 30 * 86400000);
|
|
15
16
|
|
|
16
17
|
const groups = {
|
|
18
|
+
Starred: [],
|
|
17
19
|
Today: [],
|
|
18
20
|
Yesterday: [],
|
|
19
21
|
'Last 7 Days': [],
|
|
@@ -22,6 +24,10 @@ function groupChatsByDate(chats) {
|
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
for (const chat of chats) {
|
|
27
|
+
if (chat.starred) {
|
|
28
|
+
groups.Starred.push(chat);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
25
31
|
const date = new Date(chat.updatedAt);
|
|
26
32
|
if (date >= today) {
|
|
27
33
|
groups.Today.push(chat);
|
|
@@ -83,10 +89,25 @@ export function ChatsPage({ session }) {
|
|
|
83
89
|
}, []);
|
|
84
90
|
|
|
85
91
|
const handleDelete = async (chatId) => {
|
|
92
|
+
setChats((prev) => prev.filter((c) => c.id !== chatId));
|
|
86
93
|
const { success } = await deleteChat(chatId);
|
|
87
|
-
if (success)
|
|
88
|
-
|
|
89
|
-
|
|
94
|
+
if (!success) loadChats();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleStar = async (chatId) => {
|
|
98
|
+
setChats((prev) =>
|
|
99
|
+
prev.map((c) => (c.id === chatId ? { ...c, starred: c.starred ? 0 : 1 } : c))
|
|
100
|
+
);
|
|
101
|
+
const { success } = await starChat(chatId);
|
|
102
|
+
if (!success) loadChats();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const handleRename = async (chatId, title) => {
|
|
106
|
+
setChats((prev) =>
|
|
107
|
+
prev.map((c) => (c.id === chatId ? { ...c, title } : c))
|
|
108
|
+
);
|
|
109
|
+
const { success } = await renameChat(chatId, title);
|
|
110
|
+
if (!success) loadChats();
|
|
90
111
|
};
|
|
91
112
|
|
|
92
113
|
const filtered = query
|
|
@@ -154,6 +175,8 @@ export function ChatsPage({ session }) {
|
|
|
154
175
|
chat={chat}
|
|
155
176
|
onNavigate={navigateToChat}
|
|
156
177
|
onDelete={handleDelete}
|
|
178
|
+
onStar={handleStar}
|
|
179
|
+
onRename={handleRename}
|
|
157
180
|
/>
|
|
158
181
|
))}
|
|
159
182
|
</div>
|
|
@@ -166,37 +189,121 @@ export function ChatsPage({ session }) {
|
|
|
166
189
|
);
|
|
167
190
|
}
|
|
168
191
|
|
|
169
|
-
function ChatRow({ chat, onNavigate, onDelete }) {
|
|
170
|
-
const [
|
|
192
|
+
function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
193
|
+
const [showMenu, setShowMenu] = useState(false);
|
|
194
|
+
const [editing, setEditing] = useState(false);
|
|
195
|
+
const [editTitle, setEditTitle] = useState(chat.title || '');
|
|
196
|
+
const inputRef = useRef(null);
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (editing && inputRef.current) {
|
|
200
|
+
inputRef.current.focus();
|
|
201
|
+
inputRef.current.select();
|
|
202
|
+
}
|
|
203
|
+
}, [editing]);
|
|
204
|
+
|
|
205
|
+
const startRename = () => {
|
|
206
|
+
setEditTitle(chat.title || '');
|
|
207
|
+
setEditing(true);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const saveRename = () => {
|
|
211
|
+
const trimmed = editTitle.trim();
|
|
212
|
+
if (trimmed && trimmed !== chat.title) {
|
|
213
|
+
onRename(chat.id, trimmed);
|
|
214
|
+
}
|
|
215
|
+
setEditing(false);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const cancelRename = () => {
|
|
219
|
+
setEditing(false);
|
|
220
|
+
setEditTitle(chat.title || '');
|
|
221
|
+
};
|
|
171
222
|
|
|
172
223
|
return (
|
|
173
224
|
<div
|
|
174
225
|
className="relative group flex items-center gap-3 px-3 py-3 cursor-pointer hover:bg-muted/50 rounded-md"
|
|
175
|
-
onMouseEnter={() =>
|
|
176
|
-
onMouseLeave={() =>
|
|
177
|
-
onClick={() => onNavigate(chat.id)}
|
|
226
|
+
onMouseEnter={() => setShowMenu(true)}
|
|
227
|
+
onMouseLeave={() => setShowMenu(false)}
|
|
228
|
+
onClick={() => !editing && onNavigate(chat.id)}
|
|
178
229
|
>
|
|
179
230
|
<MessageIcon size={16} />
|
|
180
231
|
<div className="flex-1 min-w-0">
|
|
181
|
-
|
|
232
|
+
{editing ? (
|
|
233
|
+
<input
|
|
234
|
+
ref={inputRef}
|
|
235
|
+
type="text"
|
|
236
|
+
value={editTitle}
|
|
237
|
+
onChange={(e) => setEditTitle(e.target.value)}
|
|
238
|
+
onKeyDown={(e) => {
|
|
239
|
+
if (e.key === 'Enter') saveRename();
|
|
240
|
+
if (e.key === 'Escape') cancelRename();
|
|
241
|
+
}}
|
|
242
|
+
onBlur={saveRename}
|
|
243
|
+
onClick={(e) => e.stopPropagation()}
|
|
244
|
+
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"
|
|
245
|
+
/>
|
|
246
|
+
) : (
|
|
247
|
+
<span
|
|
248
|
+
className="text-sm truncate block"
|
|
249
|
+
onDoubleClick={(e) => {
|
|
250
|
+
e.stopPropagation();
|
|
251
|
+
startRename();
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{chat.title || 'New Chat'}
|
|
255
|
+
</span>
|
|
256
|
+
)}
|
|
182
257
|
<span className="text-xs text-muted-foreground">
|
|
183
258
|
Last message {timeAgo(chat.updatedAt)}
|
|
184
259
|
</span>
|
|
185
260
|
</div>
|
|
186
|
-
{
|
|
187
|
-
<
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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>
|
|
200
307
|
)}
|
|
201
308
|
</div>
|
|
202
309
|
);
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
|
-
import { PageLayout } from "./page-layout.js";
|
|
5
4
|
import { ClockIcon, SpinnerIcon, ChevronDownIcon } from "./icons.js";
|
|
6
5
|
import { getSwarmConfig } from "../actions.js";
|
|
7
6
|
function describeCron(schedule) {
|
|
@@ -41,6 +40,24 @@ const typeBadgeStyles = {
|
|
|
41
40
|
command: "bg-blue-500/10 text-blue-500",
|
|
42
41
|
webhook: "bg-orange-500/10 text-orange-500"
|
|
43
42
|
};
|
|
43
|
+
const typeOrder = { agent: 0, command: 1, webhook: 2 };
|
|
44
|
+
function sortByType(items) {
|
|
45
|
+
return [...items].sort((a, b) => {
|
|
46
|
+
const ta = typeOrder[a.type || "agent"] ?? 99;
|
|
47
|
+
const tb = typeOrder[b.type || "agent"] ?? 99;
|
|
48
|
+
return ta - tb;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function GroupHeader({ label, count }) {
|
|
52
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-2 pb-1", children: [
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: label }),
|
|
54
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
55
|
+
"(",
|
|
56
|
+
count,
|
|
57
|
+
")"
|
|
58
|
+
] })
|
|
59
|
+
] });
|
|
60
|
+
}
|
|
44
61
|
function CronCard({ cron }) {
|
|
45
62
|
const [expanded, setExpanded] = useState(false);
|
|
46
63
|
const type = cron.type || "agent";
|
|
@@ -106,7 +123,7 @@ function CronCard({ cron }) {
|
|
|
106
123
|
}
|
|
107
124
|
);
|
|
108
125
|
}
|
|
109
|
-
function CronsPage(
|
|
126
|
+
function CronsPage() {
|
|
110
127
|
const [crons, setCrons] = useState([]);
|
|
111
128
|
const [loading, setLoading] = useState(true);
|
|
112
129
|
useEffect(() => {
|
|
@@ -115,18 +132,16 @@ function CronsPage({ session }) {
|
|
|
115
132
|
}).catch(() => {
|
|
116
133
|
}).finally(() => setLoading(false));
|
|
117
134
|
}, []);
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
" enabled"
|
|
129
|
-
] })
|
|
135
|
+
const enabled = sortByType(crons.filter((c) => c.enabled !== false));
|
|
136
|
+
const disabled = sortByType(crons.filter((c) => c.enabled === false));
|
|
137
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
138
|
+
!loading && /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-4", children: [
|
|
139
|
+
crons.length,
|
|
140
|
+
" job",
|
|
141
|
+
crons.length !== 1 ? "s" : "",
|
|
142
|
+
" configured, ",
|
|
143
|
+
enabled.length,
|
|
144
|
+
" enabled"
|
|
130
145
|
] }),
|
|
131
146
|
loading ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(3)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-20 animate-pulse rounded-lg bg-border/50" }, i)) }) : crons.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
|
|
132
147
|
/* @__PURE__ */ jsx("div", { className: "rounded-full bg-muted p-4 mb-4", children: /* @__PURE__ */ jsx(ClockIcon, { size: 24 }) }),
|
|
@@ -136,7 +151,16 @@ function CronsPage({ session }) {
|
|
|
136
151
|
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "config/CRONS.json" }),
|
|
137
152
|
" in your project."
|
|
138
153
|
] })
|
|
139
|
-
] }) : /* @__PURE__ */
|
|
154
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
155
|
+
enabled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
156
|
+
/* @__PURE__ */ jsx(GroupHeader, { label: "Enabled", count: enabled.length }),
|
|
157
|
+
enabled.map((cron, i) => /* @__PURE__ */ jsx(CronCard, { cron }, `enabled-${i}`))
|
|
158
|
+
] }),
|
|
159
|
+
disabled.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
160
|
+
/* @__PURE__ */ jsx(GroupHeader, { label: "Disabled", count: disabled.length }),
|
|
161
|
+
disabled.map((cron, i) => /* @__PURE__ */ jsx(CronCard, { cron }, `disabled-${i}`))
|
|
162
|
+
] })
|
|
163
|
+
] })
|
|
140
164
|
] });
|
|
141
165
|
}
|
|
142
166
|
export {
|