apteva 0.2.8 → 0.2.9
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/App.m4hg4bxq.js +218 -0
- package/dist/index.html +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/db.ts +130 -16
- package/src/integrations/composio.ts +437 -0
- package/src/integrations/index.ts +80 -0
- package/src/openapi.ts +1724 -0
- package/src/routes/api.ts +599 -107
- package/src/server.ts +75 -6
- package/src/web/App.tsx +3 -0
- package/src/web/components/agents/AgentPanel.tsx +62 -37
- package/src/web/components/api/ApiDocsPage.tsx +583 -0
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/Modal.tsx +183 -0
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +743 -0
- package/src/web/components/mcp/McpPage.tsx +242 -83
- package/src/web/components/settings/SettingsPage.tsx +24 -9
- package/src/web/components/tasks/TasksPage.tsx +1 -1
- package/src/web/index.html +1 -1
- package/src/web/types.ts +4 -1
- package/dist/App.hzbfeg94.js +0 -217
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { McpIcon } from "../common/Icons";
|
|
3
3
|
import { useAuth } from "../../context";
|
|
4
|
+
import { useConfirm, useAlert } from "../common/Modal";
|
|
4
5
|
import type { McpTool, McpToolCallResult } from "../../types";
|
|
6
|
+
import { IntegrationsPanel } from "./IntegrationsPanel";
|
|
5
7
|
|
|
6
8
|
interface McpServer {
|
|
7
9
|
id: string;
|
|
@@ -11,8 +13,11 @@ interface McpServer {
|
|
|
11
13
|
command: string | null;
|
|
12
14
|
args: string | null;
|
|
13
15
|
env: Record<string, string>;
|
|
16
|
+
url: string | null;
|
|
17
|
+
headers: Record<string, string>;
|
|
14
18
|
port: number | null;
|
|
15
19
|
status: "stopped" | "running";
|
|
20
|
+
source: string | null; // "composio", "smithery", or null for local
|
|
16
21
|
created_at: string;
|
|
17
22
|
}
|
|
18
23
|
|
|
@@ -35,6 +40,7 @@ export function McpPage() {
|
|
|
35
40
|
const [showAdd, setShowAdd] = useState(false);
|
|
36
41
|
const [selectedServer, setSelectedServer] = useState<McpServer | null>(null);
|
|
37
42
|
const [activeTab, setActiveTab] = useState<"servers" | "hosted" | "registry">("servers");
|
|
43
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
38
44
|
|
|
39
45
|
const fetchServers = async () => {
|
|
40
46
|
try {
|
|
@@ -70,7 +76,8 @@ export function McpPage() {
|
|
|
70
76
|
};
|
|
71
77
|
|
|
72
78
|
const deleteServer = async (id: string) => {
|
|
73
|
-
|
|
79
|
+
const confirmed = await confirm("Delete this MCP server?", { confirmText: "Delete", title: "Delete Server" });
|
|
80
|
+
if (!confirmed) return;
|
|
74
81
|
try {
|
|
75
82
|
await authFetch(`/api/mcp/servers/${id}`, { method: "DELETE" });
|
|
76
83
|
if (selectedServer?.id === id) {
|
|
@@ -83,6 +90,8 @@ export function McpPage() {
|
|
|
83
90
|
};
|
|
84
91
|
|
|
85
92
|
return (
|
|
93
|
+
<>
|
|
94
|
+
{ConfirmDialog}
|
|
86
95
|
<div className="flex-1 overflow-auto p-6">
|
|
87
96
|
<div className="max-w-6xl">
|
|
88
97
|
{/* Header */}
|
|
@@ -176,17 +185,21 @@ export function McpPage() {
|
|
|
176
185
|
<div className="flex gap-6">
|
|
177
186
|
{/* Server List */}
|
|
178
187
|
<div className={`space-y-3 ${selectedServer ? "w-1/2" : "w-full"}`}>
|
|
179
|
-
{servers.map(server =>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
188
|
+
{servers.map(server => {
|
|
189
|
+
const isRemote = server.type === "http" && server.url;
|
|
190
|
+
const isAvailable = isRemote || server.status === "running";
|
|
191
|
+
return (
|
|
192
|
+
<McpServerCard
|
|
193
|
+
key={server.id}
|
|
194
|
+
server={server}
|
|
195
|
+
selected={selectedServer?.id === server.id}
|
|
196
|
+
onSelect={() => setSelectedServer(isAvailable ? server : null)}
|
|
197
|
+
onStart={() => startServer(server.id)}
|
|
198
|
+
onStop={() => stopServer(server.id)}
|
|
199
|
+
onDelete={() => deleteServer(server.id)}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
190
203
|
</div>
|
|
191
204
|
|
|
192
205
|
{/* Tools Panel */}
|
|
@@ -205,7 +218,7 @@ export function McpPage() {
|
|
|
205
218
|
|
|
206
219
|
{/* Hosted Services Tab */}
|
|
207
220
|
{activeTab === "hosted" && (
|
|
208
|
-
<HostedServices />
|
|
221
|
+
<HostedServices onServerAdded={fetchServers} />
|
|
209
222
|
)}
|
|
210
223
|
|
|
211
224
|
{/* Browse Registry Tab */}
|
|
@@ -252,6 +265,7 @@ export function McpPage() {
|
|
|
252
265
|
/>
|
|
253
266
|
)}
|
|
254
267
|
</div>
|
|
268
|
+
</>
|
|
255
269
|
);
|
|
256
270
|
}
|
|
257
271
|
|
|
@@ -270,28 +284,50 @@ function McpServerCard({
|
|
|
270
284
|
onStop: () => void;
|
|
271
285
|
onDelete: () => void;
|
|
272
286
|
}) {
|
|
287
|
+
// Remote/hosted servers (http type with url) are always available
|
|
288
|
+
const isRemote = server.type === "http" && server.url;
|
|
289
|
+
const isAvailable = isRemote || server.status === "running";
|
|
290
|
+
|
|
291
|
+
// Determine what to show as the server info
|
|
292
|
+
const getServerInfo = () => {
|
|
293
|
+
if (isRemote) {
|
|
294
|
+
// Show source (composio, smithery) or just "remote"
|
|
295
|
+
const source = server.source || "remote";
|
|
296
|
+
return `${source} • http`;
|
|
297
|
+
}
|
|
298
|
+
return `${server.type} • ${server.package || server.command || "custom"}${
|
|
299
|
+
server.status === "running" && server.port ? ` • :${server.port}` : ""
|
|
300
|
+
}`;
|
|
301
|
+
};
|
|
302
|
+
|
|
273
303
|
return (
|
|
274
304
|
<div
|
|
275
305
|
className={`bg-[#111] border rounded-lg p-4 cursor-pointer transition ${
|
|
276
306
|
selected ? "border-[#f97316]" : "border-[#1a1a1a] hover:border-[#333]"
|
|
277
307
|
}`}
|
|
278
|
-
onClick={
|
|
308
|
+
onClick={isAvailable ? onSelect : undefined}
|
|
279
309
|
>
|
|
280
310
|
<div className="flex items-center justify-between">
|
|
281
311
|
<div className="flex items-center gap-3">
|
|
282
312
|
<div className={`w-2 h-2 rounded-full ${
|
|
283
|
-
|
|
313
|
+
isAvailable ? "bg-green-400" : "bg-[#444]"
|
|
284
314
|
}`} />
|
|
285
315
|
<div>
|
|
286
316
|
<h3 className="font-medium">{server.name}</h3>
|
|
287
|
-
<p className="text-sm text-[#666]">
|
|
288
|
-
{server.type} • {server.package || server.command || "custom"}
|
|
289
|
-
{server.status === "running" && server.port && ` • :${server.port}`}
|
|
290
|
-
</p>
|
|
317
|
+
<p className="text-sm text-[#666]">{getServerInfo()}</p>
|
|
291
318
|
</div>
|
|
292
319
|
</div>
|
|
293
320
|
<div className="flex items-center gap-2">
|
|
294
|
-
{
|
|
321
|
+
{isRemote ? (
|
|
322
|
+
// Remote servers: no start/stop, just delete
|
|
323
|
+
<button
|
|
324
|
+
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
325
|
+
className="text-sm text-[#666] hover:text-red-400 px-3 py-1 transition"
|
|
326
|
+
>
|
|
327
|
+
Remove
|
|
328
|
+
</button>
|
|
329
|
+
) : server.status === "running" ? (
|
|
330
|
+
// Local running server: tools + stop + delete
|
|
295
331
|
<>
|
|
296
332
|
<button
|
|
297
333
|
onClick={(e) => { e.stopPropagation(); onSelect(); }}
|
|
@@ -305,21 +341,30 @@ function McpServerCard({
|
|
|
305
341
|
>
|
|
306
342
|
Stop
|
|
307
343
|
</button>
|
|
344
|
+
<button
|
|
345
|
+
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
346
|
+
className="text-sm text-[#666] hover:text-red-400 px-3 py-1 transition"
|
|
347
|
+
>
|
|
348
|
+
Delete
|
|
349
|
+
</button>
|
|
308
350
|
</>
|
|
309
351
|
) : (
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
352
|
+
// Local stopped server: start + delete
|
|
353
|
+
<>
|
|
354
|
+
<button
|
|
355
|
+
onClick={(e) => { e.stopPropagation(); onStart(); }}
|
|
356
|
+
className="text-sm text-[#666] hover:text-green-400 px-3 py-1 transition"
|
|
357
|
+
>
|
|
358
|
+
Start
|
|
359
|
+
</button>
|
|
360
|
+
<button
|
|
361
|
+
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
362
|
+
className="text-sm text-[#666] hover:text-red-400 px-3 py-1 transition"
|
|
363
|
+
>
|
|
364
|
+
Delete
|
|
365
|
+
</button>
|
|
366
|
+
</>
|
|
316
367
|
)}
|
|
317
|
-
<button
|
|
318
|
-
onClick={(e) => { e.stopPropagation(); onDelete(); }}
|
|
319
|
-
className="text-sm text-[#666] hover:text-red-400 px-3 py-1 transition"
|
|
320
|
-
>
|
|
321
|
-
Delete
|
|
322
|
-
</button>
|
|
323
368
|
</div>
|
|
324
369
|
</div>
|
|
325
370
|
</div>
|
|
@@ -782,19 +827,43 @@ interface ComposioConfig {
|
|
|
782
827
|
createdAt?: string;
|
|
783
828
|
}
|
|
784
829
|
|
|
785
|
-
function HostedServices() {
|
|
830
|
+
function HostedServices({ onServerAdded }: { onServerAdded?: () => void }) {
|
|
786
831
|
const { authFetch } = useAuth();
|
|
832
|
+
const [subTab, setSubTab] = useState<"configs" | "connect">("configs");
|
|
787
833
|
const [composioConnected, setComposioConnected] = useState(false);
|
|
788
834
|
const [smitheryConnected, setSmitheryConnected] = useState(false);
|
|
789
835
|
const [composioConfigs, setComposioConfigs] = useState<ComposioConfig[]>([]);
|
|
836
|
+
const [addedServers, setAddedServers] = useState<Set<string>>(new Set());
|
|
790
837
|
const [loading, setLoading] = useState(true);
|
|
791
838
|
const [loadingConfigs, setLoadingConfigs] = useState(false);
|
|
839
|
+
const [addingConfig, setAddingConfig] = useState<string | null>(null);
|
|
840
|
+
const { alert, AlertDialog } = useAlert();
|
|
792
841
|
|
|
793
842
|
const fetchStatus = async () => {
|
|
794
843
|
try {
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
|
|
844
|
+
const [providersRes, serversRes] = await Promise.all([
|
|
845
|
+
authFetch("/api/providers"),
|
|
846
|
+
authFetch("/api/mcp/servers"),
|
|
847
|
+
]);
|
|
848
|
+
const providersData = await providersRes.json();
|
|
849
|
+
const serversData = await serversRes.json();
|
|
850
|
+
|
|
851
|
+
const providers = providersData.providers || [];
|
|
852
|
+
const servers = serversData.servers || [];
|
|
853
|
+
|
|
854
|
+
// Track which Composio config IDs are already added as servers
|
|
855
|
+
// Extract config ID from URLs like https://backend.composio.dev/v3/mcp/{configId}/mcp?user_id=...
|
|
856
|
+
const composioConfigIds = new Set(
|
|
857
|
+
servers
|
|
858
|
+
.filter((s: any) => s.source === "composio" && s.url)
|
|
859
|
+
.map((s: any) => {
|
|
860
|
+
const match = s.url.match(/\/v3\/mcp\/([^/]+)/);
|
|
861
|
+
return match ? match[1] : null;
|
|
862
|
+
})
|
|
863
|
+
.filter(Boolean)
|
|
864
|
+
);
|
|
865
|
+
setAddedServers(composioConfigIds);
|
|
866
|
+
|
|
798
867
|
const composio = providers.find((p: any) => p.id === "composio");
|
|
799
868
|
const smithery = providers.find((p: any) => p.id === "smithery");
|
|
800
869
|
setComposioConnected(composio?.hasKey || false);
|
|
@@ -821,6 +890,30 @@ function HostedServices() {
|
|
|
821
890
|
setLoadingConfigs(false);
|
|
822
891
|
};
|
|
823
892
|
|
|
893
|
+
const addComposioConfig = async (configId: string) => {
|
|
894
|
+
setAddingConfig(configId);
|
|
895
|
+
try {
|
|
896
|
+
const res = await authFetch(`/api/integrations/composio/configs/${configId}/add`, {
|
|
897
|
+
method: "POST",
|
|
898
|
+
});
|
|
899
|
+
if (res.ok) {
|
|
900
|
+
// Mark as added by config ID
|
|
901
|
+
setAddedServers(prev => new Set([...prev, configId]));
|
|
902
|
+
onServerAdded?.();
|
|
903
|
+
} else {
|
|
904
|
+
const data = await res.json();
|
|
905
|
+
await alert(data.error || "Failed to add config", { title: "Error", variant: "error" });
|
|
906
|
+
}
|
|
907
|
+
} catch (e) {
|
|
908
|
+
console.error("Failed to add config:", e);
|
|
909
|
+
}
|
|
910
|
+
setAddingConfig(null);
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
const isConfigAdded = (configId: string) => {
|
|
914
|
+
return addedServers.has(configId);
|
|
915
|
+
};
|
|
916
|
+
|
|
824
917
|
useEffect(() => {
|
|
825
918
|
fetchStatus();
|
|
826
919
|
}, [authFetch]);
|
|
@@ -849,13 +942,62 @@ function HostedServices() {
|
|
|
849
942
|
}
|
|
850
943
|
|
|
851
944
|
return (
|
|
945
|
+
<>
|
|
946
|
+
{AlertDialog}
|
|
852
947
|
<div className="space-y-6">
|
|
853
|
-
{/*
|
|
948
|
+
{/* Sub-tabs for Composio */}
|
|
854
949
|
{composioConnected && (
|
|
950
|
+
<div className="flex gap-1 bg-[#0a0a0a] border border-[#222] rounded-lg p-1 w-fit">
|
|
951
|
+
<button
|
|
952
|
+
onClick={() => setSubTab("configs")}
|
|
953
|
+
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
954
|
+
subTab === "configs"
|
|
955
|
+
? "bg-[#1a1a1a] text-white"
|
|
956
|
+
: "text-[#666] hover:text-[#888]"
|
|
957
|
+
}`}
|
|
958
|
+
>
|
|
959
|
+
MCP Configs
|
|
960
|
+
</button>
|
|
961
|
+
<button
|
|
962
|
+
onClick={() => setSubTab("connect")}
|
|
963
|
+
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
964
|
+
subTab === "connect"
|
|
965
|
+
? "bg-[#1a1a1a] text-white"
|
|
966
|
+
: "text-[#666] hover:text-[#888]"
|
|
967
|
+
}`}
|
|
968
|
+
>
|
|
969
|
+
Connect Apps
|
|
970
|
+
</button>
|
|
971
|
+
</div>
|
|
972
|
+
)}
|
|
973
|
+
|
|
974
|
+
{/* Connect Apps Tab */}
|
|
975
|
+
{composioConnected && subTab === "connect" && (
|
|
976
|
+
<div>
|
|
977
|
+
<div className="flex items-center justify-between mb-4">
|
|
978
|
+
<div>
|
|
979
|
+
<h2 className="font-medium">Connect Apps via Composio</h2>
|
|
980
|
+
<p className="text-sm text-[#666] mt-1">
|
|
981
|
+
Connect your accounts to enable tools in MCP configs
|
|
982
|
+
</p>
|
|
983
|
+
</div>
|
|
984
|
+
</div>
|
|
985
|
+
<IntegrationsPanel
|
|
986
|
+
providerId="composio"
|
|
987
|
+
onConnectionComplete={() => {
|
|
988
|
+
// Refresh configs after connecting an app
|
|
989
|
+
fetchComposioConfigs();
|
|
990
|
+
}}
|
|
991
|
+
/>
|
|
992
|
+
</div>
|
|
993
|
+
)}
|
|
994
|
+
|
|
995
|
+
{/* MCP Configs Tab */}
|
|
996
|
+
{composioConnected && subTab === "configs" && (
|
|
855
997
|
<div>
|
|
856
998
|
<div className="flex items-center justify-between mb-3">
|
|
857
999
|
<div className="flex items-center gap-2">
|
|
858
|
-
<h2 className="font-medium">Composio</h2>
|
|
1000
|
+
<h2 className="font-medium">Composio MCP Configs</h2>
|
|
859
1001
|
<span className="text-xs text-green-400">Connected</span>
|
|
860
1002
|
</div>
|
|
861
1003
|
<div className="flex items-center gap-3">
|
|
@@ -882,62 +1024,78 @@ function HostedServices() {
|
|
|
882
1024
|
) : composioConfigs.length === 0 ? (
|
|
883
1025
|
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 text-center">
|
|
884
1026
|
<p className="text-sm text-[#666]">No MCP configs found</p>
|
|
1027
|
+
<p className="text-xs text-[#555] mt-2">
|
|
1028
|
+
First <button onClick={() => setSubTab("connect")} className="text-[#f97316] hover:text-[#fb923c]">connect some apps</button>, then create a config.
|
|
1029
|
+
</p>
|
|
885
1030
|
<a
|
|
886
1031
|
href="https://app.composio.dev/mcp_configs"
|
|
887
1032
|
target="_blank"
|
|
888
1033
|
rel="noopener noreferrer"
|
|
889
|
-
className="text-xs text-[#f97316] hover:text-[#fb923c] mt-
|
|
1034
|
+
className="text-xs text-[#f97316] hover:text-[#fb923c] mt-2 inline-block"
|
|
890
1035
|
>
|
|
891
|
-
Create
|
|
1036
|
+
Create in Composio →
|
|
892
1037
|
</a>
|
|
893
1038
|
</div>
|
|
894
1039
|
) : (
|
|
895
1040
|
<div className="space-y-2">
|
|
896
|
-
{composioConfigs.map((config) =>
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
<div className="flex
|
|
908
|
-
{config.
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
>
|
|
913
|
-
{toolkit}
|
|
914
|
-
</span>
|
|
915
|
-
))}
|
|
916
|
-
{config.toolkits.length > 4 && (
|
|
917
|
-
<span className="text-xs text-[#555]">+{config.toolkits.length - 4}</span>
|
|
1041
|
+
{composioConfigs.map((config) => {
|
|
1042
|
+
const added = isConfigAdded(config.id);
|
|
1043
|
+
const isAdding = addingConfig === config.id;
|
|
1044
|
+
return (
|
|
1045
|
+
<div
|
|
1046
|
+
key={config.id}
|
|
1047
|
+
className={`bg-[#111] border rounded-lg p-3 transition flex items-center justify-between ${
|
|
1048
|
+
added ? "border-green-500/30" : "border-[#1a1a1a] hover:border-[#333]"
|
|
1049
|
+
}`}
|
|
1050
|
+
>
|
|
1051
|
+
<div className="flex-1 min-w-0">
|
|
1052
|
+
<div className="flex items-center gap-2">
|
|
1053
|
+
<span className="font-medium text-sm">{config.name}</span>
|
|
1054
|
+
<span className="text-xs text-[#555]">{config.toolsCount} tools</span>
|
|
1055
|
+
{added && (
|
|
1056
|
+
<span className="text-xs text-green-400">Added</span>
|
|
918
1057
|
)}
|
|
919
1058
|
</div>
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
>
|
|
936
|
-
|
|
937
|
-
|
|
1059
|
+
{config.toolkits.length > 0 && (
|
|
1060
|
+
<div className="flex flex-wrap gap-1 mt-1">
|
|
1061
|
+
{config.toolkits.slice(0, 4).map((toolkit) => (
|
|
1062
|
+
<span
|
|
1063
|
+
key={toolkit}
|
|
1064
|
+
className="text-xs bg-[#1a1a1a] text-[#666] px-1.5 py-0.5 rounded"
|
|
1065
|
+
>
|
|
1066
|
+
{toolkit}
|
|
1067
|
+
</span>
|
|
1068
|
+
))}
|
|
1069
|
+
{config.toolkits.length > 4 && (
|
|
1070
|
+
<span className="text-xs text-[#555]">+{config.toolkits.length - 4}</span>
|
|
1071
|
+
)}
|
|
1072
|
+
</div>
|
|
1073
|
+
)}
|
|
1074
|
+
</div>
|
|
1075
|
+
<div className="flex items-center gap-2 ml-3">
|
|
1076
|
+
{added ? (
|
|
1077
|
+
<span className="text-xs text-[#555] px-2 py-1">In Servers</span>
|
|
1078
|
+
) : (
|
|
1079
|
+
<button
|
|
1080
|
+
onClick={() => addComposioConfig(config.id)}
|
|
1081
|
+
disabled={isAdding}
|
|
1082
|
+
className="text-xs bg-[#f97316] hover:bg-[#fb923c] text-black px-3 py-1 rounded font-medium transition disabled:opacity-50"
|
|
1083
|
+
>
|
|
1084
|
+
{isAdding ? "Adding..." : "Add"}
|
|
1085
|
+
</button>
|
|
1086
|
+
)}
|
|
1087
|
+
<a
|
|
1088
|
+
href={`https://app.composio.dev/mcp_configs/${config.id}`}
|
|
1089
|
+
target="_blank"
|
|
1090
|
+
rel="noopener noreferrer"
|
|
1091
|
+
className="text-xs text-[#666] hover:text-[#888] transition"
|
|
1092
|
+
>
|
|
1093
|
+
Edit
|
|
1094
|
+
</a>
|
|
1095
|
+
</div>
|
|
938
1096
|
</div>
|
|
939
|
-
|
|
940
|
-
)
|
|
1097
|
+
);
|
|
1098
|
+
})}
|
|
941
1099
|
</div>
|
|
942
1100
|
)}
|
|
943
1101
|
</div>
|
|
@@ -969,11 +1127,12 @@ function HostedServices() {
|
|
|
969
1127
|
)}
|
|
970
1128
|
|
|
971
1129
|
<div className="p-3 bg-[#0a0a0a] border border-[#222] rounded text-xs text-[#666]">
|
|
972
|
-
<strong className="text-[#888]">Tip:</strong>
|
|
1130
|
+
<strong className="text-[#888]">Tip:</strong> Connect apps first, then add MCP configs to make tools available to your agents.
|
|
973
1131
|
{" · "}
|
|
974
1132
|
<a href="/settings" className="text-[#f97316] hover:text-[#fb923c]">Add more providers in Settings</a>
|
|
975
1133
|
</div>
|
|
976
1134
|
</div>
|
|
1135
|
+
</>
|
|
977
1136
|
);
|
|
978
1137
|
}
|
|
979
1138
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { CheckIcon, CloseIcon, PlusIcon } from "../common/Icons";
|
|
3
|
-
import { Modal } from "../common/Modal";
|
|
3
|
+
import { Modal, useConfirm } from "../common/Modal";
|
|
4
4
|
import { useProjects, useAuth, type Project } from "../../context";
|
|
5
5
|
import type { Provider } from "../../types";
|
|
6
6
|
|
|
@@ -81,6 +81,7 @@ function ProvidersSettings() {
|
|
|
81
81
|
const [testing, setTesting] = useState(false);
|
|
82
82
|
const [error, setError] = useState<string | null>(null);
|
|
83
83
|
const [success, setSuccess] = useState<string | null>(null);
|
|
84
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
84
85
|
|
|
85
86
|
const fetchProviders = async () => {
|
|
86
87
|
const res = await authFetch("/api/providers");
|
|
@@ -136,7 +137,8 @@ function ProvidersSettings() {
|
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
const deleteKey = async (providerId: string) => {
|
|
139
|
-
|
|
140
|
+
const confirmed = await confirm("Are you sure you want to remove this API key?", { confirmText: "Remove", title: "Remove API Key" });
|
|
141
|
+
if (!confirmed) return;
|
|
140
142
|
await authFetch(`/api/keys/${providerId}`, { method: "DELETE" });
|
|
141
143
|
fetchProviders();
|
|
142
144
|
};
|
|
@@ -147,6 +149,8 @@ function ProvidersSettings() {
|
|
|
147
149
|
const intConfiguredCount = integrations.filter(p => p.hasKey).length;
|
|
148
150
|
|
|
149
151
|
return (
|
|
152
|
+
<>
|
|
153
|
+
{ConfirmDialog}
|
|
150
154
|
<div className="space-y-10">
|
|
151
155
|
{/* AI Providers Section */}
|
|
152
156
|
<div>
|
|
@@ -224,6 +228,7 @@ function ProvidersSettings() {
|
|
|
224
228
|
</div>
|
|
225
229
|
</div>
|
|
226
230
|
</div>
|
|
231
|
+
</>
|
|
227
232
|
);
|
|
228
233
|
}
|
|
229
234
|
|
|
@@ -242,11 +247,11 @@ function ProjectsSettings() {
|
|
|
242
247
|
const { projects, createProject, updateProject, deleteProject } = useProjects();
|
|
243
248
|
const [showModal, setShowModal] = useState(false);
|
|
244
249
|
const [editingProject, setEditingProject] = useState<Project | null>(null);
|
|
250
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
245
251
|
|
|
246
252
|
const handleDelete = async (id: string) => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
253
|
+
const confirmed = await confirm("Are you sure you want to delete this project? Agents in this project will become unassigned.", { confirmText: "Delete", title: "Delete Project" });
|
|
254
|
+
if (!confirmed) return;
|
|
250
255
|
await deleteProject(id);
|
|
251
256
|
};
|
|
252
257
|
|
|
@@ -266,6 +271,8 @@ function ProjectsSettings() {
|
|
|
266
271
|
};
|
|
267
272
|
|
|
268
273
|
return (
|
|
274
|
+
<>
|
|
275
|
+
{ConfirmDialog}
|
|
269
276
|
<div className="max-w-4xl w-full">
|
|
270
277
|
<div className="mb-6 flex items-center justify-between gap-4">
|
|
271
278
|
<div>
|
|
@@ -347,6 +354,7 @@ function ProjectsSettings() {
|
|
|
347
354
|
/>
|
|
348
355
|
)}
|
|
349
356
|
</div>
|
|
357
|
+
</>
|
|
350
358
|
);
|
|
351
359
|
}
|
|
352
360
|
|
|
@@ -507,7 +515,11 @@ function UpdatesSettings() {
|
|
|
507
515
|
if (!data.success) {
|
|
508
516
|
setError(data.error || "Update failed");
|
|
509
517
|
} else {
|
|
510
|
-
|
|
518
|
+
const restartedCount = data.restarted?.length || 0;
|
|
519
|
+
const restartMsg = restartedCount > 0
|
|
520
|
+
? ` ${restartedCount} running agent${restartedCount > 1 ? 's' : ''} restarted.`
|
|
521
|
+
: '';
|
|
522
|
+
setUpdateSuccess(`Agent binary updated to v${data.version}.${restartMsg}`);
|
|
511
523
|
await checkForUpdates();
|
|
512
524
|
}
|
|
513
525
|
} catch (e) {
|
|
@@ -938,6 +950,7 @@ function DataSettings() {
|
|
|
938
950
|
const [clearing, setClearing] = useState(false);
|
|
939
951
|
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
|
940
952
|
const [eventCount, setEventCount] = useState<number | null>(null);
|
|
953
|
+
const { confirm, ConfirmDialog } = useConfirm();
|
|
941
954
|
|
|
942
955
|
const fetchStats = async () => {
|
|
943
956
|
try {
|
|
@@ -954,9 +967,8 @@ function DataSettings() {
|
|
|
954
967
|
}, []);
|
|
955
968
|
|
|
956
969
|
const clearTelemetry = async () => {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
970
|
+
const confirmed = await confirm("Are you sure you want to delete all telemetry data? This cannot be undone.", { confirmText: "Clear All", title: "Clear Telemetry Data" });
|
|
971
|
+
if (!confirmed) return;
|
|
960
972
|
|
|
961
973
|
setClearing(true);
|
|
962
974
|
setMessage(null);
|
|
@@ -979,6 +991,8 @@ function DataSettings() {
|
|
|
979
991
|
};
|
|
980
992
|
|
|
981
993
|
return (
|
|
994
|
+
<>
|
|
995
|
+
{ConfirmDialog}
|
|
982
996
|
<div className="max-w-4xl w-full">
|
|
983
997
|
<div className="mb-6">
|
|
984
998
|
<h1 className="text-2xl font-semibold mb-1">Data Management</h1>
|
|
@@ -1012,5 +1026,6 @@ function DataSettings() {
|
|
|
1012
1026
|
</button>
|
|
1013
1027
|
</div>
|
|
1014
1028
|
</div>
|
|
1029
|
+
</>
|
|
1015
1030
|
);
|
|
1016
1031
|
}
|
|
@@ -50,7 +50,7 @@ export function TasksPage({ onSelectAgent }: TasksPageProps) {
|
|
|
50
50
|
|
|
51
51
|
return (
|
|
52
52
|
<div className="flex-1 p-4 md:p-6 overflow-auto">
|
|
53
|
-
<div className="max-w-4xl
|
|
53
|
+
<div className="max-w-4xl">
|
|
54
54
|
<div className="mb-6">
|
|
55
55
|
<div className="mb-4">
|
|
56
56
|
<h1 className="text-xl md:text-2xl font-semibold mb-1">Tasks</h1>
|
package/src/web/index.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>
|
|
6
|
+
<title>Apteva</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
package/src/web/types.ts
CHANGED
|
@@ -51,8 +51,11 @@ export interface McpServer {
|
|
|
51
51
|
type: "npm" | "github" | "http" | "custom";
|
|
52
52
|
package: string | null;
|
|
53
53
|
command: string | null;
|
|
54
|
+
url: string | null;
|
|
55
|
+
headers: Record<string, string>;
|
|
54
56
|
port: number | null;
|
|
55
57
|
status: "stopped" | "running";
|
|
58
|
+
source: string | null; // "composio", "smithery", or null for local
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
export interface McpTool {
|
|
@@ -96,7 +99,7 @@ export interface OnboardingStatus {
|
|
|
96
99
|
has_any_keys: boolean;
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
export type Route = "dashboard" | "agents" | "tasks" | "mcp" | "telemetry" | "settings";
|
|
102
|
+
export type Route = "dashboard" | "agents" | "tasks" | "mcp" | "telemetry" | "settings" | "api";
|
|
100
103
|
|
|
101
104
|
export interface Task {
|
|
102
105
|
id: string;
|