apteva 0.4.18 → 0.4.19
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/dist/ActivityPage.9a1qg4bp.js +3 -0
- package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
- package/dist/App.1nmg2h01.js +4 -0
- package/dist/App.5qw2dtxs.js +4 -0
- package/dist/App.6nc5acvk.js +4 -0
- package/dist/{App.nps62kvt.js → App.7vzbaz56.js} +3 -3
- package/dist/App.8rfz30p1.js +4 -0
- package/dist/App.amwp54wf.js +4 -0
- package/dist/App.e4202qb4.js +267 -0
- package/dist/App.errxz2q4.js +4 -0
- package/dist/App.f8qsyhpr.js +4 -0
- package/dist/App.g8vq68n0.js +20 -0
- package/dist/App.kfyrnznw.js +13 -0
- package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
- package/dist/{App.np463xvy.js → App.p93mmyqw.js} +3 -3
- package/dist/App.qmg33p02.js +4 -0
- package/dist/{App.nft7h9jt.js → App.sdsc0258.js} +3 -3
- package/dist/ConnectionsPage.7zqba1r0.js +3 -0
- package/dist/McpPage.kf2g327t.js +3 -0
- package/dist/SettingsPage.472c15ep.js +3 -0
- package/dist/SkillsPage.xdxnh68a.js +3 -0
- package/dist/TasksPage.7g0b8xwc.js +3 -0
- package/dist/TelemetryPage.pr7rbz4r.js +3 -0
- package/dist/TestsPage.zhc6rqjm.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -4
- package/src/channels/index.ts +40 -0
- package/src/channels/telegram.ts +306 -0
- package/src/db.ts +180 -0
- package/src/integrations/agentdojo.ts +1 -1
- package/src/mcp-handler.ts +31 -24
- package/src/providers.ts +22 -9
- package/src/routes/api/channels.ts +182 -0
- package/src/routes/api/integrations.ts +13 -5
- package/src/routes/api/mcp.ts +27 -9
- package/src/routes/api/telemetry.ts +30 -0
- package/src/routes/api/triggers.ts +22 -2
- package/src/routes/api.ts +3 -1
- package/src/server.ts +39 -4
- package/src/triggers/agentdojo.ts +23 -18
- package/src/tui/AgentList.tsx +145 -0
- package/src/tui/App.tsx +102 -0
- package/src/tui/Login.tsx +104 -0
- package/src/tui/api.ts +72 -0
- package/src/tui/index.tsx +7 -0
- package/src/web/App.tsx +1 -1
- package/src/web/components/agents/AgentPanel.tsx +4 -37
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/connections/OverviewTab.tsx +22 -68
- package/src/web/components/connections/TriggersTab.tsx +549 -70
- package/src/web/components/layout/Header.tsx +196 -4
- package/src/web/components/settings/SettingsPage.tsx +269 -1
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/dist/ActivityPage.yv28a2vj.js +0 -3
- package/dist/ApiDocsPage.4ccwjjbk.js +0 -4
- package/dist/App.155wke5v.js +0 -4
- package/dist/App.2e19nvn4.js +0 -13
- package/dist/App.2ye1b5n0.js +0 -4
- package/dist/App.4da4ycbe.js +0 -4
- package/dist/App.b6wtzd1j.js +0 -4
- package/dist/App.fjrh28tf.js +0 -4
- package/dist/App.htc36cy8.js +0 -4
- package/dist/App.me6reaa6.js +0 -4
- package/dist/App.n5q6p960.js +0 -4
- package/dist/App.q8ws33cc.js +0 -181
- package/dist/App.tb0y0jmt.js +0 -40
- package/dist/ConnectionsPage.52evzrp7.js +0 -3
- package/dist/McpPage.bjqrp0n2.js +0 -3
- package/dist/SettingsPage.es76hnj2.js +0 -3
- package/dist/SkillsPage.06h8yf0h.js +0 -3
- package/dist/TasksPage.99df66mk.js +0 -3
- package/dist/TelemetryPage.bmdnxhq7.js +0 -3
- package/dist/TestsPage.denxrg8c.js +0 -3
|
@@ -1,18 +1,128 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { useTelemetryContext, useAuth, useProjects } from "../../context";
|
|
3
|
-
import { MenuIcon, ChevronDownIcon } from "../common/Icons";
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
+
import { useTelemetryContext, useAuth, useAuthHeaders, useProjects, useNotificationChange } from "../../context";
|
|
3
|
+
import { MenuIcon, ChevronDownIcon, BellIcon } from "../common/Icons";
|
|
4
4
|
import { MetaAgentButton } from "../meta-agent/MetaAgent";
|
|
5
5
|
|
|
6
|
+
interface Notification {
|
|
7
|
+
id: string;
|
|
8
|
+
agent_id: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
category: string;
|
|
11
|
+
type: string;
|
|
12
|
+
level: string;
|
|
13
|
+
error: string | null;
|
|
14
|
+
data: Record<string, unknown> | null;
|
|
15
|
+
seen?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
interface HeaderProps {
|
|
7
19
|
onMenuClick?: () => void;
|
|
20
|
+
agents?: Array<{ id: string; name: string; projectId: string | null }>;
|
|
8
21
|
}
|
|
9
22
|
|
|
10
|
-
export function Header({ onMenuClick }: HeaderProps) {
|
|
23
|
+
export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
11
24
|
const { connected } = useTelemetryContext();
|
|
12
25
|
const { user, logout } = useAuth();
|
|
26
|
+
const authHeaders = useAuthHeaders();
|
|
13
27
|
const { projects, currentProjectId, currentProject, setCurrentProjectId, unassignedCount, projectsEnabled } = useProjects();
|
|
14
28
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
|
15
29
|
const [showProjectMenu, setShowProjectMenu] = useState(false);
|
|
30
|
+
const [showNotifications, setShowNotifications] = useState(false);
|
|
31
|
+
const [unseenCount, setUnseenCount] = useState(0);
|
|
32
|
+
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
33
|
+
const notificationChange = useNotificationChange();
|
|
34
|
+
const { events } = useTelemetryContext();
|
|
35
|
+
const { accessToken } = useAuth();
|
|
36
|
+
const fetchedOnce = useRef(false);
|
|
37
|
+
|
|
38
|
+
const agentNames = React.useMemo(() => {
|
|
39
|
+
const map: Record<string, string> = {};
|
|
40
|
+
for (const a of agents) map[a.id] = a.name;
|
|
41
|
+
return map;
|
|
42
|
+
}, [agents]);
|
|
43
|
+
|
|
44
|
+
// Set of agent IDs matching the current project filter
|
|
45
|
+
const projectAgentIds = React.useMemo(() => {
|
|
46
|
+
if (!projectsEnabled || currentProjectId === null) return null; // null = show all
|
|
47
|
+
if (currentProjectId === "unassigned") return new Set(agents.filter(a => !a.projectId).map(a => a.id));
|
|
48
|
+
return new Set(agents.filter(a => a.projectId === currentProjectId).map(a => a.id));
|
|
49
|
+
}, [agents, currentProjectId, projectsEnabled]);
|
|
50
|
+
|
|
51
|
+
// Fetch initial unseen count once
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (fetchedOnce.current || !accessToken) return;
|
|
54
|
+
fetchedOnce.current = true;
|
|
55
|
+
fetch("/api/notifications/count", { headers: { Authorization: `Bearer ${accessToken}` } })
|
|
56
|
+
.then(r => r.json())
|
|
57
|
+
.then(d => setUnseenCount(d.count || 0))
|
|
58
|
+
.catch(() => {});
|
|
59
|
+
}, [accessToken]);
|
|
60
|
+
|
|
61
|
+
// Bump count live from SSE (only if event matches current project)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (notificationChange === 0) return;
|
|
64
|
+
const latest = events.find(e =>
|
|
65
|
+
e.level === "error" || e.category === "ERROR" || (e.category === "system" && e.type === "agent_stopped")
|
|
66
|
+
);
|
|
67
|
+
if (latest && (!projectAgentIds || projectAgentIds.has(latest.agent_id))) {
|
|
68
|
+
setUnseenCount(c => c + 1);
|
|
69
|
+
}
|
|
70
|
+
}, [notificationChange]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
71
|
+
|
|
72
|
+
// Re-count when project filter changes (from cached notifications or reset)
|
|
73
|
+
const prevProjectRef = useRef(currentProjectId);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
if (prevProjectRef.current === currentProjectId) return;
|
|
76
|
+
prevProjectRef.current = currentProjectId;
|
|
77
|
+
// Refetch count with project filter
|
|
78
|
+
if (!accessToken) return;
|
|
79
|
+
fetch("/api/notifications/count", { headers: { Authorization: `Bearer ${accessToken}` } })
|
|
80
|
+
.then(r => r.json())
|
|
81
|
+
.then(d => {
|
|
82
|
+
// API returns total unseen — client filters by project
|
|
83
|
+
if (!projectAgentIds) {
|
|
84
|
+
setUnseenCount(d.count || 0);
|
|
85
|
+
} else {
|
|
86
|
+
// We need to fetch actual notifications to filter by project
|
|
87
|
+
fetch("/api/notifications?limit=200", { headers: { Authorization: `Bearer ${accessToken}` } })
|
|
88
|
+
.then(r => r.json())
|
|
89
|
+
.then(nd => {
|
|
90
|
+
const unseen = (nd.notifications || []).filter(
|
|
91
|
+
(n: Notification) => !n.seen && projectAgentIds.has(n.agent_id)
|
|
92
|
+
);
|
|
93
|
+
setUnseenCount(unseen.length);
|
|
94
|
+
})
|
|
95
|
+
.catch(() => {});
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
.catch(() => {});
|
|
99
|
+
}, [currentProjectId, accessToken, projectAgentIds]);
|
|
100
|
+
|
|
101
|
+
const openNotifications = useCallback(async () => {
|
|
102
|
+
setShowNotifications(prev => !prev);
|
|
103
|
+
if (!showNotifications) {
|
|
104
|
+
try {
|
|
105
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
106
|
+
if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
|
|
107
|
+
const res = await fetch("/api/notifications?limit=50", { headers });
|
|
108
|
+
const data = await res.json();
|
|
109
|
+
let items: Notification[] = data.notifications || [];
|
|
110
|
+
if (projectAgentIds) items = items.filter(n => projectAgentIds.has(n.agent_id));
|
|
111
|
+
setNotifications(items);
|
|
112
|
+
// Mark all as seen
|
|
113
|
+
if (unseenCount > 0) {
|
|
114
|
+
await fetch("/api/notifications/mark-seen", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers,
|
|
117
|
+
body: JSON.stringify({ all: true }),
|
|
118
|
+
});
|
|
119
|
+
setUnseenCount(0);
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Ignore
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [showNotifications, unseenCount, accessToken, projectAgentIds]);
|
|
16
126
|
|
|
17
127
|
const handleLogout = async () => {
|
|
18
128
|
await logout();
|
|
@@ -123,6 +233,77 @@ export function Header({ onMenuClick }: HeaderProps) {
|
|
|
123
233
|
{connected ? "Live" : "Offline"}
|
|
124
234
|
</span>
|
|
125
235
|
</div>
|
|
236
|
+
{/* Notification Bell */}
|
|
237
|
+
<div className="relative">
|
|
238
|
+
<button
|
|
239
|
+
onClick={openNotifications}
|
|
240
|
+
className="relative p-2 text-[#666] hover:text-[#e0e0e0] transition rounded hover:bg-[#1a1a1a]"
|
|
241
|
+
>
|
|
242
|
+
<BellIcon className="w-5 h-5" />
|
|
243
|
+
{unseenCount > 0 && (
|
|
244
|
+
<span
|
|
245
|
+
className="absolute flex items-center justify-center bg-red-500 text-white font-bold rounded-full pointer-events-none"
|
|
246
|
+
style={{
|
|
247
|
+
top: 2,
|
|
248
|
+
right: 2,
|
|
249
|
+
fontSize: 9,
|
|
250
|
+
lineHeight: 1,
|
|
251
|
+
minWidth: 16,
|
|
252
|
+
height: 16,
|
|
253
|
+
padding: "0 4px",
|
|
254
|
+
}}
|
|
255
|
+
>
|
|
256
|
+
{unseenCount > 99 ? "99+" : unseenCount}
|
|
257
|
+
</span>
|
|
258
|
+
)}
|
|
259
|
+
</button>
|
|
260
|
+
{showNotifications && (
|
|
261
|
+
<>
|
|
262
|
+
<div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
|
|
263
|
+
<div className="absolute right-0 top-full mt-1 w-80 bg-[#111] border border-[#222] rounded-lg shadow-xl z-50 max-h-96 overflow-y-auto">
|
|
264
|
+
<div className="px-4 py-3 border-b border-[#222] flex items-center justify-between">
|
|
265
|
+
<span className="text-sm font-medium">Notifications</span>
|
|
266
|
+
{notifications.length > 0 && (
|
|
267
|
+
<span className="text-xs text-[#666]">{notifications.length} recent</span>
|
|
268
|
+
)}
|
|
269
|
+
</div>
|
|
270
|
+
{notifications.length === 0 ? (
|
|
271
|
+
<div className="px-4 py-8 text-center text-sm text-[#666]">
|
|
272
|
+
No notifications
|
|
273
|
+
</div>
|
|
274
|
+
) : (
|
|
275
|
+
<div className="py-1">
|
|
276
|
+
{notifications.map(n => (
|
|
277
|
+
<div key={n.id} className={`px-4 py-3 hover:bg-[#1a1a1a] transition border-b border-[#1a1a1a] ${!n.seen ? "bg-[#0f0f0f]" : ""}`}>
|
|
278
|
+
<div className="flex items-center gap-2 mb-1">
|
|
279
|
+
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
|
280
|
+
!n.seen
|
|
281
|
+
? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "bg-[#f97316]")
|
|
282
|
+
: "bg-[#333]"
|
|
283
|
+
}`} />
|
|
284
|
+
<span className={`text-xs font-medium truncate ${!n.seen ? "text-[#ccc]" : "text-[#666]"}`}>
|
|
285
|
+
{n.category === "system" && n.type === "agent_stopped" ? "Agent Stopped" :
|
|
286
|
+
n.category === "ERROR" ? "Error" :
|
|
287
|
+
`${n.category} / ${n.type}`}
|
|
288
|
+
</span>
|
|
289
|
+
<span className="text-[10px] text-[#555] ml-auto flex-shrink-0">
|
|
290
|
+
{formatNotifTime(n.timestamp)}
|
|
291
|
+
</span>
|
|
292
|
+
</div>
|
|
293
|
+
<div className={`text-xs truncate ${!n.seen ? "text-[#aaa]" : "text-[#666]"}`}>
|
|
294
|
+
{n.error || (n.data as any)?.message || (n.data as any)?.error || `${n.type} event`}
|
|
295
|
+
</div>
|
|
296
|
+
<div className="text-[10px] text-[#555] mt-1">
|
|
297
|
+
{agentNames[n.agent_id] || n.agent_id.slice(0, 8)}
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
))}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</div>
|
|
304
|
+
</>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
126
307
|
<MetaAgentButton />
|
|
127
308
|
{user && (
|
|
128
309
|
<div className="relative">
|
|
@@ -156,3 +337,14 @@ export function Header({ onMenuClick }: HeaderProps) {
|
|
|
156
337
|
</header>
|
|
157
338
|
);
|
|
158
339
|
}
|
|
340
|
+
|
|
341
|
+
function formatNotifTime(timestamp: string): string {
|
|
342
|
+
const diff = Date.now() - new Date(timestamp).getTime();
|
|
343
|
+
const mins = Math.floor(diff / 60000);
|
|
344
|
+
if (mins < 1) return "just now";
|
|
345
|
+
if (mins < 60) return `${mins}m ago`;
|
|
346
|
+
const hours = Math.floor(mins / 60);
|
|
347
|
+
if (hours < 24) return `${hours}h ago`;
|
|
348
|
+
const days = Math.floor(hours / 24);
|
|
349
|
+
return `${days}d ago`;
|
|
350
|
+
}
|
|
@@ -5,7 +5,7 @@ import { Select } from "../common/Select";
|
|
|
5
5
|
import { useProjects, useAuth, type Project } from "../../context";
|
|
6
6
|
import type { Provider } from "../../types";
|
|
7
7
|
|
|
8
|
-
type SettingsTab = "general" | "providers" | "projects" | "api-keys" | "account" | "updates" | "data";
|
|
8
|
+
type SettingsTab = "general" | "providers" | "projects" | "channels" | "api-keys" | "account" | "updates" | "data";
|
|
9
9
|
|
|
10
10
|
export function SettingsPage() {
|
|
11
11
|
const { projectsEnabled } = useProjects();
|
|
@@ -15,6 +15,7 @@ export function SettingsPage() {
|
|
|
15
15
|
{ key: "general", label: "General" },
|
|
16
16
|
{ key: "providers", label: "Providers" },
|
|
17
17
|
...(projectsEnabled ? [{ key: "projects" as SettingsTab, label: "Projects" }] : []),
|
|
18
|
+
{ key: "channels", label: "Channels" },
|
|
18
19
|
{ key: "api-keys", label: "API Keys" },
|
|
19
20
|
{ key: "account", label: "Account" },
|
|
20
21
|
{ key: "updates", label: "Updates" },
|
|
@@ -62,6 +63,7 @@ export function SettingsPage() {
|
|
|
62
63
|
{activeTab === "general" && <GeneralSettings />}
|
|
63
64
|
{activeTab === "providers" && <ProvidersSettings />}
|
|
64
65
|
{activeTab === "projects" && projectsEnabled && <ProjectsSettings />}
|
|
66
|
+
{activeTab === "channels" && <ChannelsSettings />}
|
|
65
67
|
{activeTab === "api-keys" && <ApiKeysSettings />}
|
|
66
68
|
{activeTab === "account" && <AccountSettings />}
|
|
67
69
|
{activeTab === "updates" && <UpdatesSettings />}
|
|
@@ -1884,3 +1886,269 @@ function DataSettings() {
|
|
|
1884
1886
|
</>
|
|
1885
1887
|
);
|
|
1886
1888
|
}
|
|
1889
|
+
|
|
1890
|
+
// --- Channels Settings ---
|
|
1891
|
+
|
|
1892
|
+
interface ChannelInfo {
|
|
1893
|
+
id: string;
|
|
1894
|
+
type: string;
|
|
1895
|
+
name: string;
|
|
1896
|
+
agent_id: string;
|
|
1897
|
+
status: "stopped" | "running" | "error";
|
|
1898
|
+
error: string | null;
|
|
1899
|
+
created_at: string;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
interface AgentOption {
|
|
1903
|
+
id: string;
|
|
1904
|
+
name: string;
|
|
1905
|
+
status: string;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function ChannelsSettings() {
|
|
1909
|
+
const { authFetch } = useAuth();
|
|
1910
|
+
const [channels, setChannels] = useState<ChannelInfo[]>([]);
|
|
1911
|
+
const [agents, setAgents] = useState<AgentOption[]>([]);
|
|
1912
|
+
const [loading, setLoading] = useState(true);
|
|
1913
|
+
const [showForm, setShowForm] = useState(false);
|
|
1914
|
+
const [formData, setFormData] = useState({ name: "", agent_id: "", botToken: "" });
|
|
1915
|
+
const [creating, setCreating] = useState(false);
|
|
1916
|
+
const [error, setError] = useState<string | null>(null);
|
|
1917
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
1918
|
+
|
|
1919
|
+
const fetchChannels = async () => {
|
|
1920
|
+
try {
|
|
1921
|
+
const res = await authFetch("/api/channels");
|
|
1922
|
+
const data = await res.json();
|
|
1923
|
+
setChannels(data.channels || []);
|
|
1924
|
+
} catch {
|
|
1925
|
+
// Ignore
|
|
1926
|
+
} finally {
|
|
1927
|
+
setLoading(false);
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
|
|
1931
|
+
const fetchAgents = async () => {
|
|
1932
|
+
try {
|
|
1933
|
+
const res = await authFetch("/api/agents");
|
|
1934
|
+
const data = await res.json();
|
|
1935
|
+
setAgents((data.agents || []).map((a: any) => ({ id: a.id, name: a.name, status: a.status })));
|
|
1936
|
+
} catch {
|
|
1937
|
+
// Ignore
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
useEffect(() => {
|
|
1942
|
+
fetchChannels();
|
|
1943
|
+
fetchAgents();
|
|
1944
|
+
}, []);
|
|
1945
|
+
|
|
1946
|
+
const createChannel = async () => {
|
|
1947
|
+
if (!formData.name || !formData.agent_id || !formData.botToken) return;
|
|
1948
|
+
setCreating(true);
|
|
1949
|
+
setError(null);
|
|
1950
|
+
|
|
1951
|
+
try {
|
|
1952
|
+
const res = await authFetch("/api/channels", {
|
|
1953
|
+
method: "POST",
|
|
1954
|
+
headers: { "Content-Type": "application/json" },
|
|
1955
|
+
body: JSON.stringify({
|
|
1956
|
+
type: "telegram",
|
|
1957
|
+
name: formData.name,
|
|
1958
|
+
agent_id: formData.agent_id,
|
|
1959
|
+
config: { botToken: formData.botToken },
|
|
1960
|
+
}),
|
|
1961
|
+
});
|
|
1962
|
+
|
|
1963
|
+
if (!res.ok) {
|
|
1964
|
+
const data = await res.json();
|
|
1965
|
+
setError(data.error || "Failed to create channel");
|
|
1966
|
+
} else {
|
|
1967
|
+
setFormData({ name: "", agent_id: "", botToken: "" });
|
|
1968
|
+
setShowForm(false);
|
|
1969
|
+
await fetchChannels();
|
|
1970
|
+
}
|
|
1971
|
+
} catch (err: any) {
|
|
1972
|
+
setError(err.message);
|
|
1973
|
+
} finally {
|
|
1974
|
+
setCreating(false);
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
|
|
1978
|
+
const toggleChannel = async (channel: ChannelInfo) => {
|
|
1979
|
+
const action = channel.status === "running" ? "stop" : "start";
|
|
1980
|
+
try {
|
|
1981
|
+
const res = await authFetch(`/api/channels/${channel.id}/${action}`, { method: "POST" });
|
|
1982
|
+
if (!res.ok) {
|
|
1983
|
+
const data = await res.json();
|
|
1984
|
+
setError(data.error || `Failed to ${action} channel`);
|
|
1985
|
+
}
|
|
1986
|
+
await fetchChannels();
|
|
1987
|
+
} catch {
|
|
1988
|
+
setError(`Failed to ${action} channel`);
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
const deleteChannel = async (channel: ChannelInfo) => {
|
|
1993
|
+
const confirmed = await confirm(`Delete channel "${channel.name}"?`, {
|
|
1994
|
+
confirmText: "Delete",
|
|
1995
|
+
title: "Delete Channel",
|
|
1996
|
+
});
|
|
1997
|
+
if (!confirmed) return;
|
|
1998
|
+
|
|
1999
|
+
try {
|
|
2000
|
+
await authFetch(`/api/channels/${channel.id}`, { method: "DELETE" });
|
|
2001
|
+
await fetchChannels();
|
|
2002
|
+
} catch {
|
|
2003
|
+
// Ignore
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
|
|
2007
|
+
const statusColors: Record<string, string> = {
|
|
2008
|
+
running: "bg-green-500/20 text-green-400",
|
|
2009
|
+
stopped: "bg-[#333] text-[#666]",
|
|
2010
|
+
error: "bg-red-500/20 text-red-400",
|
|
2011
|
+
};
|
|
2012
|
+
|
|
2013
|
+
const getAgentName = (agentId: string) => {
|
|
2014
|
+
return agents.find(a => a.id === agentId)?.name || agentId;
|
|
2015
|
+
};
|
|
2016
|
+
|
|
2017
|
+
return (
|
|
2018
|
+
<>
|
|
2019
|
+
{ConfirmDialog}
|
|
2020
|
+
<div className="max-w-2xl">
|
|
2021
|
+
<div className="flex items-center justify-between mb-6">
|
|
2022
|
+
<div>
|
|
2023
|
+
<h2 className="text-xl font-semibold mb-1">Channels</h2>
|
|
2024
|
+
<p className="text-sm text-[#666]">Connect agents to external messaging platforms</p>
|
|
2025
|
+
</div>
|
|
2026
|
+
<button
|
|
2027
|
+
onClick={() => setShowForm(!showForm)}
|
|
2028
|
+
className="flex items-center gap-2 bg-[#f97316] hover:bg-[#fb923c] text-black px-3 py-1.5 rounded text-sm font-medium transition"
|
|
2029
|
+
>
|
|
2030
|
+
<PlusIcon /> Add Channel
|
|
2031
|
+
</button>
|
|
2032
|
+
</div>
|
|
2033
|
+
|
|
2034
|
+
{error && (
|
|
2035
|
+
<div className="mb-4 bg-red-500/10 text-red-400 border border-red-500/30 px-3 py-2 rounded text-sm flex items-center justify-between">
|
|
2036
|
+
<span>{error}</span>
|
|
2037
|
+
<button onClick={() => setError(null)} className="text-red-400 hover:text-red-300 ml-2">
|
|
2038
|
+
<CloseIcon />
|
|
2039
|
+
</button>
|
|
2040
|
+
</div>
|
|
2041
|
+
)}
|
|
2042
|
+
|
|
2043
|
+
{/* Create form */}
|
|
2044
|
+
{showForm && (
|
|
2045
|
+
<div className="mb-6 bg-[#111] border border-[#1a1a1a] rounded-lg p-4 space-y-3">
|
|
2046
|
+
<h3 className="text-sm font-medium text-[#888] mb-2">New Telegram Channel</h3>
|
|
2047
|
+
|
|
2048
|
+
<div>
|
|
2049
|
+
<label className="block text-xs text-[#666] mb-1">Channel Name</label>
|
|
2050
|
+
<input
|
|
2051
|
+
type="text"
|
|
2052
|
+
value={formData.name}
|
|
2053
|
+
onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
|
|
2054
|
+
placeholder="e.g. My Telegram Bot"
|
|
2055
|
+
className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
|
|
2056
|
+
/>
|
|
2057
|
+
</div>
|
|
2058
|
+
|
|
2059
|
+
<div>
|
|
2060
|
+
<label className="block text-xs text-[#666] mb-1">Agent</label>
|
|
2061
|
+
<Select
|
|
2062
|
+
value={formData.agent_id}
|
|
2063
|
+
options={agents.map(a => ({ value: a.id, label: a.name }))}
|
|
2064
|
+
onChange={value => setFormData(prev => ({ ...prev, agent_id: value }))}
|
|
2065
|
+
placeholder="Select an agent..."
|
|
2066
|
+
/>
|
|
2067
|
+
</div>
|
|
2068
|
+
|
|
2069
|
+
<div>
|
|
2070
|
+
<label className="block text-xs text-[#666] mb-1">Bot Token</label>
|
|
2071
|
+
<input
|
|
2072
|
+
type="password"
|
|
2073
|
+
value={formData.botToken}
|
|
2074
|
+
onChange={e => setFormData(prev => ({ ...prev, botToken: e.target.value }))}
|
|
2075
|
+
placeholder="From @BotFather on Telegram"
|
|
2076
|
+
className="w-full bg-[#0a0a0a] border border-[#222] rounded px-3 py-2 text-sm focus:outline-none focus:border-[#f97316]"
|
|
2077
|
+
/>
|
|
2078
|
+
<p className="text-xs text-[#555] mt-1">
|
|
2079
|
+
Create a bot via <a href="https://t.me/BotFather" target="_blank" className="text-[#f97316] hover:underline">@BotFather</a> on Telegram to get a token.
|
|
2080
|
+
</p>
|
|
2081
|
+
</div>
|
|
2082
|
+
|
|
2083
|
+
<div className="flex gap-2 pt-1">
|
|
2084
|
+
<button
|
|
2085
|
+
onClick={createChannel}
|
|
2086
|
+
disabled={creating || !formData.name || !formData.agent_id || !formData.botToken}
|
|
2087
|
+
className="bg-[#f97316] hover:bg-[#fb923c] disabled:opacity-50 text-black px-4 py-1.5 rounded text-sm font-medium transition"
|
|
2088
|
+
>
|
|
2089
|
+
{creating ? "Creating..." : "Create"}
|
|
2090
|
+
</button>
|
|
2091
|
+
<button
|
|
2092
|
+
onClick={() => { setShowForm(false); setFormData({ name: "", agent_id: "", botToken: "" }); }}
|
|
2093
|
+
className="border border-[#333] hover:border-[#444] px-4 py-1.5 rounded text-sm transition"
|
|
2094
|
+
>
|
|
2095
|
+
Cancel
|
|
2096
|
+
</button>
|
|
2097
|
+
</div>
|
|
2098
|
+
</div>
|
|
2099
|
+
)}
|
|
2100
|
+
|
|
2101
|
+
{/* Channel list */}
|
|
2102
|
+
{loading ? (
|
|
2103
|
+
<p className="text-[#666] text-sm">Loading channels...</p>
|
|
2104
|
+
) : channels.length === 0 ? (
|
|
2105
|
+
<div className="text-center py-12 text-[#666]">
|
|
2106
|
+
<p className="text-lg mb-2">No channels configured</p>
|
|
2107
|
+
<p className="text-sm">Add a Telegram channel to let users message your agents directly.</p>
|
|
2108
|
+
</div>
|
|
2109
|
+
) : (
|
|
2110
|
+
<div className="space-y-3">
|
|
2111
|
+
{channels.map(channel => (
|
|
2112
|
+
<div key={channel.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
|
|
2113
|
+
<div className="flex items-start justify-between">
|
|
2114
|
+
<div className="flex-1 min-w-0">
|
|
2115
|
+
<div className="flex items-center gap-2 mb-1">
|
|
2116
|
+
<h3 className="font-medium">{channel.name}</h3>
|
|
2117
|
+
<span className={`px-2 py-0.5 rounded text-xs font-medium ${statusColors[channel.status] || statusColors.stopped}`}>
|
|
2118
|
+
{channel.status}
|
|
2119
|
+
</span>
|
|
2120
|
+
</div>
|
|
2121
|
+
<p className="text-sm text-[#666]">
|
|
2122
|
+
{channel.type === "telegram" ? "Telegram" : channel.type} → {getAgentName(channel.agent_id)}
|
|
2123
|
+
</p>
|
|
2124
|
+
{channel.status === "error" && channel.error && (
|
|
2125
|
+
<p className="text-xs text-red-400 mt-1">{channel.error}</p>
|
|
2126
|
+
)}
|
|
2127
|
+
</div>
|
|
2128
|
+
<div className="flex items-center gap-2 ml-4">
|
|
2129
|
+
<button
|
|
2130
|
+
onClick={() => toggleChannel(channel)}
|
|
2131
|
+
className={`px-3 py-1 rounded text-xs font-medium transition ${
|
|
2132
|
+
channel.status === "running"
|
|
2133
|
+
? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
|
|
2134
|
+
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
2135
|
+
}`}
|
|
2136
|
+
>
|
|
2137
|
+
{channel.status === "running" ? "Stop" : "Start"}
|
|
2138
|
+
</button>
|
|
2139
|
+
<button
|
|
2140
|
+
onClick={() => deleteChannel(channel)}
|
|
2141
|
+
className="text-[#666] hover:text-red-400 transition text-sm"
|
|
2142
|
+
>
|
|
2143
|
+
×
|
|
2144
|
+
</button>
|
|
2145
|
+
</div>
|
|
2146
|
+
</div>
|
|
2147
|
+
</div>
|
|
2148
|
+
))}
|
|
2149
|
+
</div>
|
|
2150
|
+
)}
|
|
2151
|
+
</div>
|
|
2152
|
+
</>
|
|
2153
|
+
);
|
|
2154
|
+
}
|
|
@@ -21,6 +21,7 @@ interface TelemetryContextValue {
|
|
|
21
21
|
activeAgents: Record<string, { type: string; expiresAt: number }>;
|
|
22
22
|
statusChangeCounter: number;
|
|
23
23
|
taskChangeCounter: number;
|
|
24
|
+
notificationCounter: number;
|
|
24
25
|
clearEvents: () => void;
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -35,6 +36,7 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
|
35
36
|
const [activeAgents, setActiveAgents] = useState<Record<string, { type: string; expiresAt: number }>>({});
|
|
36
37
|
const [statusChangeCounter, setStatusChangeCounter] = useState(0);
|
|
37
38
|
const [taskChangeCounter, setTaskChangeCounter] = useState(0);
|
|
39
|
+
const [notificationCounter, setNotificationCounter] = useState(0);
|
|
38
40
|
const eventSourceRef = useRef<EventSource | null>(null);
|
|
39
41
|
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
40
42
|
|
|
@@ -124,6 +126,11 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
|
124
126
|
if (data.some((e: TelemetryEvent) => e.category === "TASK" && (e.type === "task_created" || e.type === "task_updated" || e.type === "task_deleted"))) {
|
|
125
127
|
setTaskChangeCounter(c => c + 1);
|
|
126
128
|
}
|
|
129
|
+
|
|
130
|
+
// Detect notification-worthy events (errors, agent crashes)
|
|
131
|
+
if (data.some((e: TelemetryEvent) => e.level === "error" || e.category === "ERROR" || (e.category === "system" && e.type === "agent_stopped"))) {
|
|
132
|
+
setNotificationCounter(c => c + 1);
|
|
133
|
+
}
|
|
127
134
|
}
|
|
128
135
|
} catch {
|
|
129
136
|
// Ignore parse errors (likely keepalive or empty message)
|
|
@@ -169,7 +176,7 @@ export function TelemetryProvider({ children }: { children: React.ReactNode }) {
|
|
|
169
176
|
}, []);
|
|
170
177
|
|
|
171
178
|
return (
|
|
172
|
-
<TelemetryContext.Provider value={{ connected, events, lastActivityByAgent, activeAgents, statusChangeCounter, taskChangeCounter, clearEvents }}>
|
|
179
|
+
<TelemetryContext.Provider value={{ connected, events, lastActivityByAgent, activeAgents, statusChangeCounter, taskChangeCounter, notificationCounter, clearEvents }}>
|
|
173
180
|
{children}
|
|
174
181
|
</TelemetryContext.Provider>
|
|
175
182
|
);
|
|
@@ -248,3 +255,9 @@ export function useTaskChange(): number {
|
|
|
248
255
|
const { taskChangeCounter } = useTelemetryContext();
|
|
249
256
|
return taskChangeCounter;
|
|
250
257
|
}
|
|
258
|
+
|
|
259
|
+
// Hook to trigger notification badge refresh
|
|
260
|
+
export function useNotificationChange(): number {
|
|
261
|
+
const { notificationCounter } = useTelemetryContext();
|
|
262
|
+
return notificationCounter;
|
|
263
|
+
}
|
package/src/web/context/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { TelemetryProvider, useTelemetryContext, useTelemetry, useAgentActivity, useAgentStatusChange, useTaskChange } from "./TelemetryContext";
|
|
1
|
+
export { TelemetryProvider, useTelemetryContext, useTelemetry, useAgentActivity, useAgentStatusChange, useTaskChange, useNotificationChange } from "./TelemetryContext";
|
|
2
2
|
export type { TelemetryEvent } from "./TelemetryContext";
|
|
3
3
|
|
|
4
4
|
export { AuthProvider, useAuth, useAuthHeaders } from "./AuthContext";
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import{P as y,S as E,T as D,_ as l}from"./App.tb0y0jmt.js";var M=y(E(),1);var z=y(D(),1),V={get:"#61affe",post:"#49cc90",put:"#fca130",delete:"#f93e3e",patch:"#50e3c2"};function c({method:I,path:A,parameters:F,requestBody:L,authFetch:T}){let[U,R]=M.useState({}),[G,O]=M.useState(""),[_,k]=M.useState(null),[w,q]=M.useState(!1),[B,f]=M.useState(null);M.useEffect(()=>{if(L?.content?.["application/json"]?.schema){let W=L.content["application/json"].schema;if(W.example)O(JSON.stringify(W.example,null,2));else if(W.properties){let $={};for(let[N,Q]of Object.entries(W.properties))if(Q.example!==void 0)$[N]=Q.example;else if(Q.type==="string")$[N]="";else if(Q.type==="number"||Q.type==="integer")$[N]=0;else if(Q.type==="boolean")$[N]=!1;else if(Q.type==="array")$[N]=[];else if(Q.type==="object")$[N]={};O(JSON.stringify($,null,2))}}},[L]);let C=async()=>{q(!0),f(null),k(null);try{let W=A,$=[];for(let Z of F||[]){let H=U[Z.name]||"";if(Z.in==="path")W=W.replace(`{${Z.name}}`,encodeURIComponent(H));else if(Z.in==="query"&&H)$.push(`${Z.name}=${encodeURIComponent(H)}`)}if($.length>0)W+=`?${$.join("&")}`;let N={method:I.toUpperCase()};if(G&&["post","put","patch"].includes(I))N.headers={"Content-Type":"application/json"},N.body=G;let Q=await T(`/api${W}`,N),X;if(Q.headers.get("content-type")?.includes("application/json"))X=await Q.json();else X=await Q.text();k({status:Q.status,data:X})}catch(W){f(W.message||"Request failed")}finally{q(!1)}},b=F?.filter((W)=>W.in==="path")||[],K=F?.filter((W)=>W.in==="query")||[],S=["post","put","patch"].includes(I)&&L;return z.jsxDEV("div",{style:{marginTop:16,padding:16,background:"#0a0a14",borderRadius:6,border:"1px solid #222"},children:[z.jsxDEV("h4",{style:{fontSize:13,color:"#f97316",marginBottom:12,fontWeight:600},children:"Try it out"},void 0,!1,void 0,this),b.length>0&&z.jsxDEV("div",{style:{marginBottom:12},children:[z.jsxDEV("div",{style:{fontSize:11,color:"#666",marginBottom:6},children:"Path Parameters"},void 0,!1,void 0,this),b.map((W)=>z.jsxDEV("div",{style:{marginBottom:8},children:[z.jsxDEV("label",{style:{fontSize:12,color:"#888",display:"block",marginBottom:4},children:[W.name," ",W.required&&z.jsxDEV("span",{style:{color:"#f66"},children:"*"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("input",{type:"text",value:U[W.name]||"",onChange:($)=>R({...U,[W.name]:$.target.value}),placeholder:W.schema?.type||"string",style:{width:"100%",padding:"8px 12px",background:"#111",border:"1px solid #333",borderRadius:4,color:"#fff",fontSize:13,fontFamily:"monospace"}},void 0,!1,void 0,this)]},W.name,!0,void 0,this))]},void 0,!0,void 0,this),K.length>0&&z.jsxDEV("div",{style:{marginBottom:12},children:[z.jsxDEV("div",{style:{fontSize:11,color:"#666",marginBottom:6},children:"Query Parameters"},void 0,!1,void 0,this),K.map((W)=>z.jsxDEV("div",{style:{marginBottom:8},children:[z.jsxDEV("label",{style:{fontSize:12,color:"#888",display:"block",marginBottom:4},children:[W.name," ",W.required&&z.jsxDEV("span",{style:{color:"#f66"},children:"*"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("input",{type:"text",value:U[W.name]||"",onChange:($)=>R({...U,[W.name]:$.target.value}),placeholder:W.schema?.type||"string",style:{width:"100%",padding:"8px 12px",background:"#111",border:"1px solid #333",borderRadius:4,color:"#fff",fontSize:13,fontFamily:"monospace"}},void 0,!1,void 0,this)]},W.name,!0,void 0,this))]},void 0,!0,void 0,this),S&&z.jsxDEV("div",{style:{marginBottom:12},children:[z.jsxDEV("div",{style:{fontSize:11,color:"#666",marginBottom:6},children:"Request Body (JSON)"},void 0,!1,void 0,this),z.jsxDEV("textarea",{value:G,onChange:(W)=>O(W.target.value),rows:6,style:{width:"100%",padding:"8px 12px",background:"#111",border:"1px solid #333",borderRadius:4,color:"#fff",fontSize:12,fontFamily:"monospace",resize:"vertical"}},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("button",{onClick:C,disabled:w,style:{padding:"10px 20px",background:w?"#333":"#f97316",color:w?"#666":"#000",border:"none",borderRadius:4,cursor:w?"not-allowed":"pointer",fontSize:13,fontWeight:600},children:w?"Executing...":"Execute"},void 0,!1,void 0,this),B&&z.jsxDEV("div",{style:{marginTop:12,padding:12,background:"#2a1515",borderRadius:4,color:"#f66",fontSize:12},children:B},void 0,!1,void 0,this),_&&z.jsxDEV("div",{style:{marginTop:12},children:[z.jsxDEV("div",{style:{fontSize:11,color:"#666",marginBottom:6},children:["Response"," ",z.jsxDEV("span",{style:{color:_.status>=200&&_.status<300?"#49cc90":"#f66"},children:_.status},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("pre",{style:{padding:12,background:"#111",borderRadius:4,color:"#888",fontSize:11,fontFamily:"monospace",overflow:"auto",maxHeight:300,whiteSpace:"pre-wrap",wordBreak:"break-word"},children:typeof _.data==="string"?_.data:JSON.stringify(_.data,null,2)},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}function h(){let{authFetch:I}=l(),[A,F]=M.useState(null),[L,T]=M.useState(!0),[U,R]=M.useState(new Set),[G,O]=M.useState(null),[_,k]=M.useState(!1);M.useEffect(()=>{B()},[]);async function w(){if(!A)return;try{await navigator.clipboard.writeText(JSON.stringify(A,null,2)),k(!0),setTimeout(()=>k(!1),2000)}catch(N){console.error("Failed to copy:",N)}}function q(){if(!A)return;let N=new Blob([JSON.stringify(A,null,2)],{type:"application/json"}),Q=URL.createObjectURL(N),X=document.createElement("a");X.href=Q,X.download="apteva-openapi.json",X.click(),URL.revokeObjectURL(Q)}async function B(){try{let N=await I("/api/openapi");if(N.ok){let Q=await N.json();F(Q)}}catch(N){console.error("Failed to load OpenAPI spec:",N)}finally{T(!1)}}function f(N){R((Q)=>{let X=new Set(Q);if(X.has(N))X.delete(N);else X.add(N);return X})}function C(N,Q=0){if(!N)return"{}";if(Q>2)return"...";if(N.$ref)return N.$ref.split("/").pop()||"Object";if(N.type==="array")return`${C(N.items,Q+1)}[]`;if(N.type==="object"&&N.properties){let X=Object.entries(N.properties).slice(0,3).map(([Z,H])=>`${Z}: ${H.type||"any"}`).join(", "),Y=Object.keys(N.properties).length>3?", ...":"";return`{ ${X}${Y} }`}return N.type||"any"}if(L)return z.jsxDEV("div",{style:{padding:24},children:z.jsxDEV("p",{style:{color:"#888"},children:"Loading API documentation..."},void 0,!1,void 0,this)},void 0,!1,void 0,this);if(!A)return z.jsxDEV("div",{style:{padding:24},children:z.jsxDEV("p",{style:{color:"#f66"},children:"Failed to load API documentation"},void 0,!1,void 0,this)},void 0,!1,void 0,this);let b=A.tags||[],K=Object.entries(A.paths);function S(N){let Q=new Set;function X(Y){if(!Y)return;if(typeof Y==="object"){if(Y.$ref){let Z=Y.$ref.split("/").pop();if(Z)Q.add(Z)}for(let Z of Object.values(Y))X(Z)}}return X(N.requestBody),X(N.responses),Q}function W(){if(!G||!A.components?.schemas)return Object.keys(A.components?.schemas||{});let N=new Set;for(let[Q,X]of $)for(let[Y,Z]of Object.entries(X)){if(!["get","post","put","delete","patch"].includes(Y))continue;if(Z.tags?.includes(G))S(Z).forEach((g)=>N.add(g))}return Array.from(N)}let $=G?K.filter(([N,Q])=>Object.values(Q).some((X)=>X.tags?.includes(G))):K;return z.jsxDEV("div",{style:{padding:24,maxWidth:1000,height:"100%",overflowY:"auto"},children:[z.jsxDEV("div",{style:{marginBottom:24},children:[z.jsxDEV("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"flex-start",marginBottom:8},children:[z.jsxDEV("h1",{style:{fontSize:24,fontWeight:600},children:A.info.title},void 0,!1,void 0,this),z.jsxDEV("div",{style:{display:"flex",gap:8},children:[z.jsxDEV("button",{onClick:w,style:{padding:"8px 16px",borderRadius:4,border:"1px solid #333",background:_?"#49cc90":"#1a1a2e",color:_?"#000":"#fff",cursor:"pointer",fontSize:12,fontFamily:"inherit"},children:_?"Copied!":"Copy JSON"},void 0,!1,void 0,this),z.jsxDEV("button",{onClick:q,style:{padding:"8px 16px",borderRadius:4,border:"1px solid #333",background:"#1a1a2e",color:"#fff",cursor:"pointer",fontSize:12,fontFamily:"inherit"},children:"Download"},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("p",{style:{color:"#888",marginBottom:8},children:A.info.description.split(`
|
|
2
|
-
`)[0]},void 0,!1,void 0,this),z.jsxDEV("p",{style:{color:"#666",fontSize:12},children:["Version: ",A.info.version]},void 0,!0,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("div",{style:{background:"#1a1a2e",padding:12,borderRadius:6,marginBottom:24,fontFamily:"monospace"},children:[z.jsxDEV("span",{style:{color:"#888"},children:"Base URL: "},void 0,!1,void 0,this),z.jsxDEV("span",{style:{color:"#61affe"},children:[window.location.origin,"/api"]},void 0,!0,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("div",{style:{marginBottom:24,display:"flex",flexWrap:"wrap",gap:8},children:[z.jsxDEV("button",{onClick:()=>O(null),style:{padding:"6px 12px",borderRadius:4,border:"1px solid #333",background:G===null?"#333":"transparent",color:G===null?"#fff":"#888",cursor:"pointer",fontSize:12},children:"All"},void 0,!1,void 0,this),b.map((N)=>z.jsxDEV("button",{onClick:()=>O(N.name),style:{padding:"6px 12px",borderRadius:4,border:"1px solid #333",background:G===N.name?"#333":"transparent",color:G===N.name?"#fff":"#888",cursor:"pointer",fontSize:12},title:N.description,children:N.name},N.name,!1,void 0,this))]},void 0,!0,void 0,this),z.jsxDEV("div",{style:{display:"flex",flexDirection:"column",gap:8},children:$.map(([N,Q])=>Object.entries(Q).filter(([X])=>["get","post","put","delete","patch"].includes(X)).map(([X,Y])=>{let Z=`${X}:${N}`,H=U.has(Z),g=X.toUpperCase(),j=V[X]||"#888";return z.jsxDEV("div",{style:{border:"1px solid #333",borderRadius:6,overflow:"hidden"},children:[z.jsxDEV("div",{onClick:()=>f(Z),style:{display:"flex",alignItems:"center",gap:12,padding:"12px 16px",background:H?"#1a1a2e":"transparent",cursor:"pointer"},children:[z.jsxDEV("span",{style:{background:j,color:"#000",padding:"4px 8px",borderRadius:4,fontSize:11,fontWeight:600,minWidth:60,textAlign:"center"},children:g},void 0,!1,void 0,this),z.jsxDEV("span",{style:{fontFamily:"monospace",color:"#fff"},children:N},void 0,!1,void 0,this),z.jsxDEV("span",{style:{color:"#888",flex:1,fontSize:13},children:Y.summary},void 0,!1,void 0,this),z.jsxDEV("span",{style:{color:"#666",fontSize:12},children:H?"[-]":"[+]"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),H&&z.jsxDEV("div",{style:{padding:16,background:"#0d0d1a",borderTop:"1px solid #333"},children:[Y.description&&z.jsxDEV("p",{style:{color:"#888",marginBottom:16,fontSize:13},children:Y.description},void 0,!1,void 0,this),Y.parameters&&Y.parameters.length>0&&z.jsxDEV("div",{style:{marginBottom:16},children:[z.jsxDEV("h4",{style:{fontSize:13,color:"#888",marginBottom:8},children:"Parameters"},void 0,!1,void 0,this),z.jsxDEV("div",{style:{background:"#1a1a2e",borderRadius:4,padding:12},children:Y.parameters.map((J)=>z.jsxDEV("div",{style:{display:"flex",gap:12,marginBottom:8,fontSize:12},children:[z.jsxDEV("span",{style:{color:"#61affe",minWidth:100},children:[J.name,J.required&&z.jsxDEV("span",{style:{color:"#f66"},children:"*"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("span",{style:{color:"#666"},children:["(",J.in,")"]},void 0,!0,void 0,this),z.jsxDEV("span",{style:{color:"#888"},children:J.schema?.type||"string"},void 0,!1,void 0,this),J.description&&z.jsxDEV("span",{style:{color:"#666"},children:["- ",J.description]},void 0,!0,void 0,this)]},J.name,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Y.requestBody&&z.jsxDEV("div",{style:{marginBottom:16},children:[z.jsxDEV("h4",{style:{fontSize:13,color:"#888",marginBottom:8},children:["Request Body",Y.requestBody.required&&z.jsxDEV("span",{style:{color:"#f66"},children:" (required)"},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("div",{style:{background:"#1a1a2e",borderRadius:4,padding:12,fontFamily:"monospace",fontSize:12,color:"#49cc90"},children:Object.entries(Y.requestBody.content||{}).map(([J,P])=>z.jsxDEV("div",{children:[z.jsxDEV("span",{style:{color:"#666"},children:[J,": "]},void 0,!0,void 0,this),C(P.schema)]},J,!0,void 0,this))},void 0,!1,void 0,this)]},void 0,!0,void 0,this),Y.responses&&z.jsxDEV("div",{children:[z.jsxDEV("h4",{style:{fontSize:13,color:"#888",marginBottom:8},children:"Responses"},void 0,!1,void 0,this),z.jsxDEV("div",{style:{background:"#1a1a2e",borderRadius:4,padding:12},children:Object.entries(Y.responses).map(([J,P])=>{let v=P.content?.["application/json"]?.schema,u=v?.$ref?.split("/").pop(),n=v?.type,i=v?.items?.$ref?.split("/").pop();return z.jsxDEV("div",{style:{marginBottom:12,fontSize:12},children:[z.jsxDEV("div",{style:{display:"flex",gap:12,marginBottom:4},children:[z.jsxDEV("span",{style:{color:J.startsWith("2")?"#49cc90":"#f66",minWidth:40},children:J},void 0,!1,void 0,this),z.jsxDEV("span",{style:{color:"#888"},children:P.description},void 0,!1,void 0,this)]},void 0,!0,void 0,this),v&&z.jsxDEV("div",{style:{marginLeft:52,padding:"8px 12px",background:"#0d0d1a",borderRadius:4,fontFamily:"monospace"},children:u?z.jsxDEV("span",{style:{color:"#61affe"},children:u},void 0,!1,void 0,this):n==="array"&&i?z.jsxDEV("span",{style:{color:"#61affe"},children:[i,"[]"]},void 0,!0,void 0,this):n==="array"?z.jsxDEV("span",{style:{color:"#888"},children:"array"},void 0,!1,void 0,this):z.jsxDEV("span",{style:{color:"#888"},children:C(v)},void 0,!1,void 0,this)},void 0,!1,void 0,this)]},J,!0,void 0,this)})},void 0,!1,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV(c,{method:X,path:N,parameters:Y.parameters,requestBody:Y.requestBody,authFetch:I},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},Z,!0,void 0,this)}))},void 0,!1,void 0,this),A.components?.schemas&&W().length>0&&z.jsxDEV("div",{style:{marginTop:32},children:[z.jsxDEV("h2",{style:{fontSize:18,fontWeight:600,marginBottom:16},children:["Schemas ",G&&z.jsxDEV("span",{style:{color:"#666",fontSize:14},children:["(",G,")"]},void 0,!0,void 0,this)]},void 0,!0,void 0,this),z.jsxDEV("div",{style:{display:"flex",flexDirection:"column",gap:8},children:W().map((N)=>{let Q=A.components.schemas[N];if(!Q)return null;return z.jsxDEV("div",{style:{border:"1px solid #333",borderRadius:6,padding:12},children:[z.jsxDEV("h3",{style:{fontSize:14,color:"#61affe",marginBottom:8},children:N},void 0,!1,void 0,this),Q.properties&&z.jsxDEV("div",{style:{fontSize:12},children:Object.entries(Q.properties).map(([X,Y])=>z.jsxDEV("div",{style:{display:"flex",gap:8,marginBottom:4,fontFamily:"monospace"},children:[z.jsxDEV("span",{style:{color:"#fff",minWidth:120},children:X},void 0,!1,void 0,this),z.jsxDEV("span",{style:{color:"#888"},children:[Y.type||(Y.$ref?Y.$ref.split("/").pop():"any"),Y.nullable&&" | null"]},void 0,!0,void 0,this),Y.enum&&z.jsxDEV("span",{style:{color:"#666"},children:["[",Y.enum.join(" | "),"]"]},void 0,!0,void 0,this)]},X,!0,void 0,this))},void 0,!1,void 0,this)]},N,!0,void 0,this)})},void 0,!1,void 0,this)]},void 0,!0,void 0,this)]},void 0,!0,void 0,this)}export{h as ApiDocsPage};
|
|
3
|
-
|
|
4
|
-
//# debugId=C23C784EFCF3231E64756E2164756E21
|