apteva 0.4.31 → 0.4.41
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.7907h64p.js +3 -0
- package/dist/ApiDocsPage.k3jjenpq.js +4 -0
- package/dist/App.01nq20st.js +4 -0
- package/dist/App.1maqvamf.js +4 -0
- package/dist/App.2yjrh32f.js +4 -0
- package/dist/App.3qw8nben.js +20 -0
- package/dist/App.7fb3e7mp.js +4 -0
- package/dist/App.7sy3wq8c.js +4 -0
- package/dist/App.apjrmctz.js +57 -0
- package/dist/App.av6t2yhe.js +4 -0
- package/dist/App.jqj5a094.js +46 -0
- package/dist/App.mc7xf85h.js +4 -0
- package/dist/App.myxqcj9x.js +4 -0
- package/dist/App.nm91r1mp.js +13 -0
- package/dist/App.qcknavjz.js +221 -0
- package/dist/App.vc7vfhg4.js +4 -0
- package/dist/App.z4s9zkw5.js +4 -0
- package/dist/ConnectionsPage.z1pw5xe2.js +3 -0
- package/dist/McpPage.8vc97z0b.js +3 -0
- package/dist/SettingsPage.p61bz8kd.js +3 -0
- package/dist/SkillsPage.r9x43g3g.js +3 -0
- package/dist/TasksPage.1e0zkye4.js +3 -0
- package/dist/TelemetryPage.p9vbe4gf.js +3 -0
- package/dist/TestsPage.d4xy504e.js +3 -0
- package/dist/ThreadsPage.m016am3x.js +3 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +8 -7
- package/src/crypto.ts +4 -3
- package/src/db.ts +153 -28
- package/src/integrations/agentdojo.ts +94 -12
- package/src/integrations/index.ts +7 -0
- package/src/mcp-platform.ts +494 -121
- package/src/providers.ts +12 -12
- package/src/routes/api/agent-utils.ts +59 -46
- package/src/routes/api/agents.ts +52 -1
- package/src/routes/api/integrations.ts +11 -5
- package/src/routes/api/mcp.ts +5 -4
- package/src/routes/api/meta-agent.ts +35 -1
- package/src/routes/api/projects.ts +3 -3
- package/src/routes/api/providers.ts +121 -30
- package/src/routes/api/skills.ts +2 -3
- package/src/routes/api/system.ts +8 -13
- package/src/server.ts +31 -32
- package/src/triggers/agentdojo.ts +2 -2
- package/src/web/App.tsx +18 -10
- package/src/web/components/activity/ActivityPage.tsx +241 -388
- package/src/web/components/agents/AgentCard.tsx +5 -13
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/Select.tsx +4 -3
- package/src/web/components/dashboard/Dashboard.tsx +155 -30
- package/src/web/components/index.ts +1 -1
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +126 -35
- package/src/web/components/mcp/McpPage.tsx +10 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +4 -2
- package/src/web/components/settings/SettingsPage.tsx +133 -48
- package/src/web/components/tasks/TasksPage.tsx +48 -16
- package/src/web/components/telemetry/TelemetryPage.tsx +184 -0
- package/src/web/components/threads/ThreadsPage.tsx +313 -0
- package/src/web/context/AuthContext.tsx +3 -3
- package/src/web/context/ProjectContext.tsx +3 -3
- package/src/web/context/TelemetryContext.tsx +24 -6
- package/src/web/context/index.ts +1 -1
- package/src/web/styles.css +20 -4
- package/src/web/types.ts +4 -3
- package/dist/ActivityPage.41nbye4r.js +0 -3
- package/dist/ApiDocsPage.4smnt8m3.js +0 -4
- package/dist/App.0sbax9et.js +0 -4
- package/dist/App.0ws427h8.js +0 -4
- package/dist/App.6q6bar8b.js +0 -4
- package/dist/App.80301vdb.js +0 -4
- package/dist/App.af2wg84v.js +0 -267
- package/dist/App.ca1rz1ph.js +0 -4
- package/dist/App.ensa6z0r.js +0 -4
- package/dist/App.f8g7tych.js +0 -13
- package/dist/App.mvtqv6qc.js +0 -20
- package/dist/App.ncgc9cxy.js +0 -4
- package/dist/App.p0fb1pds.js +0 -4
- package/dist/App.pmaq48sj.js +0 -4
- package/dist/App.yv87t9m5.js +0 -4
- package/dist/App.zjmfm8p6.js +0 -4
- package/dist/ConnectionsPage.anb3rv9a.js +0 -3
- package/dist/McpPage.y396h6fy.js +0 -3
- package/dist/SettingsPage.p1hc60gk.js +0 -3
- package/dist/SkillsPage.yj3xdsay.js +0 -3
- package/dist/TasksPage.sjv0khtv.js +0 -3
- package/dist/TelemetryPage.2qm4w16r.js +0 -3
- package/dist/TestsPage.zzs4qfj8.js +0 -3
|
@@ -10,6 +10,12 @@ interface IntegrationApp {
|
|
|
10
10
|
logo: string | null;
|
|
11
11
|
categories: string[];
|
|
12
12
|
authSchemes: string[];
|
|
13
|
+
providerSlug?: string;
|
|
14
|
+
credentialFields?: {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
required?: boolean;
|
|
18
|
+
}[];
|
|
13
19
|
}
|
|
14
20
|
|
|
15
21
|
interface ConnectedAccount {
|
|
@@ -68,9 +74,10 @@ export function IntegrationsPanel({
|
|
|
68
74
|
const [error, setError] = useState<string | null>(null);
|
|
69
75
|
// For auth method selection (when app supports both OAuth and API Key)
|
|
70
76
|
const [authMethodModal, setAuthMethodModal] = useState<{ app: IntegrationApp } | null>(null);
|
|
71
|
-
// For API Key modal
|
|
77
|
+
// For API Key / credential modal
|
|
72
78
|
const [apiKeyModal, setApiKeyModal] = useState<{ app: IntegrationApp } | null>(null);
|
|
73
79
|
const [apiKeyInput, setApiKeyInput] = useState("");
|
|
80
|
+
const [credentialInputs, setCredentialInputs] = useState<Record<string, string>>({});
|
|
74
81
|
// For MCP config creation modal
|
|
75
82
|
const [mcpConfigModal, setMcpConfigModal] = useState<{ app: IntegrationApp } | null>(null);
|
|
76
83
|
const [mcpConfigName, setMcpConfigName] = useState("");
|
|
@@ -153,17 +160,18 @@ export function IntegrationsPanel({
|
|
|
153
160
|
}, [pendingConnection, authFetch, providerId, projectId, fetchData, onConnectionComplete]);
|
|
154
161
|
|
|
155
162
|
// Initiate connection
|
|
156
|
-
const connectApp = async (app: IntegrationApp, apiKey?: string, forceOAuth?: boolean) => {
|
|
163
|
+
const connectApp = async (app: IntegrationApp, apiKey?: string, forceOAuth?: boolean, fields?: Record<string, string>) => {
|
|
157
164
|
// If app supports multiple auth methods and user hasn't chosen, show choice
|
|
158
|
-
if (hasMultipleAuthMethods(app) && !apiKey && !forceOAuth) {
|
|
165
|
+
if (hasMultipleAuthMethods(app) && !apiKey && !fields && !forceOAuth) {
|
|
159
166
|
setAuthMethodModal({ app });
|
|
160
167
|
return;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
// If app supports API key (and user didn't choose OAuth), show
|
|
164
|
-
if (supportsApiKey(app) && !apiKey && !forceOAuth) {
|
|
170
|
+
// If app supports API key (and user didn't choose OAuth), show credential modal
|
|
171
|
+
if (supportsApiKey(app) && !apiKey && !fields && !forceOAuth) {
|
|
165
172
|
setApiKeyModal({ app });
|
|
166
173
|
setApiKeyInput("");
|
|
174
|
+
setCredentialInputs({});
|
|
167
175
|
return;
|
|
168
176
|
}
|
|
169
177
|
|
|
@@ -173,7 +181,13 @@ export function IntegrationsPanel({
|
|
|
173
181
|
try {
|
|
174
182
|
// Build request body
|
|
175
183
|
const body: any = { appSlug: app.slug };
|
|
176
|
-
if (
|
|
184
|
+
if (fields && Object.keys(fields).length > 0) {
|
|
185
|
+
// Multi-field credentials
|
|
186
|
+
body.credentials = {
|
|
187
|
+
authScheme: "API_KEY",
|
|
188
|
+
fields,
|
|
189
|
+
};
|
|
190
|
+
} else if (apiKey) {
|
|
177
191
|
body.credentials = {
|
|
178
192
|
authScheme: "API_KEY",
|
|
179
193
|
apiKey,
|
|
@@ -231,11 +245,21 @@ export function IntegrationsPanel({
|
|
|
231
245
|
}
|
|
232
246
|
};
|
|
233
247
|
|
|
234
|
-
// Handle API key form submission
|
|
248
|
+
// Handle API key / credential form submission
|
|
235
249
|
const handleApiKeySubmit = (e: React.FormEvent) => {
|
|
236
250
|
e.preventDefault();
|
|
237
|
-
if (!apiKeyModal
|
|
238
|
-
|
|
251
|
+
if (!apiKeyModal) return;
|
|
252
|
+
const hasFields = apiKeyModal.app.credentialFields && apiKeyModal.app.credentialFields.length > 0;
|
|
253
|
+
if (hasFields) {
|
|
254
|
+
// Check required fields are filled
|
|
255
|
+
const requiredFields = apiKeyModal.app.credentialFields!.filter(f => f.required !== false);
|
|
256
|
+
const allFilled = requiredFields.every(f => credentialInputs[f.name]?.trim());
|
|
257
|
+
if (!allFilled) return;
|
|
258
|
+
connectApp(apiKeyModal.app, undefined, false, credentialInputs);
|
|
259
|
+
} else {
|
|
260
|
+
if (!apiKeyInput.trim()) return;
|
|
261
|
+
connectApp(apiKeyModal.app, apiKeyInput.trim());
|
|
262
|
+
}
|
|
239
263
|
};
|
|
240
264
|
|
|
241
265
|
// Disconnect (called after confirmation)
|
|
@@ -291,6 +315,20 @@ export function IntegrationsPanel({
|
|
|
291
315
|
return;
|
|
292
316
|
}
|
|
293
317
|
|
|
318
|
+
// Auto-add the server locally
|
|
319
|
+
let autoAdded = false;
|
|
320
|
+
if (data.config?.id) {
|
|
321
|
+
try {
|
|
322
|
+
const addRes = await authFetch(
|
|
323
|
+
`/api/integrations/${providerId}/configs/${data.config.id}/add${projectParam}`,
|
|
324
|
+
{ method: "POST" }
|
|
325
|
+
);
|
|
326
|
+
autoAdded = addRes.ok;
|
|
327
|
+
} catch {
|
|
328
|
+
// Non-fatal — server was still created on the provider
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
294
332
|
setMcpConfigSuccess(mcpConfigName);
|
|
295
333
|
onConnectionComplete?.();
|
|
296
334
|
} catch (e) {
|
|
@@ -311,17 +349,23 @@ export function IntegrationsPanel({
|
|
|
311
349
|
});
|
|
312
350
|
};
|
|
313
351
|
|
|
314
|
-
// Check if app is connected
|
|
315
|
-
const isConnected = (
|
|
352
|
+
// Check if app is connected (also matches via providerSlug for multi-toolkit providers)
|
|
353
|
+
const isConnected = (app: IntegrationApp) => {
|
|
316
354
|
return connectedAccounts.some(
|
|
317
|
-
(a) => a.
|
|
355
|
+
(a) => a.status === "active" && (
|
|
356
|
+
a.appId === app.slug ||
|
|
357
|
+
(app.providerSlug && a.appId === app.providerSlug)
|
|
358
|
+
)
|
|
318
359
|
);
|
|
319
360
|
};
|
|
320
361
|
|
|
321
|
-
// Get connection for app (prefer active account)
|
|
322
|
-
const getConnection = (
|
|
323
|
-
return connectedAccounts.find((a) => a.appId ===
|
|
324
|
-
|| connectedAccounts.find((a) => a.appId ===
|
|
362
|
+
// Get connection for app (prefer active account, also matches via providerSlug)
|
|
363
|
+
const getConnection = (app: IntegrationApp) => {
|
|
364
|
+
return connectedAccounts.find((a) => a.appId === app.slug && a.status === "active")
|
|
365
|
+
|| (app.providerSlug && connectedAccounts.find((a) => a.appId === app.providerSlug && a.status === "active"))
|
|
366
|
+
|| connectedAccounts.find((a) => a.appId === app.slug)
|
|
367
|
+
|| (app.providerSlug && connectedAccounts.find((a) => a.appId === app.providerSlug))
|
|
368
|
+
|| undefined;
|
|
325
369
|
};
|
|
326
370
|
|
|
327
371
|
// Filter apps
|
|
@@ -337,8 +381,8 @@ export function IntegrationsPanel({
|
|
|
337
381
|
});
|
|
338
382
|
|
|
339
383
|
// Group by connected/not connected
|
|
340
|
-
const connectedApps = filteredApps.filter((app) => isConnected(app
|
|
341
|
-
const availableApps = filteredApps.filter((app) => !isConnected(app
|
|
384
|
+
const connectedApps = filteredApps.filter((app) => isConnected(app));
|
|
385
|
+
const availableApps = filteredApps.filter((app) => !isConnected(app));
|
|
342
386
|
|
|
343
387
|
if (loading) {
|
|
344
388
|
return <div className="text-center py-8 text-[#666]">Loading apps...</div>;
|
|
@@ -369,6 +413,7 @@ export function IntegrationsPanel({
|
|
|
369
413
|
setAuthMethodModal(null);
|
|
370
414
|
setApiKeyModal({ app: authMethodModal.app });
|
|
371
415
|
setApiKeyInput("");
|
|
416
|
+
setCredentialInputs({});
|
|
372
417
|
}}
|
|
373
418
|
className="w-full text-left p-3 bg-[#0a0a0a] hover:bg-[#1a1a1a] border border-[#333] hover:border-[#f97316] rounded-lg transition"
|
|
374
419
|
>
|
|
@@ -400,7 +445,7 @@ export function IntegrationsPanel({
|
|
|
400
445
|
</div>
|
|
401
446
|
)}
|
|
402
447
|
|
|
403
|
-
{/* API Key Modal */}
|
|
448
|
+
{/* API Key / Credentials Modal */}
|
|
404
449
|
{apiKeyModal && (
|
|
405
450
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
406
451
|
<div className="bg-[#111] border border-[#333] rounded-lg p-6 w-full max-w-md mx-4">
|
|
@@ -414,18 +459,46 @@ export function IntegrationsPanel({
|
|
|
414
459
|
)}
|
|
415
460
|
<div>
|
|
416
461
|
<h3 className="font-medium">Connect {apiKeyModal.app.name}</h3>
|
|
417
|
-
<p className="text-xs text-[#666]">
|
|
462
|
+
<p className="text-xs text-[#666]">
|
|
463
|
+
{apiKeyModal.app.credentialFields?.length
|
|
464
|
+
? "Enter your credentials to connect"
|
|
465
|
+
: "Enter your API key to connect"}
|
|
466
|
+
</p>
|
|
418
467
|
</div>
|
|
419
468
|
</div>
|
|
420
469
|
<form onSubmit={handleApiKeySubmit}>
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
470
|
+
{apiKeyModal.app.credentialFields && apiKeyModal.app.credentialFields.length > 0 ? (
|
|
471
|
+
<div className="space-y-3 mb-4">
|
|
472
|
+
{apiKeyModal.app.credentialFields.map((field, idx) => (
|
|
473
|
+
<div key={field.name}>
|
|
474
|
+
<label className="block text-xs text-[#888] mb-1">
|
|
475
|
+
{field.name.replace(/([A-Z])/g, " $1").replace(/[-_]/g, " ").replace(/\b\w/g, c => c.toUpperCase()).trim()}
|
|
476
|
+
{field.required !== false && <span className="text-red-400 ml-0.5">*</span>}
|
|
477
|
+
</label>
|
|
478
|
+
{field.description && (
|
|
479
|
+
<p className="text-[10px] text-[#555] mb-1">{field.description}</p>
|
|
480
|
+
)}
|
|
481
|
+
<input
|
|
482
|
+
type="password"
|
|
483
|
+
value={credentialInputs[field.name] || ""}
|
|
484
|
+
onChange={(e) => setCredentialInputs(prev => ({ ...prev, [field.name]: e.target.value }))}
|
|
485
|
+
placeholder={`Enter ${field.name}...`}
|
|
486
|
+
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-4 py-2 focus:outline-none focus:border-[#f97316]"
|
|
487
|
+
autoFocus={idx === 0}
|
|
488
|
+
/>
|
|
489
|
+
</div>
|
|
490
|
+
))}
|
|
491
|
+
</div>
|
|
492
|
+
) : (
|
|
493
|
+
<input
|
|
494
|
+
type="password"
|
|
495
|
+
value={apiKeyInput}
|
|
496
|
+
onChange={(e) => setApiKeyInput(e.target.value)}
|
|
497
|
+
placeholder="Enter API Key..."
|
|
498
|
+
className="w-full bg-[#0a0a0a] border border-[#333] rounded-lg px-4 py-2 mb-4 focus:outline-none focus:border-[#f97316]"
|
|
499
|
+
autoFocus
|
|
500
|
+
/>
|
|
501
|
+
)}
|
|
429
502
|
<div className="flex gap-2">
|
|
430
503
|
<button
|
|
431
504
|
type="button"
|
|
@@ -436,7 +509,12 @@ export function IntegrationsPanel({
|
|
|
436
509
|
</button>
|
|
437
510
|
<button
|
|
438
511
|
type="submit"
|
|
439
|
-
disabled={
|
|
512
|
+
disabled={
|
|
513
|
+
connecting === apiKeyModal.app.slug ||
|
|
514
|
+
(apiKeyModal.app.credentialFields?.length
|
|
515
|
+
? !apiKeyModal.app.credentialFields.filter(f => f.required !== false).every(f => credentialInputs[f.name]?.trim())
|
|
516
|
+
: !apiKeyInput.trim())
|
|
517
|
+
}
|
|
440
518
|
className="flex-1 text-sm bg-[#f97316] hover:bg-[#ea580c] text-white px-4 py-2 rounded transition disabled:opacity-50"
|
|
441
519
|
>
|
|
442
520
|
{connecting === apiKeyModal.app.slug ? "Connecting..." : "Connect"}
|
|
@@ -459,10 +537,7 @@ export function IntegrationsPanel({
|
|
|
459
537
|
</div>
|
|
460
538
|
<h3 className="font-medium text-lg">MCP Config Created!</h3>
|
|
461
539
|
<p className="text-sm text-[#888] mt-2">
|
|
462
|
-
"{mcpConfigSuccess}" has been created
|
|
463
|
-
</p>
|
|
464
|
-
<p className="text-xs text-[#666] mt-2">
|
|
465
|
-
You can now add it to your agents from the MCP Configs tab.
|
|
540
|
+
"{mcpConfigSuccess}" has been created and added to your servers.
|
|
466
541
|
</p>
|
|
467
542
|
</div>
|
|
468
543
|
<button
|
|
@@ -589,14 +664,19 @@ export function IntegrationsPanel({
|
|
|
589
664
|
<AppCard
|
|
590
665
|
key={app.id}
|
|
591
666
|
app={app}
|
|
592
|
-
connection={getConnection(app
|
|
667
|
+
connection={getConnection(app)}
|
|
593
668
|
onConnect={() => connectApp(app)}
|
|
594
669
|
onDisconnect={() => {
|
|
595
|
-
const conn = getConnection(app
|
|
670
|
+
const conn = getConnection(app);
|
|
596
671
|
if (conn) handleDisconnect(conn);
|
|
597
672
|
}}
|
|
598
673
|
onCreateMcpConfig={hideMcpConfig ? undefined : () => openMcpConfigModal(app)}
|
|
599
674
|
onBrowseTriggers={onBrowseTriggers ? () => onBrowseTriggers(app.slug) : undefined}
|
|
675
|
+
onUpdateKey={supportsApiKey(app) ? () => {
|
|
676
|
+
setApiKeyModal({ app });
|
|
677
|
+
setApiKeyInput("");
|
|
678
|
+
setCredentialInputs({});
|
|
679
|
+
} : undefined}
|
|
600
680
|
connecting={connecting === app.slug}
|
|
601
681
|
/>
|
|
602
682
|
))}
|
|
@@ -643,6 +723,7 @@ function AppCard({
|
|
|
643
723
|
onDisconnect,
|
|
644
724
|
onCreateMcpConfig,
|
|
645
725
|
onBrowseTriggers,
|
|
726
|
+
onUpdateKey,
|
|
646
727
|
connecting,
|
|
647
728
|
}: {
|
|
648
729
|
app: IntegrationApp;
|
|
@@ -651,6 +732,7 @@ function AppCard({
|
|
|
651
732
|
onDisconnect?: () => void;
|
|
652
733
|
onCreateMcpConfig?: () => void;
|
|
653
734
|
onBrowseTriggers?: () => void;
|
|
735
|
+
onUpdateKey?: () => void;
|
|
654
736
|
connecting: boolean;
|
|
655
737
|
}) {
|
|
656
738
|
const isConnected = connection?.status === "active";
|
|
@@ -739,6 +821,15 @@ function AppCard({
|
|
|
739
821
|
Browse Triggers
|
|
740
822
|
</button>
|
|
741
823
|
)}
|
|
824
|
+
{onUpdateKey && (
|
|
825
|
+
<button
|
|
826
|
+
onClick={onUpdateKey}
|
|
827
|
+
className="text-xs text-[#666] hover:text-[#f97316] transition px-2"
|
|
828
|
+
title="Update API Key"
|
|
829
|
+
>
|
|
830
|
+
Key
|
|
831
|
+
</button>
|
|
832
|
+
)}
|
|
742
833
|
{onDisconnect && (
|
|
743
834
|
<button
|
|
744
835
|
onClick={onDisconnect}
|
|
@@ -251,7 +251,16 @@ export function McpPage() {
|
|
|
251
251
|
onStart={() => startServer(server.id)}
|
|
252
252
|
onStop={() => stopServer(server.id)}
|
|
253
253
|
onDelete={() => deleteServer(server.id)}
|
|
254
|
-
onEdit={() =>
|
|
254
|
+
onEdit={async () => {
|
|
255
|
+
// Fetch full server details (with decrypted env/headers) for editing
|
|
256
|
+
try {
|
|
257
|
+
const res = await authFetch(`/api/mcp/servers/${server.id}`);
|
|
258
|
+
const data = await res.json();
|
|
259
|
+
setEditingServer(data.server || server);
|
|
260
|
+
} catch {
|
|
261
|
+
setEditingServer(server);
|
|
262
|
+
}
|
|
263
|
+
}}
|
|
255
264
|
/>
|
|
256
265
|
);
|
|
257
266
|
})}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useState, useEffect, useMemo, createContext, useContext, type ReactNode } from "react";
|
|
1
|
+
import React, { useState, useEffect, useMemo, useCallback, createContext, useContext, type ReactNode } from "react";
|
|
2
2
|
import { Chat } from "@apteva/apteva-kit";
|
|
3
|
-
import { useAuth, useProjects } from "../../context";
|
|
3
|
+
import { useAuth, useProjects, useTriggerRefresh } from "../../context";
|
|
4
4
|
|
|
5
5
|
interface MetaAgentStatus {
|
|
6
6
|
enabled: boolean;
|
|
@@ -130,6 +130,7 @@ export function MetaAgentButton() {
|
|
|
130
130
|
export function MetaAgentPanel() {
|
|
131
131
|
const ctx = useMetaAgent();
|
|
132
132
|
const { currentProjectId, currentProject } = useProjects();
|
|
133
|
+
const triggerRefresh = useTriggerRefresh();
|
|
133
134
|
if (!ctx?.isAvailable || !ctx.isOpen) return null;
|
|
134
135
|
|
|
135
136
|
const { agent, isRunning, error, isStarting, startAgent, close } = ctx;
|
|
@@ -181,6 +182,7 @@ export function MetaAgentPanel() {
|
|
|
181
182
|
variant="terminal"
|
|
182
183
|
showHeader={false}
|
|
183
184
|
context={chatContext}
|
|
185
|
+
onToolResult={triggerRefresh}
|
|
184
186
|
/>
|
|
185
187
|
) : (
|
|
186
188
|
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
|
|
@@ -406,7 +406,7 @@ function ProvidersSettings() {
|
|
|
406
406
|
|
|
407
407
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
408
408
|
{browserProviders.map(provider => (
|
|
409
|
-
<
|
|
409
|
+
<IntegrationKeyCard
|
|
410
410
|
key={provider.id}
|
|
411
411
|
provider={provider}
|
|
412
412
|
isEditing={selectedProvider === provider.id}
|
|
@@ -423,14 +423,14 @@ function ProvidersSettings() {
|
|
|
423
423
|
onCancelEdit={() => {
|
|
424
424
|
setSelectedProvider(null);
|
|
425
425
|
setApiKey("");
|
|
426
|
-
setExtraField("");
|
|
427
426
|
setError(null);
|
|
428
427
|
}}
|
|
429
428
|
onApiKeyChange={setApiKey}
|
|
430
429
|
onSave={saveKey}
|
|
431
430
|
onDelete={() => deleteKey(provider.id)}
|
|
432
|
-
|
|
433
|
-
|
|
431
|
+
projectsEnabled={projectsEnabled}
|
|
432
|
+
projects={projects}
|
|
433
|
+
onRefresh={fetchProviders}
|
|
434
434
|
/>
|
|
435
435
|
))}
|
|
436
436
|
</div>
|
|
@@ -956,20 +956,47 @@ function ProviderKeyCard({
|
|
|
956
956
|
onExtraFieldChange,
|
|
957
957
|
}: ProviderKeyCardProps) {
|
|
958
958
|
const isOllama = provider.id === "ollama";
|
|
959
|
-
const
|
|
959
|
+
const isCDP = provider.id === "cdp";
|
|
960
|
+
const isUrlBased = isOllama || isCDP;
|
|
960
961
|
const isBrowser = provider.type === "browser";
|
|
961
962
|
const isMultiField = provider.id === "browserbase";
|
|
962
|
-
const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number } | null>(null);
|
|
963
|
+
const [ollamaStatus, setOllamaStatus] = React.useState<{ connected: boolean; modelCount?: number; isDocker?: boolean } | null>(null);
|
|
964
|
+
const [installing, setInstalling] = React.useState(false);
|
|
965
|
+
const [installResult, setInstallResult] = React.useState<{ success: boolean; message: string } | null>(null);
|
|
966
|
+
|
|
967
|
+
// Check Ollama status when configured or after install
|
|
968
|
+
const checkOllamaStatus = React.useCallback(() => {
|
|
969
|
+
fetch("/api/providers/ollama/status")
|
|
970
|
+
.then(res => res.json())
|
|
971
|
+
.then(data => setOllamaStatus({ connected: data.connected, modelCount: data.modelCount, isDocker: data.isDocker }))
|
|
972
|
+
.catch(() => setOllamaStatus({ connected: false }));
|
|
973
|
+
}, []);
|
|
963
974
|
|
|
964
|
-
// Check Ollama status when configured
|
|
965
975
|
React.useEffect(() => {
|
|
966
|
-
if (isOllama
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
976
|
+
if (isOllama) {
|
|
977
|
+
checkOllamaStatus();
|
|
978
|
+
}
|
|
979
|
+
}, [isOllama, provider.hasKey, checkOllamaStatus]);
|
|
980
|
+
|
|
981
|
+
const handleInstallOllama = async () => {
|
|
982
|
+
setInstalling(true);
|
|
983
|
+
setInstallResult(null);
|
|
984
|
+
try {
|
|
985
|
+
const res = await fetch("/api/providers/ollama/install", { method: "POST" });
|
|
986
|
+
const data = await res.json();
|
|
987
|
+
if (data.success) {
|
|
988
|
+
setInstallResult({ success: true, message: data.message });
|
|
989
|
+
// Auto-save the default URL and refresh status
|
|
990
|
+
checkOllamaStatus();
|
|
991
|
+
} else {
|
|
992
|
+
setInstallResult({ success: false, message: data.error || "Installation failed" });
|
|
993
|
+
}
|
|
994
|
+
} catch {
|
|
995
|
+
setInstallResult({ success: false, message: "Failed to connect to server" });
|
|
996
|
+
} finally {
|
|
997
|
+
setInstalling(false);
|
|
971
998
|
}
|
|
972
|
-
}
|
|
999
|
+
};
|
|
973
1000
|
|
|
974
1001
|
return (
|
|
975
1002
|
<div className={`bg-[#111] border rounded-lg p-4 ${
|
|
@@ -1049,22 +1076,17 @@ function ProviderKeyCard({
|
|
|
1049
1076
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1050
1077
|
placeholder={isOllama
|
|
1051
1078
|
? "http://localhost:11434"
|
|
1052
|
-
:
|
|
1053
|
-
|
|
1054
|
-
: provider.id === "chrome"
|
|
1055
|
-
? "http://localhost:9222"
|
|
1056
|
-
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
1079
|
+
: isCDP ? "ws://localhost:9222"
|
|
1080
|
+
: provider.hasKey ? "Enter new API key..." : "Enter API key..."}
|
|
1057
1081
|
autoFocus
|
|
1058
1082
|
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
1059
1083
|
/>
|
|
1060
1084
|
)}
|
|
1061
1085
|
{isUrlBased && (
|
|
1062
1086
|
<p className="text-xs text-[#666]">
|
|
1063
|
-
{
|
|
1064
|
-
? "Enter
|
|
1065
|
-
:
|
|
1066
|
-
? "Enter your BrowserEngine service URL (e.g., http://localhost:8098)"
|
|
1067
|
-
: "Enter your Chrome DevTools URL (e.g., http://localhost:9222)"}
|
|
1087
|
+
{isCDP
|
|
1088
|
+
? "Enter the CDP URL of your browser (e.g., ws://localhost:9222)"
|
|
1089
|
+
: "Enter your Ollama server URL. Default is http://localhost:11434"}
|
|
1068
1090
|
</p>
|
|
1069
1091
|
)}
|
|
1070
1092
|
{error && <p className="text-red-400 text-sm">{error}</p>}
|
|
@@ -1086,7 +1108,24 @@ function ProviderKeyCard({
|
|
|
1086
1108
|
</div>
|
|
1087
1109
|
</div>
|
|
1088
1110
|
) : provider.hasKey ? (
|
|
1089
|
-
<div
|
|
1111
|
+
<div>
|
|
1112
|
+
{isOllama && ollamaStatus && !ollamaStatus.connected && !ollamaStatus.isDocker && (
|
|
1113
|
+
<div className="mb-3">
|
|
1114
|
+
<button
|
|
1115
|
+
onClick={handleInstallOllama}
|
|
1116
|
+
disabled={installing}
|
|
1117
|
+
className="w-full px-3 py-1.5 bg-yellow-500/20 text-yellow-400 hover:bg-yellow-500/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
|
|
1118
|
+
>
|
|
1119
|
+
{installing ? "Starting Ollama..." : "Start Ollama"}
|
|
1120
|
+
</button>
|
|
1121
|
+
{installResult && (
|
|
1122
|
+
<p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
|
|
1123
|
+
{installResult.message}
|
|
1124
|
+
</p>
|
|
1125
|
+
)}
|
|
1126
|
+
</div>
|
|
1127
|
+
)}
|
|
1128
|
+
<div className="flex items-center justify-between">
|
|
1090
1129
|
{provider.docsUrl ? (
|
|
1091
1130
|
<a
|
|
1092
1131
|
href={provider.docsUrl}
|
|
@@ -1094,7 +1133,7 @@ function ProviderKeyCard({
|
|
|
1094
1133
|
rel="noopener noreferrer"
|
|
1095
1134
|
className="text-sm text-[#3b82f6] hover:underline"
|
|
1096
1135
|
>
|
|
1097
|
-
{isOllama ? "
|
|
1136
|
+
{isOllama ? "Ollama docs" : "View docs"}
|
|
1098
1137
|
</a>
|
|
1099
1138
|
) : (
|
|
1100
1139
|
<span />
|
|
@@ -1113,27 +1152,46 @@ function ProviderKeyCard({
|
|
|
1113
1152
|
Remove
|
|
1114
1153
|
</button>
|
|
1115
1154
|
</div>
|
|
1155
|
+
</div>
|
|
1116
1156
|
</div>
|
|
1117
1157
|
) : (
|
|
1118
|
-
<div
|
|
1119
|
-
{
|
|
1120
|
-
<
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1158
|
+
<div>
|
|
1159
|
+
{isOllama && !ollamaStatus?.isDocker && (
|
|
1160
|
+
<div className="mb-3">
|
|
1161
|
+
<button
|
|
1162
|
+
onClick={handleInstallOllama}
|
|
1163
|
+
disabled={installing}
|
|
1164
|
+
className="w-full px-3 py-2 bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30 rounded text-sm font-medium transition disabled:opacity-50 disabled:cursor-wait"
|
|
1165
|
+
>
|
|
1166
|
+
{installing ? "Installing Ollama..." : ollamaStatus?.connected ? "Ollama Running" : "Install Ollama"}
|
|
1167
|
+
</button>
|
|
1168
|
+
{installResult && (
|
|
1169
|
+
<p className={`text-xs mt-1.5 ${installResult.success ? "text-green-400" : "text-red-400"}`}>
|
|
1170
|
+
{installResult.message}
|
|
1171
|
+
</p>
|
|
1172
|
+
)}
|
|
1173
|
+
</div>
|
|
1130
1174
|
)}
|
|
1131
|
-
<
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1175
|
+
<div className="flex items-center justify-between">
|
|
1176
|
+
{provider.docsUrl ? (
|
|
1177
|
+
<a
|
|
1178
|
+
href={provider.docsUrl}
|
|
1179
|
+
target="_blank"
|
|
1180
|
+
rel="noopener noreferrer"
|
|
1181
|
+
className="text-sm text-[#3b82f6] hover:underline"
|
|
1182
|
+
>
|
|
1183
|
+
{isOllama ? "Manual install" : isBrowser ? "View docs" : "Get API key"}
|
|
1184
|
+
</a>
|
|
1185
|
+
) : (
|
|
1186
|
+
<span />
|
|
1187
|
+
)}
|
|
1188
|
+
<button
|
|
1189
|
+
onClick={onStartEdit}
|
|
1190
|
+
className="text-sm text-[#f97316] hover:text-[#fb923c]"
|
|
1191
|
+
>
|
|
1192
|
+
{isUrlBased ? "Configure" : "+ Add key"}
|
|
1193
|
+
</button>
|
|
1194
|
+
</div>
|
|
1137
1195
|
</div>
|
|
1138
1196
|
)}
|
|
1139
1197
|
</div>
|
|
@@ -1180,8 +1238,11 @@ function IntegrationKeyCard({
|
|
|
1180
1238
|
const [expanded, setExpanded] = useState(false);
|
|
1181
1239
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
1182
1240
|
const [localSaving, setLocalSaving] = useState(false);
|
|
1241
|
+
const [bbProjectId, setBbProjectId] = useState(""); // Browserbase project ID (their internal ID)
|
|
1183
1242
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
1184
1243
|
|
|
1244
|
+
const isBrowserbase = provider.id === "browserbase";
|
|
1245
|
+
|
|
1185
1246
|
// Fetch all keys for this provider
|
|
1186
1247
|
const fetchKeys = async () => {
|
|
1187
1248
|
try {
|
|
@@ -1212,12 +1273,18 @@ function IntegrationKeyCard({
|
|
|
1212
1273
|
setLocalSaving(true);
|
|
1213
1274
|
setLocalError(null);
|
|
1214
1275
|
|
|
1276
|
+
// For Browserbase, combine API key + BB project ID into JSON
|
|
1277
|
+
let keyToSave = apiKey;
|
|
1278
|
+
if (isBrowserbase && bbProjectId) {
|
|
1279
|
+
keyToSave = JSON.stringify({ api_key: apiKey, project_id: bbProjectId });
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1215
1282
|
try {
|
|
1216
1283
|
const res = await authFetch(`/api/keys/${provider.id}`, {
|
|
1217
1284
|
method: "POST",
|
|
1218
1285
|
headers: { "Content-Type": "application/json" },
|
|
1219
1286
|
body: JSON.stringify({
|
|
1220
|
-
key:
|
|
1287
|
+
key: keyToSave,
|
|
1221
1288
|
project_id: selectedProjectId || null,
|
|
1222
1289
|
}),
|
|
1223
1290
|
});
|
|
@@ -1226,6 +1293,7 @@ function IntegrationKeyCard({
|
|
|
1226
1293
|
|
|
1227
1294
|
if (res.ok) {
|
|
1228
1295
|
onApiKeyChange("");
|
|
1296
|
+
setBbProjectId("");
|
|
1229
1297
|
setSelectedProjectId("");
|
|
1230
1298
|
onCancelEdit();
|
|
1231
1299
|
fetchKeys();
|
|
@@ -1288,10 +1356,10 @@ function IntegrationKeyCard({
|
|
|
1288
1356
|
{isEditing ? (
|
|
1289
1357
|
<div className="space-y-3">
|
|
1290
1358
|
<input
|
|
1291
|
-
type=
|
|
1359
|
+
type={inputType}
|
|
1292
1360
|
value={apiKey}
|
|
1293
1361
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1294
|
-
placeholder={provider.hasKey ?
|
|
1362
|
+
placeholder={provider.hasKey ? `Enter new ${isUrlBased ? "URL" : "API key"}...` : inputPlaceholder}
|
|
1295
1363
|
autoFocus
|
|
1296
1364
|
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
1297
1365
|
/>
|
|
@@ -1361,6 +1429,13 @@ function IntegrationKeyCard({
|
|
|
1361
1429
|
);
|
|
1362
1430
|
}
|
|
1363
1431
|
|
|
1432
|
+
// Determine input type and placeholder based on provider
|
|
1433
|
+
const isUrlBased = provider.isLocal;
|
|
1434
|
+
const inputType = isUrlBased ? "text" : "password";
|
|
1435
|
+
const inputPlaceholder = isUrlBased
|
|
1436
|
+
? (provider.id === "cdp" ? "ws://localhost:9222" : "http://localhost:11434")
|
|
1437
|
+
: "Enter API key...";
|
|
1438
|
+
|
|
1364
1439
|
// Enhanced view with project support
|
|
1365
1440
|
return (
|
|
1366
1441
|
<>
|
|
@@ -1441,14 +1516,24 @@ function IntegrationKeyCard({
|
|
|
1441
1516
|
{isEditing ? (
|
|
1442
1517
|
<div className="space-y-3">
|
|
1443
1518
|
<input
|
|
1444
|
-
type=
|
|
1519
|
+
type={inputType}
|
|
1445
1520
|
value={apiKey}
|
|
1446
1521
|
onChange={e => onApiKeyChange(e.target.value)}
|
|
1447
|
-
placeholder=
|
|
1522
|
+
placeholder={inputPlaceholder}
|
|
1448
1523
|
autoFocus
|
|
1449
1524
|
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316]"
|
|
1450
1525
|
/>
|
|
1451
1526
|
|
|
1527
|
+
{isBrowserbase && (
|
|
1528
|
+
<input
|
|
1529
|
+
type="text"
|
|
1530
|
+
value={bbProjectId}
|
|
1531
|
+
onChange={e => setBbProjectId(e.target.value)}
|
|
1532
|
+
placeholder="Browserbase Project ID (optional)"
|
|
1533
|
+
className="w-full bg-[#0a0a0a] border border-[#333] rounded px-3 py-2 focus:outline-none focus:border-[#f97316] text-sm"
|
|
1534
|
+
/>
|
|
1535
|
+
)}
|
|
1536
|
+
|
|
1452
1537
|
<Select
|
|
1453
1538
|
value={selectedProjectId}
|
|
1454
1539
|
onChange={setSelectedProjectId}
|