@promptbook/cli 0.103.0-51 → 0.103.0-53
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/apps/agents-server/README.md +1 -1
- package/apps/agents-server/config.ts +3 -3
- package/apps/agents-server/next.config.ts +1 -1
- package/apps/agents-server/public/sw.js +16 -0
- package/apps/agents-server/src/app/AddAgentButton.tsx +24 -4
- package/apps/agents-server/src/app/actions.ts +15 -13
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +541 -0
- package/apps/agents-server/src/app/admin/chat-feedback/page.tsx +22 -0
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +532 -0
- package/apps/agents-server/src/app/admin/chat-history/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +241 -27
- package/apps/agents-server/src/app/admin/models/page.tsx +22 -0
- package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +131 -0
- package/apps/agents-server/src/app/admin/users/[userId]/page.tsx +21 -0
- package/apps/agents-server/src/app/admin/users/page.tsx +18 -0
- package/apps/agents-server/src/app/agents/[agentName]/AgentQrCode.tsx +3 -3
- package/apps/agents-server/src/app/agents/[agentName]/ClearAgentChatFeedbackButton.tsx +63 -0
- package/apps/agents-server/src/app/agents/[agentName]/ClearAgentChatHistoryButton.tsx +63 -0
- package/apps/agents-server/src/app/agents/[agentName]/CloneAgentButton.tsx +41 -0
- package/apps/agents-server/src/app/agents/[agentName]/InstallPwaButton.tsx +74 -0
- package/apps/agents-server/src/app/agents/[agentName]/ServiceWorkerRegister.tsx +24 -0
- package/apps/agents-server/src/app/agents/[agentName]/_utils.ts +19 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/agents/route.ts +67 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/profile/route.ts +3 -0
- package/apps/agents-server/src/app/agents/[agentName]/api/voice/route.ts +177 -0
- package/apps/agents-server/src/app/agents/[agentName]/book/page.tsx +3 -0
- package/apps/agents-server/src/app/agents/[agentName]/book+chat/AgentBookAndChat.tsx +53 -1
- package/apps/agents-server/src/app/agents/[agentName]/generateAgentMetadata.ts +12 -12
- package/apps/agents-server/src/app/agents/[agentName]/history/RestoreVersionButton.tsx +46 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/actions.ts +12 -0
- package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +62 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/icon-256.png/route.tsx +80 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-fullhd.png/route.tsx +92 -0
- package/apps/agents-server/src/app/agents/[agentName]/images/screenshot-phone.png/route.tsx +92 -0
- package/apps/agents-server/src/app/agents/[agentName]/integration/page.tsx +61 -0
- package/apps/agents-server/src/app/agents/[agentName]/opengraph-image.tsx +102 -0
- package/apps/agents-server/src/app/agents/[agentName]/page.tsx +46 -24
- package/apps/agents-server/src/app/api/agents/[agentName]/clone/route.ts +47 -0
- package/apps/agents-server/src/app/api/agents/[agentName]/route.ts +19 -0
- package/apps/agents-server/src/app/api/agents/route.ts +22 -13
- package/apps/agents-server/src/app/api/auth/login/route.ts +6 -44
- package/apps/agents-server/src/app/api/chat-feedback/[id]/route.ts +38 -0
- package/apps/agents-server/src/app/api/chat-feedback/route.ts +157 -0
- package/apps/agents-server/src/app/api/chat-history/[id]/route.ts +37 -0
- package/apps/agents-server/src/app/api/chat-history/route.ts +147 -0
- package/apps/agents-server/src/app/api/federated-agents/route.ts +17 -0
- package/apps/agents-server/src/app/api/upload/route.ts +9 -1
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +62 -0
- package/apps/agents-server/src/app/docs/page.tsx +33 -0
- package/apps/agents-server/src/app/layout.tsx +29 -3
- package/apps/agents-server/src/app/manifest.ts +109 -0
- package/apps/agents-server/src/app/page.tsx +8 -45
- package/apps/agents-server/src/app/recycle-bin/RestoreAgentButton.tsx +40 -0
- package/apps/agents-server/src/app/recycle-bin/actions.ts +27 -0
- package/apps/agents-server/src/app/recycle-bin/page.tsx +58 -0
- package/apps/agents-server/src/app/restricted/page.tsx +33 -0
- package/apps/agents-server/src/app/test/og-image/README.md +1 -0
- package/apps/agents-server/src/app/test/og-image/opengraph-image.tsx +37 -0
- package/apps/agents-server/src/app/test/og-image/page.tsx +22 -0
- package/apps/agents-server/src/components/Footer/Footer.tsx +175 -0
- package/apps/agents-server/src/components/Header/Header.tsx +445 -79
- package/apps/agents-server/src/components/Homepage/AgentCard.tsx +46 -14
- package/apps/agents-server/src/components/Homepage/AgentsList.tsx +58 -0
- package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSection.tsx +21 -0
- package/apps/agents-server/src/components/Homepage/ExternalAgentsSectionClient.tsx +183 -0
- package/apps/agents-server/src/components/Homepage/ModelsSection.tsx +75 -0
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +28 -3
- package/apps/agents-server/src/components/LoginDialog/LoginDialog.tsx +18 -17
- package/apps/agents-server/src/components/Portal/Portal.tsx +38 -0
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +82 -131
- package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +139 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +38 -6
- package/apps/agents-server/src/middleware.ts +146 -93
- package/apps/agents-server/src/tools/$provideServer.ts +2 -2
- package/apps/agents-server/src/utils/authenticateUser.ts +42 -0
- package/apps/agents-server/src/utils/chatFeedbackAdmin.ts +96 -0
- package/apps/agents-server/src/utils/chatHistoryAdmin.ts +96 -0
- package/apps/agents-server/src/utils/getEffectiveFederatedServers.ts +22 -0
- package/apps/agents-server/src/utils/getFederatedAgents.ts +31 -8
- package/apps/agents-server/src/utils/getFederatedServersFromMetadata.ts +10 -0
- package/apps/agents-server/src/utils/getVisibleCommitmentDefinitions.ts +12 -0
- package/apps/agents-server/src/utils/isUserAdmin.ts +2 -2
- package/apps/agents-server/vercel.json +7 -0
- package/esm/index.es.js +344 -11
- package/esm/index.es.js.map +1 -1
- package/esm/typings/servers.d.ts +8 -1
- package/esm/typings/src/_packages/components.index.d.ts +2 -0
- package/esm/typings/src/_packages/core.index.d.ts +6 -0
- package/esm/typings/src/_packages/types.index.d.ts +2 -0
- package/esm/typings/src/_packages/utils.index.d.ts +2 -0
- package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +1 -0
- package/esm/typings/src/book-2.0/agent-source/AgentModelRequirements.d.ts +7 -0
- package/esm/typings/src/book-2.0/agent-source/createCommitmentRegex.d.ts +2 -2
- package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +10 -0
- package/esm/typings/src/book-components/PromptbookAgent/PromptbookAgent.d.ts +6 -0
- package/esm/typings/src/book-components/_common/HamburgerMenu/HamburgerMenu.d.ts +12 -0
- package/esm/typings/src/book-components/icons/MicIcon.d.ts +8 -0
- package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +17 -0
- package/esm/typings/src/commitments/MESSAGE/AgentMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/MESSAGE/UserMessageCommitmentDefinition.d.ts +28 -0
- package/esm/typings/src/commitments/META_COLOR/META_COLOR.d.ts +38 -0
- package/esm/typings/src/commitments/_base/BaseCommitmentDefinition.d.ts +2 -1
- package/esm/typings/src/commitments/index.d.ts +22 -1
- package/esm/typings/src/execution/LlmExecutionTools.d.ts +9 -0
- package/esm/typings/src/llm-providers/agent/Agent.d.ts +1 -0
- package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +2 -1
- package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +10 -1
- package/esm/typings/src/utils/normalization/normalizeMessageText.d.ts +9 -0
- package/esm/typings/src/utils/normalization/normalizeMessageText.test.d.ts +1 -0
- package/esm/typings/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/umd/index.umd.js +344 -11
- package/umd/index.umd.js.map +1 -1
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { Card } from '../../../components/Homepage/Card';
|
|
5
|
+
import {
|
|
6
|
+
$clearAgentChatHistory,
|
|
7
|
+
$deleteChatHistoryRow,
|
|
8
|
+
$fetchChatHistory,
|
|
9
|
+
type ChatHistoryRow,
|
|
10
|
+
type ChatHistorySortField,
|
|
11
|
+
type ChatHistorySortOrder,
|
|
12
|
+
} from '../../../utils/chatHistoryAdmin';
|
|
13
|
+
|
|
14
|
+
type ChatHistoryClientProps = {
|
|
15
|
+
/**
|
|
16
|
+
* Optional initial agent filter, taken from the URL query.
|
|
17
|
+
*/
|
|
18
|
+
initialAgentName?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type AdminAgentInfo = {
|
|
22
|
+
agentName: string;
|
|
23
|
+
fullname?: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type AgentsApiResponse = {
|
|
27
|
+
agents: Array<{
|
|
28
|
+
agentName: string;
|
|
29
|
+
meta: { fullname?: string | null };
|
|
30
|
+
}>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function formatDate(dateString: string | null | undefined): string {
|
|
34
|
+
if (!dateString) return '-';
|
|
35
|
+
const date = new Date(dateString);
|
|
36
|
+
if (Number.isNaN(date.getTime())) return dateString;
|
|
37
|
+
return date.toLocaleString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getMessageRole(message: unknown): string {
|
|
41
|
+
if (!message || typeof message !== 'object') return '-';
|
|
42
|
+
// Chat route stores { role, content }
|
|
43
|
+
const role = (message as { role?: string }).role;
|
|
44
|
+
return role || '-';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getMessagePreview(message: unknown, maxLength = 120): string {
|
|
48
|
+
if (message == null) return '-';
|
|
49
|
+
|
|
50
|
+
if (typeof message === 'string') {
|
|
51
|
+
return message.length > maxLength ? `${message.slice(0, maxLength)}…` : message;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (typeof message === 'object') {
|
|
55
|
+
const content =
|
|
56
|
+
(message as { content?: unknown }).content ??
|
|
57
|
+
(message as { text?: unknown }).text ??
|
|
58
|
+
message;
|
|
59
|
+
|
|
60
|
+
let text: string;
|
|
61
|
+
|
|
62
|
+
if (typeof content === 'string') {
|
|
63
|
+
text = content;
|
|
64
|
+
} else if (Array.isArray(content)) {
|
|
65
|
+
text = content.map((part) => String(part)).join(' ');
|
|
66
|
+
} else {
|
|
67
|
+
text = JSON.stringify(content);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return text.length > maxLength ? `${text.slice(0, maxLength)}…` : text;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return String(message);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function ChatHistoryClient({ initialAgentName }: ChatHistoryClientProps) {
|
|
77
|
+
const [items, setItems] = useState<ChatHistoryRow[]>([]);
|
|
78
|
+
const [total, setTotal] = useState(0);
|
|
79
|
+
const [page, setPage] = useState(1);
|
|
80
|
+
const [pageSize, setPageSize] = useState(20);
|
|
81
|
+
const [agentName, setAgentName] = useState(initialAgentName ?? '');
|
|
82
|
+
const [searchInput, setSearchInput] = useState('');
|
|
83
|
+
const [search, setSearch] = useState('');
|
|
84
|
+
const [sortBy, setSortBy] = useState<ChatHistorySortField>('createdAt');
|
|
85
|
+
const [sortOrder, setSortOrder] = useState<ChatHistorySortOrder>('desc');
|
|
86
|
+
const [loading, setLoading] = useState(true);
|
|
87
|
+
const [error, setError] = useState<string | null>(null);
|
|
88
|
+
|
|
89
|
+
const [agents, setAgents] = useState<AdminAgentInfo[]>([]);
|
|
90
|
+
const [agentsLoading, setAgentsLoading] = useState(false);
|
|
91
|
+
|
|
92
|
+
// Load agents for filter dropdown
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
let isCancelled = false;
|
|
95
|
+
|
|
96
|
+
async function loadAgents() {
|
|
97
|
+
try {
|
|
98
|
+
setAgentsLoading(true);
|
|
99
|
+
const response = await fetch('/api/agents');
|
|
100
|
+
if (!response.ok) {
|
|
101
|
+
// If agents listing fails, we can still show chat history
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const data = (await response.json()) as AgentsApiResponse;
|
|
105
|
+
|
|
106
|
+
if (isCancelled) return;
|
|
107
|
+
|
|
108
|
+
const mappedAgents: AdminAgentInfo[] =
|
|
109
|
+
data.agents?.map((agent) => ({
|
|
110
|
+
agentName: agent.agentName,
|
|
111
|
+
fullname: agent.meta?.fullname ?? null,
|
|
112
|
+
})) ?? [];
|
|
113
|
+
|
|
114
|
+
// Sort by display name
|
|
115
|
+
mappedAgents.sort((a, b) => {
|
|
116
|
+
const nameA = (a.fullname || a.agentName).toLowerCase();
|
|
117
|
+
const nameB = (b.fullname || b.agentName).toLowerCase();
|
|
118
|
+
return nameA.localeCompare(nameB);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
setAgents(mappedAgents);
|
|
122
|
+
} finally {
|
|
123
|
+
if (!isCancelled) {
|
|
124
|
+
setAgentsLoading(false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
loadAgents();
|
|
130
|
+
|
|
131
|
+
return () => {
|
|
132
|
+
isCancelled = true;
|
|
133
|
+
};
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
// Initial search input: we do not auto-search, but if initialAgentName is present we set it
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (initialAgentName) {
|
|
139
|
+
setAgentName(initialAgentName);
|
|
140
|
+
}
|
|
141
|
+
}, [initialAgentName]);
|
|
142
|
+
|
|
143
|
+
// Load chat history whenever filters / pagination / sorting change
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
let isCancelled = false;
|
|
146
|
+
|
|
147
|
+
async function loadData() {
|
|
148
|
+
try {
|
|
149
|
+
setLoading(true);
|
|
150
|
+
setError(null);
|
|
151
|
+
|
|
152
|
+
const response = await $fetchChatHistory({
|
|
153
|
+
page,
|
|
154
|
+
pageSize,
|
|
155
|
+
agentName: agentName || undefined,
|
|
156
|
+
search: search || undefined,
|
|
157
|
+
sortBy,
|
|
158
|
+
sortOrder,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (isCancelled) return;
|
|
162
|
+
|
|
163
|
+
setItems(response.items);
|
|
164
|
+
setTotal(response.total);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (isCancelled) return;
|
|
167
|
+
setError(err instanceof Error ? err.message : 'Failed to load chat history');
|
|
168
|
+
} finally {
|
|
169
|
+
if (!isCancelled) {
|
|
170
|
+
setLoading(false);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
loadData();
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
isCancelled = true;
|
|
179
|
+
};
|
|
180
|
+
}, [page, pageSize, agentName, search, sortBy, sortOrder]);
|
|
181
|
+
|
|
182
|
+
const totalPages = useMemo(() => {
|
|
183
|
+
if (total <= 0 || pageSize <= 0) return 1;
|
|
184
|
+
return Math.max(1, Math.ceil(total / pageSize));
|
|
185
|
+
}, [total, pageSize]);
|
|
186
|
+
|
|
187
|
+
const handleSearchSubmit = (event: React.FormEvent) => {
|
|
188
|
+
event.preventDefault();
|
|
189
|
+
setPage(1);
|
|
190
|
+
setSearch(searchInput.trim());
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const handleAgentChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
194
|
+
const value = event.target.value;
|
|
195
|
+
setAgentName(value);
|
|
196
|
+
setPage(1);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handlePageSizeChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
|
200
|
+
const next = parseInt(event.target.value, 10);
|
|
201
|
+
if (!Number.isNaN(next) && next > 0) {
|
|
202
|
+
setPageSize(next);
|
|
203
|
+
setPage(1);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleSortChange = (field: ChatHistorySortField) => {
|
|
208
|
+
if (sortBy === field) {
|
|
209
|
+
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
|
210
|
+
} else {
|
|
211
|
+
setSortBy(field);
|
|
212
|
+
setSortOrder(field === 'createdAt' ? 'desc' : 'asc');
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const handleDeleteRow = async (row: ChatHistoryRow) => {
|
|
217
|
+
if (!row.id) return;
|
|
218
|
+
|
|
219
|
+
const confirmed = window.confirm('Are you sure you want to delete this chat message?');
|
|
220
|
+
if (!confirmed) return;
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
await $deleteChatHistoryRow(row.id);
|
|
224
|
+
// Reload current page
|
|
225
|
+
const response = await $fetchChatHistory({
|
|
226
|
+
page,
|
|
227
|
+
pageSize,
|
|
228
|
+
agentName: agentName || undefined,
|
|
229
|
+
search: search || undefined,
|
|
230
|
+
sortBy,
|
|
231
|
+
sortOrder,
|
|
232
|
+
});
|
|
233
|
+
setItems(response.items);
|
|
234
|
+
setTotal(response.total);
|
|
235
|
+
} catch (err) {
|
|
236
|
+
setError(err instanceof Error ? err.message : 'Failed to delete chat message');
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleClearAgentHistory = async () => {
|
|
241
|
+
if (!agentName) return;
|
|
242
|
+
|
|
243
|
+
const confirmed = window.confirm(
|
|
244
|
+
`Are you sure you want to permanently delete all chat history for agent "${agentName}"?`,
|
|
245
|
+
);
|
|
246
|
+
if (!confirmed) return;
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
setLoading(true);
|
|
250
|
+
setError(null);
|
|
251
|
+
await $clearAgentChatHistory(agentName);
|
|
252
|
+
|
|
253
|
+
// After clearing, reload first page
|
|
254
|
+
setPage(1);
|
|
255
|
+
const response = await $fetchChatHistory({
|
|
256
|
+
page: 1,
|
|
257
|
+
pageSize,
|
|
258
|
+
agentName: agentName || undefined,
|
|
259
|
+
search: search || undefined,
|
|
260
|
+
sortBy,
|
|
261
|
+
sortOrder,
|
|
262
|
+
});
|
|
263
|
+
setItems(response.items);
|
|
264
|
+
setTotal(response.total);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
setError(err instanceof Error ? err.message : 'Failed to clear chat history');
|
|
267
|
+
} finally {
|
|
268
|
+
setLoading(false);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const isSortedBy = (field: ChatHistorySortField) => sortBy === field;
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<div className="container mx-auto px-4 py-8 space-y-6">
|
|
276
|
+
<div className="mt-20 mb-4 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
|
|
277
|
+
<div>
|
|
278
|
+
<h1 className="text-3xl text-gray-900 font-light">Chat history</h1>
|
|
279
|
+
<p className="mt-1 text-sm text-gray-500">
|
|
280
|
+
Inspect and manage all recorded chat messages across your agents.
|
|
281
|
+
</p>
|
|
282
|
+
</div>
|
|
283
|
+
<div className="text-sm text-gray-500 md:text-right">
|
|
284
|
+
<div className="text-xl font-semibold text-gray-900">
|
|
285
|
+
{total.toLocaleString()}
|
|
286
|
+
</div>
|
|
287
|
+
<div className="text-xs uppercase tracking-wide text-gray-400">
|
|
288
|
+
Total messages
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
</div>
|
|
292
|
+
<Card>
|
|
293
|
+
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
|
294
|
+
<form onSubmit={handleSearchSubmit} className="flex flex-col gap-2 md:flex-row md:items-end">
|
|
295
|
+
<div className="flex flex-col gap-1">
|
|
296
|
+
<label htmlFor="search" className="text-sm font-medium text-gray-700">
|
|
297
|
+
Search
|
|
298
|
+
</label>
|
|
299
|
+
<input
|
|
300
|
+
id="search"
|
|
301
|
+
type="text"
|
|
302
|
+
value={searchInput}
|
|
303
|
+
onChange={(event) => setSearchInput(event.target.value)}
|
|
304
|
+
placeholder="Search by agent name, URL or IP"
|
|
305
|
+
className="w-full md:w-72 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
306
|
+
/>
|
|
307
|
+
</div>
|
|
308
|
+
<button
|
|
309
|
+
type="submit"
|
|
310
|
+
className="mt-2 inline-flex items-center justify-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 md:mt-0 md:ml-3"
|
|
311
|
+
>
|
|
312
|
+
Apply
|
|
313
|
+
</button>
|
|
314
|
+
</form>
|
|
315
|
+
|
|
316
|
+
<div className="flex flex-col gap-2 md:flex-row md:items-end md:gap-4">
|
|
317
|
+
<div className="flex flex-col gap-1">
|
|
318
|
+
<label htmlFor="agentFilter" className="text-sm font-medium text-gray-700">
|
|
319
|
+
Agent filter
|
|
320
|
+
</label>
|
|
321
|
+
<select
|
|
322
|
+
id="agentFilter"
|
|
323
|
+
value={agentName}
|
|
324
|
+
onChange={handleAgentChange}
|
|
325
|
+
className="w-full md:w-64 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
326
|
+
>
|
|
327
|
+
<option value="">All agents</option>
|
|
328
|
+
{agents.map((agent) => (
|
|
329
|
+
<option key={agent.agentName} value={agent.agentName}>
|
|
330
|
+
{agent.fullname || agent.agentName}
|
|
331
|
+
</option>
|
|
332
|
+
))}
|
|
333
|
+
</select>
|
|
334
|
+
{agentsLoading && (
|
|
335
|
+
<span className="text-xs text-gray-400">Loading agents…</span>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div className="flex flex-col gap-1">
|
|
340
|
+
<label htmlFor="pageSize" className="text-sm font-medium text-gray-700">
|
|
341
|
+
Page size
|
|
342
|
+
</label>
|
|
343
|
+
<select
|
|
344
|
+
id="pageSize"
|
|
345
|
+
value={pageSize}
|
|
346
|
+
onChange={handlePageSizeChange}
|
|
347
|
+
className="w-28 px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
348
|
+
>
|
|
349
|
+
<option value={10}>10</option>
|
|
350
|
+
<option value={20}>20</option>
|
|
351
|
+
<option value={50}>50</option>
|
|
352
|
+
<option value={100}>100</option>
|
|
353
|
+
</select>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
{agentName && (
|
|
359
|
+
<div className="mt-4 flex items-center justify-between gap-4 rounded-md border border-amber-200 bg-amber-50 px-4 py-3">
|
|
360
|
+
<p className="text-sm text-amber-800">
|
|
361
|
+
Showing chat history for agent{' '}
|
|
362
|
+
<span className="font-semibold break-all">{agentName}</span>.
|
|
363
|
+
</p>
|
|
364
|
+
<button
|
|
365
|
+
type="button"
|
|
366
|
+
onClick={handleClearAgentHistory}
|
|
367
|
+
className="inline-flex items-center justify-center rounded-md border border-red-300 bg-white px-3 py-1.5 text-xs font-medium text-red-700 hover:bg-red-50"
|
|
368
|
+
>
|
|
369
|
+
Clear history for this agent
|
|
370
|
+
</button>
|
|
371
|
+
</div>
|
|
372
|
+
)}
|
|
373
|
+
</Card>
|
|
374
|
+
|
|
375
|
+
<Card>
|
|
376
|
+
<div className="flex items-center justify-between mb-4">
|
|
377
|
+
<h2 className="text-lg font-medium text-gray-900">
|
|
378
|
+
Messages ({total})
|
|
379
|
+
</h2>
|
|
380
|
+
</div>
|
|
381
|
+
{error && (
|
|
382
|
+
<div className="mb-4 rounded-md bg-red-50 px-4 py-3 text-sm text-red-800">
|
|
383
|
+
{error}
|
|
384
|
+
</div>
|
|
385
|
+
)}
|
|
386
|
+
|
|
387
|
+
{loading && items.length === 0 ? (
|
|
388
|
+
<div className="py-8 text-center text-gray-500">Loading chat history…</div>
|
|
389
|
+
) : items.length === 0 ? (
|
|
390
|
+
<div className="py-8 text-center text-gray-500">No chat history found.</div>
|
|
391
|
+
) : (
|
|
392
|
+
<div className="overflow-x-auto">
|
|
393
|
+
<table className="min-w-full divide-y divide-gray-200 text-sm">
|
|
394
|
+
<thead className="bg-gray-50">
|
|
395
|
+
<tr>
|
|
396
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">
|
|
397
|
+
<button
|
|
398
|
+
type="button"
|
|
399
|
+
onClick={() => handleSortChange('createdAt')}
|
|
400
|
+
className="inline-flex items-center gap-1"
|
|
401
|
+
>
|
|
402
|
+
Time
|
|
403
|
+
{isSortedBy('createdAt') && (
|
|
404
|
+
<span>{sortOrder === 'asc' ? '▲' : '▼'}</span>
|
|
405
|
+
)}
|
|
406
|
+
</button>
|
|
407
|
+
</th>
|
|
408
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">
|
|
409
|
+
<button
|
|
410
|
+
type="button"
|
|
411
|
+
onClick={() => handleSortChange('agentName')}
|
|
412
|
+
className="inline-flex items-center gap-1"
|
|
413
|
+
>
|
|
414
|
+
Agent
|
|
415
|
+
{isSortedBy('agentName') && (
|
|
416
|
+
<span>{sortOrder === 'asc' ? '▲' : '▼'}</span>
|
|
417
|
+
)}
|
|
418
|
+
</button>
|
|
419
|
+
</th>
|
|
420
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">Role</th>
|
|
421
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">Message</th>
|
|
422
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">URL</th>
|
|
423
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">IP</th>
|
|
424
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">Language</th>
|
|
425
|
+
<th className="px-4 py-3 text-left font-medium text-gray-500">Platform</th>
|
|
426
|
+
<th className="px-4 py-3 text-right font-medium text-gray-500">Actions</th>
|
|
427
|
+
</tr>
|
|
428
|
+
</thead>
|
|
429
|
+
<tbody className="divide-y divide-gray-200 bg-white">
|
|
430
|
+
{items.map((row) => (
|
|
431
|
+
<tr key={row.id}>
|
|
432
|
+
<td className="whitespace-nowrap px-4 py-3 text-gray-700">
|
|
433
|
+
{formatDate(row.createdAt)}
|
|
434
|
+
</td>
|
|
435
|
+
<td className="whitespace-nowrap px-4 py-3 text-gray-700">
|
|
436
|
+
{row.agentName}
|
|
437
|
+
</td>
|
|
438
|
+
<td className="whitespace-nowrap px-4 py-3 text-gray-700">
|
|
439
|
+
{getMessageRole(row.message)}
|
|
440
|
+
</td>
|
|
441
|
+
<td className="max-w-xs px-4 py-3 text-gray-700">
|
|
442
|
+
<div className="max-h-24 overflow-hidden overflow-ellipsis text-xs leading-snug">
|
|
443
|
+
{getMessagePreview(row.message)}
|
|
444
|
+
</div>
|
|
445
|
+
</td>
|
|
446
|
+
<td className="max-w-xs px-4 py-3 text-gray-500">
|
|
447
|
+
<div className="truncate text-xs">
|
|
448
|
+
{row.url || '-'}
|
|
449
|
+
</div>
|
|
450
|
+
</td>
|
|
451
|
+
<td className="whitespace-nowrap px-4 py-3 text-gray-500">
|
|
452
|
+
{row.ip || '-'}
|
|
453
|
+
</td>
|
|
454
|
+
<td className="whitespace-nowrap px-4 py-3 text-gray-500">
|
|
455
|
+
{row.language || '-'}
|
|
456
|
+
</td>
|
|
457
|
+
<td className="max-w-xs px-4 py-3 text-gray-500">
|
|
458
|
+
<div className="truncate text-xs">
|
|
459
|
+
{row.platform || '-'}
|
|
460
|
+
</div>
|
|
461
|
+
</td>
|
|
462
|
+
<td className="whitespace-nowrap px-4 py-3 text-right text-xs font-medium">
|
|
463
|
+
<button
|
|
464
|
+
type="button"
|
|
465
|
+
onClick={() => handleDeleteRow(row)}
|
|
466
|
+
className="text-red-600 hover:text-red-800"
|
|
467
|
+
>
|
|
468
|
+
Delete
|
|
469
|
+
</button>
|
|
470
|
+
</td>
|
|
471
|
+
</tr>
|
|
472
|
+
))}
|
|
473
|
+
</tbody>
|
|
474
|
+
</table>
|
|
475
|
+
</div>
|
|
476
|
+
)}
|
|
477
|
+
|
|
478
|
+
<div className="mt-4 flex flex-col items-center justify-between gap-3 border-t border-gray-100 pt-4 text-xs text-gray-600 md:flex-row">
|
|
479
|
+
<div>
|
|
480
|
+
{total > 0 ? (
|
|
481
|
+
<>
|
|
482
|
+
Showing{' '}
|
|
483
|
+
<span className="font-semibold">
|
|
484
|
+
{Math.min((page - 1) * pageSize + 1, total)}
|
|
485
|
+
</span>{' '}
|
|
486
|
+
–{' '}
|
|
487
|
+
<span className="font-semibold">
|
|
488
|
+
{Math.min(page * pageSize, total)}
|
|
489
|
+
</span>{' '}
|
|
490
|
+
of{' '}
|
|
491
|
+
<span className="font-semibold">
|
|
492
|
+
{total}
|
|
493
|
+
</span>{' '}
|
|
494
|
+
messages
|
|
495
|
+
</>
|
|
496
|
+
) : (
|
|
497
|
+
'No messages'
|
|
498
|
+
)}
|
|
499
|
+
</div>
|
|
500
|
+
<div className="flex items-center gap-2">
|
|
501
|
+
<button
|
|
502
|
+
type="button"
|
|
503
|
+
onClick={() => setPage((prev) => Math.max(1, prev - 1))}
|
|
504
|
+
disabled={page <= 1}
|
|
505
|
+
className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
|
|
506
|
+
>
|
|
507
|
+
Previous
|
|
508
|
+
</button>
|
|
509
|
+
<span>
|
|
510
|
+
Page{' '}
|
|
511
|
+
<span className="font-semibold">
|
|
512
|
+
{page}
|
|
513
|
+
</span>{' '}
|
|
514
|
+
of{' '}
|
|
515
|
+
<span className="font-semibold">
|
|
516
|
+
{totalPages}
|
|
517
|
+
</span>
|
|
518
|
+
</span>
|
|
519
|
+
<button
|
|
520
|
+
type="button"
|
|
521
|
+
onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
|
|
522
|
+
disabled={page >= totalPages}
|
|
523
|
+
className="inline-flex items-center justify-center rounded-md border border-gray-300 px-2 py-1 text-xs font-medium text-gray-700 disabled:cursor-not-allowed disabled:opacity-50"
|
|
524
|
+
>
|
|
525
|
+
Next
|
|
526
|
+
</button>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</Card>
|
|
530
|
+
</div>
|
|
531
|
+
);
|
|
532
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ForbiddenPage } from '../../../components/ForbiddenPage/ForbiddenPage';
|
|
2
|
+
import { isUserAdmin } from '../../../utils/isUserAdmin';
|
|
3
|
+
import { ChatHistoryClient } from './ChatHistoryClient';
|
|
4
|
+
|
|
5
|
+
type AdminChatHistoryPageProps = {
|
|
6
|
+
searchParams?: Promise<{
|
|
7
|
+
agentName?: string;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default async function AdminChatHistoryPage({ searchParams }: AdminChatHistoryPageProps) {
|
|
12
|
+
const isAdmin = await isUserAdmin();
|
|
13
|
+
|
|
14
|
+
if (!isAdmin) {
|
|
15
|
+
return <ForbiddenPage />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const initialAgentName = (await searchParams)?.agentName || undefined;
|
|
19
|
+
|
|
20
|
+
return <ChatHistoryClient initialAgentName={initialAgentName} />;
|
|
21
|
+
}
|