apteva 0.4.20 → 0.4.26
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.cycn14ck.js +3 -0
- package/dist/{ApiDocsPage.kf6bbwkk.js → ApiDocsPage.3q5x9hhg.js} +2 -2
- package/dist/App.0wwyytz2.js +4 -0
- package/dist/{App.c90t3dxg.js → App.2prdcxgq.js} +3 -3
- package/dist/{App.2yy66bnp.js → App.40azyqz6.js} +3 -3
- package/dist/App.6ftxk387.js +4 -0
- package/dist/{App.jfx3der4.js → App.9bzz8dqh.js} +3 -3
- package/dist/App.a7h91mxr.js +4 -0
- package/dist/{App.7v1w3ys9.js → App.e54ynjf2.js} +3 -3
- package/dist/{App.edwahsvz.js → App.fq11mvc7.js} +2 -2
- package/dist/{App.2jmkqm8c.js → App.h6k4j1w9.js} +3 -3
- package/dist/App.jq5tmjws.js +267 -0
- package/dist/{App.q3bpx15d.js → App.k377qek6.js} +2 -2
- package/dist/{App.039re6cf.js → App.r2c5nw36.js} +3 -3
- package/dist/{App.n4jb3c22.js → App.sb2fg71h.js} +3 -3
- package/dist/App.wnap3h7r.js +4 -0
- package/dist/ConnectionsPage.6fyhqfhz.js +3 -0
- package/dist/McpPage.hk2qt1qt.js +3 -0
- package/dist/SettingsPage.gwpx9v7v.js +3 -0
- package/dist/SkillsPage.j5zech2z.js +3 -0
- package/dist/TasksPage.65dcf4vw.js +3 -0
- package/dist/TelemetryPage.07xrbd7k.js +3 -0
- package/dist/TestsPage.q6zfephf.js +3 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +2 -2
- package/src/integrations/agentdojo.ts +1 -1
- package/src/providers.ts +2 -0
- package/src/routes/api/triggers.ts +45 -5
- package/src/web/App.tsx +1 -0
- package/src/web/components/activity/ActivityPage.tsx +347 -212
- package/src/web/components/agents/AgentCard.tsx +32 -3
- package/src/web/components/agents/AgentPanel.tsx +188 -4
- package/src/web/components/connections/IntegrationsTab.tsx +57 -31
- package/src/web/components/connections/TriggersTab.tsx +336 -159
- package/src/web/components/dashboard/Dashboard.tsx +39 -7
- package/src/web/components/layout/Header.tsx +0 -34
- package/src/web/components/layout/Sidebar.tsx +43 -3
- package/src/web/components/tasks/TasksPage.tsx +32 -6
- package/dist/ActivityPage.h769ek3a.js +0 -3
- package/dist/App.3515wsb4.js +0 -4
- package/dist/App.r0a2nmqs.js +0 -267
- package/dist/App.s2yrcz15.js +0 -4
- package/dist/App.s5j82a5j.js +0 -4
- package/dist/App.tg1b94tx.js +0 -4
- package/dist/ConnectionsPage.a67fjgbf.js +0 -3
- package/dist/McpPage.d4p3xvtk.js +0 -3
- package/dist/SettingsPage.46sqpe39.js +0 -3
- package/dist/SkillsPage.j9hkqm99.js +0 -3
- package/dist/TasksPage.6pvkb7s7.js +0 -3
- package/dist/TelemetryPage.5zq9msb5.js +0 -3
- package/dist/TestsPage.24432yqt.js +0 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, SkillsIcon } from "../common/Icons";
|
|
3
|
-
import { useAgentActivity, useProjects } from "../../context";
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon, SkillsIcon, ActivityIcon } from "../common/Icons";
|
|
3
|
+
import { useAgentActivity, useProjects, useAuth } from "../../context";
|
|
4
4
|
import type { Agent, AgentFeatures } from "../../types";
|
|
5
5
|
|
|
6
6
|
interface AgentCardProps {
|
|
@@ -28,7 +28,16 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
28
28
|
const skills = agent.skillDetails || [];
|
|
29
29
|
const { isActive, type } = useAgentActivity(agent.id);
|
|
30
30
|
const { projects } = useProjects();
|
|
31
|
+
const { authFetch } = useAuth();
|
|
31
32
|
const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
|
|
33
|
+
const [subscriptions, setSubscriptions] = useState<{ id: string; trigger_slug: string; enabled: boolean }[]>([]);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
authFetch(`/api/subscriptions?agent_id=${agent.id}`)
|
|
37
|
+
.then(res => res.ok ? res.json() : { subscriptions: [] })
|
|
38
|
+
.then(data => setSubscriptions(data.subscriptions || []))
|
|
39
|
+
.catch(() => {});
|
|
40
|
+
}, [agent.id, authFetch]);
|
|
32
41
|
|
|
33
42
|
return (
|
|
34
43
|
<div
|
|
@@ -115,6 +124,26 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
115
124
|
</div>
|
|
116
125
|
)}
|
|
117
126
|
|
|
127
|
+
{/* Subscriptions (triggers listening to) */}
|
|
128
|
+
{subscriptions.length > 0 && (
|
|
129
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
130
|
+
{subscriptions.map((sub) => (
|
|
131
|
+
<span
|
|
132
|
+
key={sub.id}
|
|
133
|
+
className={`inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs ${
|
|
134
|
+
sub.enabled
|
|
135
|
+
? "bg-cyan-500/10 text-cyan-400"
|
|
136
|
+
: "bg-[#222] text-[#666]"
|
|
137
|
+
}`}
|
|
138
|
+
title={`Trigger: ${sub.trigger_slug.replace(/_/g, " ")}`}
|
|
139
|
+
>
|
|
140
|
+
<ActivityIcon className="w-3 h-3" />
|
|
141
|
+
{sub.trigger_slug.replace(/_/g, " ")}
|
|
142
|
+
</span>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
|
|
118
147
|
<p className="text-sm text-[#666] line-clamp-2 mb-4 flex-1">
|
|
119
148
|
{agent.systemPrompt}
|
|
120
149
|
</p>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
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
|
-
import { formatCron, formatRelativeTime } from "../tasks/TasksPage";
|
|
4
|
+
import { formatCron, formatRelativeTime, TrajectoryView } from "../tasks/TasksPage";
|
|
5
5
|
import { Select } from "../common/Select";
|
|
6
6
|
import { useConfirm } from "../common/Modal";
|
|
7
7
|
import { useTelemetry } from "../../context";
|
|
8
8
|
import { useAuth } from "../../context";
|
|
9
|
-
import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, AgentMode, MultiAgentConfig } from "../../types";
|
|
9
|
+
import type { Agent, Provider, AgentFeatures, McpServer, SkillSummary, AgentMode, MultiAgentConfig, Task } from "../../types";
|
|
10
10
|
import { getMultiAgentConfig } from "../../types";
|
|
11
11
|
|
|
12
12
|
type Tab = "chat" | "threads" | "tasks" | "memory" | "files" | "settings";
|
|
@@ -314,10 +314,13 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
function TasksTab({ agent }: { agent: Agent }) {
|
|
317
|
-
const
|
|
317
|
+
const { authFetch } = useAuth();
|
|
318
|
+
const [tasks, setTasks] = useState<Task[]>([]);
|
|
318
319
|
const [loading, setLoading] = useState(true);
|
|
319
320
|
const [error, setError] = useState<string | null>(null);
|
|
320
321
|
const [filter, setFilter] = useState<string>("all");
|
|
322
|
+
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
|
323
|
+
const [loadingTask, setLoadingTask] = useState(false);
|
|
321
324
|
const { events } = useTelemetry({ agent_id: agent.id, category: "task" });
|
|
322
325
|
|
|
323
326
|
// Reset state when agent changes
|
|
@@ -325,6 +328,7 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
325
328
|
setTasks([]);
|
|
326
329
|
setError(null);
|
|
327
330
|
setLoading(true);
|
|
331
|
+
setSelectedTask(null);
|
|
328
332
|
}, [agent.id]);
|
|
329
333
|
|
|
330
334
|
const fetchTasks = async () => {
|
|
@@ -346,6 +350,24 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
346
350
|
}
|
|
347
351
|
};
|
|
348
352
|
|
|
353
|
+
const selectTask = async (task: Task) => {
|
|
354
|
+
setSelectedTask(task);
|
|
355
|
+
setLoadingTask(true);
|
|
356
|
+
try {
|
|
357
|
+
const res = await authFetch(`/api/tasks/${task.agentId || agent.id}/${task.id}`);
|
|
358
|
+
if (res.ok) {
|
|
359
|
+
const data = await res.json();
|
|
360
|
+
if (data.task) {
|
|
361
|
+
setSelectedTask({ ...data.task, agentId: task.agentId || agent.id, agentName: task.agentName || agent.name });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} catch (e) {
|
|
365
|
+
console.error("Failed to fetch task details:", e);
|
|
366
|
+
} finally {
|
|
367
|
+
setLoadingTask(false);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
349
371
|
// Refetch when agent changes, filter changes, or task telemetry arrives
|
|
350
372
|
useEffect(() => {
|
|
351
373
|
setLoading(true);
|
|
@@ -403,6 +425,133 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
403
425
|
{ value: "failed", label: "Failed" },
|
|
404
426
|
];
|
|
405
427
|
|
|
428
|
+
// Show task detail view when a task is selected
|
|
429
|
+
if (selectedTask) {
|
|
430
|
+
return (
|
|
431
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
432
|
+
{/* Back button */}
|
|
433
|
+
<div className="px-4 pt-3 pb-2 border-b border-[#1a1a1a] shrink-0">
|
|
434
|
+
<button
|
|
435
|
+
onClick={() => setSelectedTask(null)}
|
|
436
|
+
className="text-sm text-[#666] hover:text-[#e0e0e0] transition flex items-center gap-1"
|
|
437
|
+
>
|
|
438
|
+
<span>←</span> Back to tasks
|
|
439
|
+
</button>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{/* Task detail content */}
|
|
443
|
+
<div className="flex-1 overflow-auto p-4 space-y-4">
|
|
444
|
+
{/* Title & Status */}
|
|
445
|
+
<div>
|
|
446
|
+
<div className="flex items-start justify-between gap-2 mb-1">
|
|
447
|
+
<h3 className="text-lg font-medium">{selectedTask.title}</h3>
|
|
448
|
+
<span className={`px-2 py-1 rounded text-xs font-medium flex-shrink-0 ${statusColors[selectedTask.status]}`}>
|
|
449
|
+
{selectedTask.status}
|
|
450
|
+
</span>
|
|
451
|
+
</div>
|
|
452
|
+
</div>
|
|
453
|
+
|
|
454
|
+
{/* Description */}
|
|
455
|
+
{selectedTask.description && (
|
|
456
|
+
<div>
|
|
457
|
+
<h4 className="text-xs text-[#666] uppercase tracking-wider mb-1">Description</h4>
|
|
458
|
+
<p className="text-sm text-[#888] whitespace-pre-wrap">{selectedTask.description}</p>
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
|
|
462
|
+
{/* Metadata */}
|
|
463
|
+
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
464
|
+
<div>
|
|
465
|
+
<span className="text-[#666]">Type</span>
|
|
466
|
+
<p className="capitalize">{selectedTask.type}</p>
|
|
467
|
+
</div>
|
|
468
|
+
<div>
|
|
469
|
+
<span className="text-[#666]">Priority</span>
|
|
470
|
+
<p>{selectedTask.priority}</p>
|
|
471
|
+
</div>
|
|
472
|
+
{selectedTask.recurrence && (
|
|
473
|
+
<div>
|
|
474
|
+
<span className="text-[#666]">Recurrence</span>
|
|
475
|
+
<p>{formatCron(selectedTask.recurrence)}</p>
|
|
476
|
+
<p className="text-xs text-[#444] mt-0.5 font-mono">{selectedTask.recurrence}</p>
|
|
477
|
+
</div>
|
|
478
|
+
)}
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
{/* Timestamps */}
|
|
482
|
+
<div className="space-y-2 text-sm">
|
|
483
|
+
<div className="flex justify-between">
|
|
484
|
+
<span className="text-[#666]">Created</span>
|
|
485
|
+
<span>{new Date(selectedTask.created_at).toLocaleString()}</span>
|
|
486
|
+
</div>
|
|
487
|
+
{selectedTask.execute_at && (
|
|
488
|
+
<div className="flex justify-between">
|
|
489
|
+
<span className="text-[#666]">Scheduled</span>
|
|
490
|
+
<span className="text-[#f97316]">{formatRelativeTime(selectedTask.execute_at)}</span>
|
|
491
|
+
</div>
|
|
492
|
+
)}
|
|
493
|
+
{selectedTask.executed_at && (
|
|
494
|
+
<div className="flex justify-between">
|
|
495
|
+
<span className="text-[#666]">Started</span>
|
|
496
|
+
<span>{new Date(selectedTask.executed_at).toLocaleString()}</span>
|
|
497
|
+
</div>
|
|
498
|
+
)}
|
|
499
|
+
{selectedTask.completed_at && (
|
|
500
|
+
<div className="flex justify-between">
|
|
501
|
+
<span className="text-[#666]">Completed</span>
|
|
502
|
+
<span>{new Date(selectedTask.completed_at).toLocaleString()}</span>
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
{selectedTask.next_run && (
|
|
506
|
+
<div className="flex justify-between">
|
|
507
|
+
<span className="text-[#666]">Next Run</span>
|
|
508
|
+
<span className="text-[#f97316]">{formatRelativeTime(selectedTask.next_run)}</span>
|
|
509
|
+
</div>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
|
|
513
|
+
{/* Error */}
|
|
514
|
+
{selectedTask.status === "failed" && selectedTask.error && (
|
|
515
|
+
<div className="min-w-0">
|
|
516
|
+
<h4 className="text-xs text-red-400 uppercase tracking-wider mb-1">Error</h4>
|
|
517
|
+
<div className="bg-red-500/10 border border-red-500/20 rounded p-3 overflow-x-auto">
|
|
518
|
+
<pre className="text-sm text-red-400 whitespace-pre-wrap break-words">{selectedTask.error}</pre>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
)}
|
|
522
|
+
|
|
523
|
+
{/* Result */}
|
|
524
|
+
{selectedTask.status === "completed" && selectedTask.result && (
|
|
525
|
+
<div className="min-w-0">
|
|
526
|
+
<h4 className="text-xs text-green-400 uppercase tracking-wider mb-1">Result</h4>
|
|
527
|
+
<div className="bg-green-500/10 border border-green-500/20 rounded p-3 overflow-x-auto">
|
|
528
|
+
<pre className="text-sm text-green-400 whitespace-pre-wrap break-words">
|
|
529
|
+
{typeof selectedTask.result === "string" ? selectedTask.result : JSON.stringify(selectedTask.result, null, 2)}
|
|
530
|
+
</pre>
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
)}
|
|
534
|
+
|
|
535
|
+
{/* Trajectory */}
|
|
536
|
+
{loadingTask && !selectedTask.trajectory && (
|
|
537
|
+
<div>
|
|
538
|
+
<h4 className="text-xs text-[#666] uppercase tracking-wider mb-2">Trajectory</h4>
|
|
539
|
+
<div className="text-sm text-[#555]">Loading trajectory...</div>
|
|
540
|
+
</div>
|
|
541
|
+
)}
|
|
542
|
+
{selectedTask.trajectory && selectedTask.trajectory.length > 0 && (
|
|
543
|
+
<div>
|
|
544
|
+
<h4 className="text-xs text-[#666] uppercase tracking-wider mb-2">
|
|
545
|
+
Trajectory ({selectedTask.trajectory.length} steps)
|
|
546
|
+
</h4>
|
|
547
|
+
<TrajectoryView trajectory={selectedTask.trajectory} />
|
|
548
|
+
</div>
|
|
549
|
+
)}
|
|
550
|
+
</div>
|
|
551
|
+
</div>
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
406
555
|
return (
|
|
407
556
|
<div className="flex-1 overflow-auto p-4">
|
|
408
557
|
{/* Filter tabs */}
|
|
@@ -431,7 +580,11 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
431
580
|
) : (
|
|
432
581
|
<div className="space-y-3">
|
|
433
582
|
{tasks.map(task => (
|
|
434
|
-
<div
|
|
583
|
+
<div
|
|
584
|
+
key={task.id}
|
|
585
|
+
onClick={() => selectTask(task)}
|
|
586
|
+
className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4 cursor-pointer hover:border-[#333] transition"
|
|
587
|
+
>
|
|
435
588
|
<div className="flex items-start justify-between mb-2">
|
|
436
589
|
<div className="flex-1 min-w-0">
|
|
437
590
|
<h3 className="font-medium">{task.title || task.name}</h3>
|
|
@@ -971,6 +1124,15 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
971
1124
|
const [availableSkills, setAvailableSkills] = useState<AvailableSkill[]>([]);
|
|
972
1125
|
const [apiKey, setApiKey] = useState<string | null>(null);
|
|
973
1126
|
const [showApiKey, setShowApiKey] = useState(false);
|
|
1127
|
+
const [subscriptions, setSubscriptions] = useState<{ id: string; trigger_slug: string; enabled: boolean }[]>([]);
|
|
1128
|
+
|
|
1129
|
+
// Fetch subscriptions for this agent
|
|
1130
|
+
useEffect(() => {
|
|
1131
|
+
authFetch(`/api/subscriptions?agent_id=${agent.id}`)
|
|
1132
|
+
.then(res => res.ok ? res.json() : { subscriptions: [] })
|
|
1133
|
+
.then(data => setSubscriptions(data.subscriptions || []))
|
|
1134
|
+
.catch(() => {});
|
|
1135
|
+
}, [agent.id, authFetch]);
|
|
974
1136
|
|
|
975
1137
|
// Fetch available MCP servers
|
|
976
1138
|
useEffect(() => {
|
|
@@ -1443,6 +1605,28 @@ function SettingsTab({ agent, providers, onUpdateAgent, onDeleteAgent }: {
|
|
|
1443
1605
|
</p>
|
|
1444
1606
|
)}
|
|
1445
1607
|
|
|
1608
|
+
{/* Subscriptions */}
|
|
1609
|
+
<div className="mt-8 pt-6 border-t border-[#222]">
|
|
1610
|
+
<p className="text-sm text-[#666] mb-3">Subscriptions</p>
|
|
1611
|
+
{subscriptions.length === 0 ? (
|
|
1612
|
+
<p className="text-xs text-[#555]">No subscriptions. Set up triggers in Connections to have this agent listen to external events.</p>
|
|
1613
|
+
) : (
|
|
1614
|
+
<div className="space-y-2">
|
|
1615
|
+
{subscriptions.map(sub => (
|
|
1616
|
+
<div key={sub.id} className="flex items-center gap-2 px-3 py-2 bg-[#111] rounded border border-[#1a1a1a]">
|
|
1617
|
+
<span className={`w-2 h-2 rounded-full shrink-0 ${sub.enabled ? "bg-cyan-400" : "bg-[#444]"}`} />
|
|
1618
|
+
<span className={`text-sm flex-1 ${sub.enabled ? "text-cyan-400" : "text-[#666]"}`}>
|
|
1619
|
+
{sub.trigger_slug.replace(/_/g, " ")}
|
|
1620
|
+
</span>
|
|
1621
|
+
<span className={`text-[10px] px-1.5 py-0.5 rounded ${sub.enabled ? "bg-cyan-500/10 text-cyan-400" : "bg-[#222] text-[#555]"}`}>
|
|
1622
|
+
{sub.enabled ? "active" : "disabled"}
|
|
1623
|
+
</span>
|
|
1624
|
+
</div>
|
|
1625
|
+
))}
|
|
1626
|
+
</div>
|
|
1627
|
+
)}
|
|
1628
|
+
</div>
|
|
1629
|
+
|
|
1446
1630
|
{/* Developer Info (dev mode only) */}
|
|
1447
1631
|
{isDev && apiKey && (
|
|
1448
1632
|
<div className="mt-8 pt-6 border-t border-[#222]">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback } from "react";
|
|
1
|
+
import React, { useState, useEffect, useCallback } from "react";
|
|
2
2
|
import { useAuth, useProjects } from "../../context";
|
|
3
3
|
import { IntegrationsPanel } from "../mcp/IntegrationsPanel";
|
|
4
4
|
|
|
@@ -12,18 +12,35 @@ interface TriggerType {
|
|
|
12
12
|
logo: string | null;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
interface ProviderInfo {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
connected: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
export function IntegrationsTab() {
|
|
16
22
|
const { authFetch } = useAuth();
|
|
17
23
|
const { currentProjectId } = useProjects();
|
|
18
24
|
|
|
19
25
|
const projectId = currentProjectId && currentProjectId !== "unassigned" ? currentProjectId : null;
|
|
26
|
+
const projectParam = projectId ? `?project_id=${projectId}` : "";
|
|
20
27
|
|
|
21
|
-
// Provider selection
|
|
22
|
-
const [
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
// Provider selection — only show configured providers
|
|
29
|
+
const [providers, setProviders] = useState<ProviderInfo[]>([]);
|
|
30
|
+
const [selectedProvider, setSelectedProvider] = useState("");
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
authFetch(`/api/triggers/providers${projectParam}`)
|
|
34
|
+
.then(r => r.json())
|
|
35
|
+
.then(data => {
|
|
36
|
+
const connected = (data.providers || []).filter((p: ProviderInfo) => p.connected);
|
|
37
|
+
setProviders(connected);
|
|
38
|
+
if (connected.length > 0 && !connected.find((p: ProviderInfo) => p.id === selectedProvider)) {
|
|
39
|
+
setSelectedProvider(connected[0].id);
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {});
|
|
43
|
+
}, [authFetch]);
|
|
27
44
|
|
|
28
45
|
// Trigger type browsing
|
|
29
46
|
const [browsingToolkit, setBrowsingToolkit] = useState<string | null>(null);
|
|
@@ -53,32 +70,41 @@ export function IntegrationsTab() {
|
|
|
53
70
|
Connect external apps via OAuth or API Key. Connected apps can be used for triggers and MCP integrations.
|
|
54
71
|
</p>
|
|
55
72
|
|
|
56
|
-
{/* Provider Selector */}
|
|
57
|
-
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
{/* Provider Selector — only show if multiple configured */}
|
|
74
|
+
{providers.length > 1 && (
|
|
75
|
+
<div className="flex items-center gap-2 mb-4">
|
|
76
|
+
<span className="text-xs text-[#666]">Provider:</span>
|
|
77
|
+
<div className="flex gap-1 bg-[#111] border border-[#1a1a1a] rounded-lg p-0.5">
|
|
78
|
+
{providers.map(p => (
|
|
79
|
+
<button
|
|
80
|
+
key={p.id}
|
|
81
|
+
onClick={() => setSelectedProvider(p.id)}
|
|
82
|
+
className={`px-3 py-1 rounded text-xs font-medium transition ${
|
|
83
|
+
selectedProvider === p.id
|
|
84
|
+
? "bg-[#1a1a1a] text-white"
|
|
85
|
+
: "text-[#666] hover:text-[#888]"
|
|
86
|
+
}`}
|
|
87
|
+
>
|
|
88
|
+
{p.name}
|
|
89
|
+
</button>
|
|
90
|
+
))}
|
|
91
|
+
</div>
|
|
73
92
|
</div>
|
|
74
|
-
|
|
93
|
+
)}
|
|
75
94
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
{providers.length === 0 ? (
|
|
96
|
+
<div className="bg-[#111] border border-[#1a1a1a] rounded-lg p-8 text-center">
|
|
97
|
+
<p className="text-[#666]">No integration providers configured.</p>
|
|
98
|
+
<p className="text-sm text-[#555] mt-1">Add API keys for Composio or AgentDojo in Settings.</p>
|
|
99
|
+
</div>
|
|
100
|
+
) : (
|
|
101
|
+
<IntegrationsPanel
|
|
102
|
+
providerId={selectedProvider}
|
|
103
|
+
projectId={projectId}
|
|
104
|
+
hideMcpConfig
|
|
105
|
+
onBrowseTriggers={handleBrowseTriggers}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
82
108
|
|
|
83
109
|
{/* Trigger Types Panel */}
|
|
84
110
|
{browsingToolkit && (
|