apteva 0.4.18 → 0.4.20
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.h769ek3a.js +3 -0
- package/dist/ApiDocsPage.kf6bbwkk.js +4 -0
- package/dist/{App.nps62kvt.js → App.039re6cf.js} +3 -3
- package/dist/App.2jmkqm8c.js +4 -0
- package/dist/{App.np463xvy.js → App.2yy66bnp.js} +3 -3
- package/dist/App.3515wsb4.js +4 -0
- package/dist/App.7v1w3ys9.js +4 -0
- package/dist/{App.nft7h9jt.js → App.c90t3dxg.js} +3 -3
- package/dist/App.edwahsvz.js +4 -0
- package/dist/App.jfx3der4.js +4 -0
- package/dist/App.n4jb3c22.js +13 -0
- package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
- package/dist/App.q3bpx15d.js +20 -0
- package/dist/App.r0a2nmqs.js +267 -0
- package/dist/App.s2yrcz15.js +4 -0
- package/dist/App.s5j82a5j.js +4 -0
- package/dist/App.tg1b94tx.js +4 -0
- package/dist/ConnectionsPage.a67fjgbf.js +3 -0
- package/dist/McpPage.d4p3xvtk.js +3 -0
- package/dist/SettingsPage.46sqpe39.js +3 -0
- package/dist/SkillsPage.j9hkqm99.js +3 -0
- package/dist/TasksPage.6pvkb7s7.js +3 -0
- package/dist/TelemetryPage.5zq9msb5.js +3 -0
- package/dist/TestsPage.24432yqt.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/mcp-platform.ts +353 -2
- package/src/providers.ts +22 -9
- package/src/routes/api/agents.ts +15 -2
- 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/system.ts +12 -1
- 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/routes/auth.ts +11 -2
- 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 +2 -2
- 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/dashboard/Dashboard.tsx +5 -4
- package/src/web/components/layout/Header.tsx +196 -4
- package/src/web/components/settings/SettingsPage.tsx +269 -1
- package/src/web/context/AuthContext.tsx +18 -11
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/hooks/useAgents.ts +7 -3
- package/src/web/hooks/useOnboarding.ts +9 -30
- 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
package/src/tui/App.tsx
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import Spinner from "ink-spinner";
|
|
4
|
+
import { AptevaAPI, type User } from "./api.js";
|
|
5
|
+
import { Login } from "./Login.js";
|
|
6
|
+
import { AgentList } from "./AgentList.js";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { resolve, dirname } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
|
|
11
|
+
interface AppProps {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function App({ baseUrl }: AppProps) {
|
|
16
|
+
const [api] = useState(() => new AptevaAPI(baseUrl));
|
|
17
|
+
const [screen, setScreen] = useState<"connecting" | "login" | "agents">("connecting");
|
|
18
|
+
const [user, setUser] = useState<User | null>(null);
|
|
19
|
+
const [connectError, setConnectError] = useState("");
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
let cancelled = false;
|
|
23
|
+
|
|
24
|
+
const tryConnect = async () => {
|
|
25
|
+
// First check if server is already running
|
|
26
|
+
const connected = await api.checkConnection();
|
|
27
|
+
if (cancelled) return;
|
|
28
|
+
|
|
29
|
+
if (connected) {
|
|
30
|
+
setScreen("login");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Server not running — try to start it
|
|
35
|
+
setConnectError("Server not running. Starting...");
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
// Find the server entry point relative to this file
|
|
39
|
+
const serverPath = resolve(dirname(fileURLToPath(import.meta.url)), "../server.ts");
|
|
40
|
+
const child = spawn("bun", ["run", serverPath], {
|
|
41
|
+
stdio: "ignore",
|
|
42
|
+
detached: true,
|
|
43
|
+
env: { ...process.env, PORT: new URL(baseUrl).port || "4280" },
|
|
44
|
+
});
|
|
45
|
+
child.unref();
|
|
46
|
+
|
|
47
|
+
// Wait for server to come up (poll for up to 10 seconds)
|
|
48
|
+
for (let i = 0; i < 20; i++) {
|
|
49
|
+
if (cancelled) return;
|
|
50
|
+
await new Promise(r => setTimeout(r, 500));
|
|
51
|
+
const up = await api.checkConnection();
|
|
52
|
+
if (up) {
|
|
53
|
+
if (!cancelled) setScreen("login");
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Spawn failed
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!cancelled) {
|
|
62
|
+
setConnectError(`Cannot connect to ${baseUrl}. Start the server with: bun run dev`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
tryConnect();
|
|
67
|
+
return () => { cancelled = true; };
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
if (screen === "connecting") {
|
|
71
|
+
return (
|
|
72
|
+
<Box flexDirection="column" padding={1}>
|
|
73
|
+
<Box marginBottom={1}>
|
|
74
|
+
<Text color="hex('#f97316')" bold>{">"}_</Text>
|
|
75
|
+
<Text bold> apteva</Text>
|
|
76
|
+
</Box>
|
|
77
|
+
<Box>
|
|
78
|
+
<Text color="hex('#f97316')"><Spinner type="dots" /></Text>
|
|
79
|
+
<Text> {connectError || `Connecting to ${baseUrl}...`}</Text>
|
|
80
|
+
</Box>
|
|
81
|
+
</Box>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (screen === "login") {
|
|
86
|
+
return (
|
|
87
|
+
<Login
|
|
88
|
+
api={api}
|
|
89
|
+
onSuccess={(u) => {
|
|
90
|
+
setUser(u);
|
|
91
|
+
setScreen("agents");
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (screen === "agents" && user) {
|
|
98
|
+
return <AgentList api={api} user={user} />;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Box, Text, useInput } from "ink";
|
|
3
|
+
import TextInput from "ink-text-input";
|
|
4
|
+
import Spinner from "ink-spinner";
|
|
5
|
+
import type { AptevaAPI, User } from "./api.js";
|
|
6
|
+
|
|
7
|
+
interface LoginProps {
|
|
8
|
+
api: AptevaAPI;
|
|
9
|
+
onSuccess: (user: User) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Login({ api, onSuccess }: LoginProps) {
|
|
13
|
+
const [field, setField] = useState<"username" | "password">("username");
|
|
14
|
+
const [username, setUsername] = useState("");
|
|
15
|
+
const [password, setPassword] = useState("");
|
|
16
|
+
const [error, setError] = useState("");
|
|
17
|
+
const [loading, setLoading] = useState(false);
|
|
18
|
+
|
|
19
|
+
useInput((input, key) => {
|
|
20
|
+
if (key.tab || (key.return && field === "username" && username)) {
|
|
21
|
+
setField(field === "username" ? "password" : "username");
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const handleSubmit = async () => {
|
|
26
|
+
if (!username || !password) return;
|
|
27
|
+
setLoading(true);
|
|
28
|
+
setError("");
|
|
29
|
+
const result = await api.login(username, password);
|
|
30
|
+
setLoading(false);
|
|
31
|
+
if (result.success && result.user) {
|
|
32
|
+
onSuccess(result.user);
|
|
33
|
+
} else {
|
|
34
|
+
setError(result.error || "Login failed");
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Box flexDirection="column" padding={1}>
|
|
40
|
+
<Box marginBottom={1}>
|
|
41
|
+
<Text color="hex('#f97316')" bold>
|
|
42
|
+
{">"}_
|
|
43
|
+
</Text>
|
|
44
|
+
<Text bold> apteva</Text>
|
|
45
|
+
</Box>
|
|
46
|
+
|
|
47
|
+
<Box marginBottom={1}>
|
|
48
|
+
<Text dimColor>Sign in to continue</Text>
|
|
49
|
+
</Box>
|
|
50
|
+
|
|
51
|
+
<Box>
|
|
52
|
+
<Text color={field === "username" ? "hex('#f97316')" : "white"}>
|
|
53
|
+
Username:{" "}
|
|
54
|
+
</Text>
|
|
55
|
+
{field === "username" ? (
|
|
56
|
+
<TextInput
|
|
57
|
+
value={username}
|
|
58
|
+
onChange={setUsername}
|
|
59
|
+
onSubmit={() => {
|
|
60
|
+
if (username) setField("password");
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
) : (
|
|
64
|
+
<Text>{username}</Text>
|
|
65
|
+
)}
|
|
66
|
+
</Box>
|
|
67
|
+
|
|
68
|
+
<Box>
|
|
69
|
+
<Text color={field === "password" ? "hex('#f97316')" : "white"}>
|
|
70
|
+
Password:{" "}
|
|
71
|
+
</Text>
|
|
72
|
+
{field === "password" ? (
|
|
73
|
+
<TextInput
|
|
74
|
+
value={password}
|
|
75
|
+
onChange={setPassword}
|
|
76
|
+
onSubmit={handleSubmit}
|
|
77
|
+
mask="*"
|
|
78
|
+
/>
|
|
79
|
+
) : (
|
|
80
|
+
<Text dimColor>{"*".repeat(password.length) || "..."}</Text>
|
|
81
|
+
)}
|
|
82
|
+
</Box>
|
|
83
|
+
|
|
84
|
+
{loading && (
|
|
85
|
+
<Box marginTop={1}>
|
|
86
|
+
<Text color="hex('#f97316')">
|
|
87
|
+
<Spinner type="dots" />
|
|
88
|
+
</Text>
|
|
89
|
+
<Text> Signing in...</Text>
|
|
90
|
+
</Box>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{error && (
|
|
94
|
+
<Box marginTop={1}>
|
|
95
|
+
<Text color="red">{error}</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
<Box marginTop={1}>
|
|
100
|
+
<Text dimColor>Tab to switch fields · Enter to submit</Text>
|
|
101
|
+
</Box>
|
|
102
|
+
</Box>
|
|
103
|
+
);
|
|
104
|
+
}
|
package/src/tui/api.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface Agent {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
model: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
status: "running" | "stopped" | "error";
|
|
7
|
+
port: number | null;
|
|
8
|
+
projectId: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface User {
|
|
12
|
+
id: string;
|
|
13
|
+
username: string;
|
|
14
|
+
role: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AptevaAPI {
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
private token: string | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(baseUrl: string) {
|
|
22
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async checkConnection(): Promise<boolean> {
|
|
26
|
+
try {
|
|
27
|
+
const res = await fetch(`${this.baseUrl}/api/auth/check`, { signal: AbortSignal.timeout(3000) });
|
|
28
|
+
return res.ok;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private async fetch(path: string, opts: RequestInit = {}): Promise<Response> {
|
|
35
|
+
const headers: Record<string, string> = {
|
|
36
|
+
"Content-Type": "application/json",
|
|
37
|
+
...(opts.headers as Record<string, string> || {}),
|
|
38
|
+
};
|
|
39
|
+
if (this.token) {
|
|
40
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
41
|
+
}
|
|
42
|
+
return fetch(`${this.baseUrl}${path}`, { ...opts, headers });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async login(username: string, password: string): Promise<{ success: boolean; user?: User; error?: string }> {
|
|
46
|
+
try {
|
|
47
|
+
const res = await this.fetch("/api/auth/login", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify({ username, password }),
|
|
50
|
+
});
|
|
51
|
+
const data = await res.json();
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
return { success: false, error: data.error || "Login failed" };
|
|
54
|
+
}
|
|
55
|
+
this.token = data.accessToken;
|
|
56
|
+
return { success: true, user: data.user };
|
|
57
|
+
} catch (err: any) {
|
|
58
|
+
return { success: false, error: err.message || "Connection failed" };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getAgents(): Promise<Agent[]> {
|
|
63
|
+
try {
|
|
64
|
+
const res = await this.fetch("/api/agents");
|
|
65
|
+
if (!res.ok) return [];
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
return data.agents || [];
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/web/App.tsx
CHANGED
|
@@ -64,7 +64,7 @@ function AppContent() {
|
|
|
64
64
|
updateAgent,
|
|
65
65
|
deleteAgent,
|
|
66
66
|
toggleAgent,
|
|
67
|
-
} = useAgents(shouldFetchData);
|
|
67
|
+
} = useAgents(shouldFetchData, currentProjectId);
|
|
68
68
|
|
|
69
69
|
const {
|
|
70
70
|
providers,
|
|
@@ -240,7 +240,7 @@ function AppContent() {
|
|
|
240
240
|
|
|
241
241
|
return (
|
|
242
242
|
<div className="h-screen bg-[#0a0a0a] text-[#e0e0e0] font-mono flex flex-col overflow-hidden">
|
|
243
|
-
<Header onMenuClick={() => setMobileMenuOpen(true)} />
|
|
243
|
+
<Header onMenuClick={() => setMobileMenuOpen(true)} agents={agents} />
|
|
244
244
|
|
|
245
245
|
{startError && (
|
|
246
246
|
<ErrorBanner message={startError} onDismiss={() => setStartError(null)} />
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Chat } from "@apteva/apteva-kit";
|
|
2
|
+
import { Chat, convertApiMessages } from "@apteva/apteva-kit";
|
|
3
3
|
import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, RecurringIcon, ScheduledIcon, TaskOnceIcon } from "../common/Icons";
|
|
4
4
|
import { formatCron, formatRelativeTime } from "../tasks/TasksPage";
|
|
5
5
|
import { Select } from "../common/Select";
|
|
@@ -195,15 +195,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
195
195
|
const res = await fetch(`/api/agents/${agent.id}/threads/${threadId}/messages`);
|
|
196
196
|
if (res.ok) {
|
|
197
197
|
const data = await res.json();
|
|
198
|
-
|
|
199
|
-
.filter((m: any) => typeof m.content === "string")
|
|
200
|
-
.map((m: any) => ({
|
|
201
|
-
id: m.id,
|
|
202
|
-
role: m.role,
|
|
203
|
-
content: m.content,
|
|
204
|
-
timestamp: new Date(m.created_at),
|
|
205
|
-
}));
|
|
206
|
-
setInitialMessages(msgs);
|
|
198
|
+
setInitialMessages(convertApiMessages(data.messages || []));
|
|
207
199
|
} else {
|
|
208
200
|
setInitialMessages([]);
|
|
209
201
|
}
|
|
@@ -255,36 +247,10 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
255
247
|
|
|
256
248
|
// Show live chat for selected thread
|
|
257
249
|
if (selectedThread) {
|
|
258
|
-
const selectedThreadData = threads.find(t => t.id === selectedThread);
|
|
259
250
|
return (
|
|
260
251
|
<>
|
|
261
252
|
{ConfirmDialog}
|
|
262
253
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
263
|
-
{/* Header with back button */}
|
|
264
|
-
<div className="flex items-center gap-3 px-4 py-2 border-b border-[#1a1a1a] flex-shrink-0">
|
|
265
|
-
<button
|
|
266
|
-
onClick={() => { setSelectedThread(null); setInitialMessages([]); }}
|
|
267
|
-
className="text-[#666] hover:text-[#e0e0e0] transition text-lg"
|
|
268
|
-
>
|
|
269
|
-
←
|
|
270
|
-
</button>
|
|
271
|
-
<div className="flex-1 min-w-0">
|
|
272
|
-
<p className="text-sm font-medium truncate">
|
|
273
|
-
{selectedThreadData?.title || `Thread ${selectedThread.slice(0, 8)}`}
|
|
274
|
-
</p>
|
|
275
|
-
<p className="text-xs text-[#666]">
|
|
276
|
-
{selectedThreadData && new Date(selectedThreadData.updated_at || selectedThreadData.created_at).toLocaleString()}
|
|
277
|
-
</p>
|
|
278
|
-
</div>
|
|
279
|
-
<button
|
|
280
|
-
onClick={(e) => deleteThread(selectedThread, e)}
|
|
281
|
-
className="text-[#666] hover:text-red-400 text-sm px-2 py-1"
|
|
282
|
-
>
|
|
283
|
-
Delete
|
|
284
|
-
</button>
|
|
285
|
-
</div>
|
|
286
|
-
|
|
287
|
-
{/* Live chat in this thread */}
|
|
288
254
|
{loadingMessages ? (
|
|
289
255
|
<div className="flex-1 flex items-center justify-center text-[#666]">Loading messages...</div>
|
|
290
256
|
) : (
|
|
@@ -297,7 +263,8 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
297
263
|
placeholder="Continue this conversation..."
|
|
298
264
|
context={agent.systemPrompt}
|
|
299
265
|
variant="terminal"
|
|
300
|
-
showHeader={
|
|
266
|
+
showHeader={true}
|
|
267
|
+
onHeaderBack={() => { setSelectedThread(null); setInitialMessages([]); }}
|
|
301
268
|
/>
|
|
302
269
|
)}
|
|
303
270
|
</div>
|
|
@@ -232,3 +232,11 @@ export function TaskOnceIcon({ className = "w-4 h-4" }: IconProps) {
|
|
|
232
232
|
</svg>
|
|
233
233
|
);
|
|
234
234
|
}
|
|
235
|
+
|
|
236
|
+
export function BellIcon({ className = "w-5 h-5" }: IconProps) {
|
|
237
|
+
return (
|
|
238
|
+
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
239
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
240
|
+
</svg>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -12,15 +12,6 @@ interface Subscription {
|
|
|
12
12
|
updated_at: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
interface TriggerInstance {
|
|
16
|
-
id: string;
|
|
17
|
-
trigger_slug: string;
|
|
18
|
-
connected_account_id: string | null;
|
|
19
|
-
status: "active" | "disabled";
|
|
20
|
-
config: Record<string, unknown>;
|
|
21
|
-
created_at: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
15
|
interface Agent {
|
|
25
16
|
id: string;
|
|
26
17
|
name: string;
|
|
@@ -32,7 +23,6 @@ export function OverviewTab() {
|
|
|
32
23
|
const { currentProjectId } = useProjects();
|
|
33
24
|
|
|
34
25
|
const [subscriptions, setSubscriptions] = useState<Subscription[]>([]);
|
|
35
|
-
const [triggers, setTriggers] = useState<TriggerInstance[]>([]);
|
|
36
26
|
const [agents, setAgents] = useState<Agent[]>([]);
|
|
37
27
|
const [loading, setLoading] = useState(true);
|
|
38
28
|
|
|
@@ -42,9 +32,8 @@ export function OverviewTab() {
|
|
|
42
32
|
const projectParam = currentProjectId && currentProjectId !== "unassigned" ? `?project_id=${currentProjectId}` : "";
|
|
43
33
|
|
|
44
34
|
try {
|
|
45
|
-
const [subsRes,
|
|
35
|
+
const [subsRes, agentsRes] = await Promise.all([
|
|
46
36
|
authFetch(`/api/subscriptions${projectParam}`).catch(() => null),
|
|
47
|
-
authFetch(`/api/triggers${projectParam}`).catch(() => null),
|
|
48
37
|
authFetch(`/api/agents`).catch(() => null),
|
|
49
38
|
]);
|
|
50
39
|
|
|
@@ -52,10 +41,6 @@ export function OverviewTab() {
|
|
|
52
41
|
const data = await subsRes.json();
|
|
53
42
|
setSubscriptions(data.subscriptions || []);
|
|
54
43
|
}
|
|
55
|
-
if (triggersRes?.ok) {
|
|
56
|
-
const data = await triggersRes.json();
|
|
57
|
-
setTriggers(data.triggers || []);
|
|
58
|
-
}
|
|
59
44
|
if (agentsRes?.ok) {
|
|
60
45
|
const data = await agentsRes.json();
|
|
61
46
|
setAgents(data.agents || []);
|
|
@@ -73,40 +58,40 @@ export function OverviewTab() {
|
|
|
73
58
|
return <div className="text-center py-12 text-[#666]">Loading...</div>;
|
|
74
59
|
}
|
|
75
60
|
|
|
76
|
-
const
|
|
77
|
-
const
|
|
61
|
+
const enabledSubs = subscriptions.filter(s => s.enabled);
|
|
62
|
+
const disabledSubs = subscriptions.filter(s => !s.enabled);
|
|
78
63
|
const agentMap = new Map(agents.map(a => [a.id, a]));
|
|
79
64
|
|
|
80
65
|
return (
|
|
81
66
|
<div className="space-y-6">
|
|
82
67
|
{/* Stats */}
|
|
83
|
-
<div className="grid grid-cols-
|
|
84
|
-
<StatCard label="
|
|
85
|
-
<StatCard label="
|
|
86
|
-
<StatCard label="Total
|
|
68
|
+
<div className="grid grid-cols-3 gap-4">
|
|
69
|
+
<StatCard label="Active" value={enabledSubs.length} />
|
|
70
|
+
<StatCard label="Disabled" value={disabledSubs.length} />
|
|
71
|
+
<StatCard label="Total" value={subscriptions.length} />
|
|
87
72
|
</div>
|
|
88
73
|
|
|
89
|
-
{/*
|
|
74
|
+
{/* Subscriptions */}
|
|
90
75
|
<section>
|
|
91
|
-
<h3 className="text-sm font-medium text-[#888] mb-3">
|
|
92
|
-
{
|
|
76
|
+
<h3 className="text-sm font-medium text-[#888] mb-3">Subscriptions ({subscriptions.length})</h3>
|
|
77
|
+
{subscriptions.length === 0 ? (
|
|
93
78
|
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-6 text-center text-[#666] text-sm">
|
|
94
|
-
No subscriptions. Go to the Triggers tab to
|
|
79
|
+
No subscriptions yet. Go to the Triggers tab to create one.
|
|
95
80
|
</div>
|
|
96
81
|
) : (
|
|
97
82
|
<div className="space-y-2">
|
|
98
|
-
{
|
|
83
|
+
{subscriptions.map(sub => {
|
|
99
84
|
const agent = agentMap.get(sub.agent_id);
|
|
100
85
|
return (
|
|
101
86
|
<div key={sub.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-3 flex items-center gap-3">
|
|
102
|
-
<div className=
|
|
87
|
+
<div className={`w-2 h-2 rounded-full flex-shrink-0 ${sub.enabled ? "bg-green-400" : "bg-[#555]"}`} />
|
|
103
88
|
<div className="flex-1 min-w-0">
|
|
104
89
|
<div className="text-sm font-medium truncate">
|
|
105
|
-
{sub.trigger_slug.replace(/_/g, " ")}
|
|
90
|
+
{sub.trigger_slug.replace(/_/g, " ").replace(/-/g, " ")}
|
|
106
91
|
</div>
|
|
107
92
|
<div className="text-xs text-[#666]">
|
|
108
93
|
{sub.trigger_instance_id
|
|
109
|
-
? `
|
|
94
|
+
? `ID: ${sub.trigger_instance_id.slice(0, 12)}...`
|
|
110
95
|
: "All instances"
|
|
111
96
|
}
|
|
112
97
|
</div>
|
|
@@ -115,50 +100,19 @@ export function OverviewTab() {
|
|
|
115
100
|
<span className="text-[#555]">→</span>{" "}
|
|
116
101
|
<span className="text-[#f97316]">{agent?.name || "Unknown Agent"}</span>
|
|
117
102
|
</div>
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
</span>
|
|
126
|
-
)}
|
|
103
|
+
<span className={`text-xs px-2 py-0.5 rounded flex-shrink-0 ${
|
|
104
|
+
sub.enabled
|
|
105
|
+
? "bg-green-500/10 text-green-400"
|
|
106
|
+
: "bg-[#1a1a1a] text-[#555]"
|
|
107
|
+
}`}>
|
|
108
|
+
{sub.enabled ? "active" : "disabled"}
|
|
109
|
+
</span>
|
|
127
110
|
</div>
|
|
128
111
|
);
|
|
129
112
|
})}
|
|
130
113
|
</div>
|
|
131
114
|
)}
|
|
132
115
|
</section>
|
|
133
|
-
|
|
134
|
-
{/* Active Triggers */}
|
|
135
|
-
<section>
|
|
136
|
-
<h3 className="text-sm font-medium text-[#888] mb-3">Active Triggers ({activeTriggers.length})</h3>
|
|
137
|
-
{activeTriggers.length === 0 ? (
|
|
138
|
-
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-6 text-center text-[#666] text-sm">
|
|
139
|
-
No active triggers on Composio.
|
|
140
|
-
</div>
|
|
141
|
-
) : (
|
|
142
|
-
<div className="space-y-2">
|
|
143
|
-
{activeTriggers.map(trigger => (
|
|
144
|
-
<div key={trigger.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-3 flex items-center gap-3">
|
|
145
|
-
<div className="w-2 h-2 rounded-full bg-green-400 flex-shrink-0" />
|
|
146
|
-
<div className="flex-1 min-w-0">
|
|
147
|
-
<div className="text-sm font-medium truncate">
|
|
148
|
-
{trigger.trigger_slug.replace(/_/g, " ")}
|
|
149
|
-
</div>
|
|
150
|
-
<div className="text-xs text-[#666]">
|
|
151
|
-
ID: {trigger.id.slice(0, 8)}...
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
<span className="text-xs text-green-400 bg-green-500/10 px-2 py-0.5 rounded">
|
|
155
|
-
active
|
|
156
|
-
</span>
|
|
157
|
-
</div>
|
|
158
|
-
))}
|
|
159
|
-
</div>
|
|
160
|
-
)}
|
|
161
|
-
</section>
|
|
162
116
|
</div>
|
|
163
117
|
);
|
|
164
118
|
}
|