apteva 0.4.17 → 0.4.18
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.yv28a2vj.js +3 -0
- package/dist/ApiDocsPage.4ccwjjbk.js +4 -0
- package/dist/App.155wke5v.js +4 -0
- package/dist/App.2e19nvn4.js +13 -0
- package/dist/App.2ye1b5n0.js +4 -0
- package/dist/App.4da4ycbe.js +4 -0
- package/dist/App.b6wtzd1j.js +4 -0
- package/dist/App.fjrh28tf.js +4 -0
- package/dist/App.htc36cy8.js +4 -0
- package/dist/App.me6reaa6.js +4 -0
- package/dist/App.n5q6p960.js +4 -0
- package/dist/App.nft7h9jt.js +4 -0
- package/dist/App.np463xvy.js +4 -0
- package/dist/App.nps62kvt.js +4 -0
- package/dist/App.q8ws33cc.js +181 -0
- package/dist/App.tb0y0jmt.js +40 -0
- package/dist/ConnectionsPage.52evzrp7.js +3 -0
- package/dist/McpPage.bjqrp0n2.js +3 -0
- package/dist/SettingsPage.es76hnj2.js +3 -0
- package/dist/SkillsPage.06h8yf0h.js +3 -0
- package/dist/TasksPage.99df66mk.js +3 -0
- package/dist/TelemetryPage.bmdnxhq7.js +3 -0
- package/dist/TestsPage.denxrg8c.js +3 -0
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/auth/middleware.ts +2 -0
- package/src/db.ts +162 -11
- package/src/mcp-platform.ts +41 -1
- package/src/routes/api/agent-utils.ts +38 -2
- package/src/routes/api/agents.ts +65 -2
- package/src/routes/api/projects.ts +19 -2
- package/src/routes/api/system.ts +26 -12
- package/src/routes/api/triggers.ts +458 -0
- package/src/routes/api/webhooks.ts +171 -0
- package/src/routes/api.ts +4 -0
- package/src/routes/static.ts +12 -3
- package/src/server.ts +4 -2
- package/src/triggers/agentdojo.ts +248 -0
- package/src/triggers/composio.ts +264 -0
- package/src/triggers/index.ts +71 -0
- package/src/web/App.tsx +17 -10
- package/src/web/components/agents/AgentCard.tsx +14 -7
- package/src/web/components/agents/AgentPanel.tsx +105 -115
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/index.ts +1 -0
- package/src/web/components/connections/ConnectionsPage.tsx +54 -0
- package/src/web/components/connections/IntegrationsTab.tsx +144 -0
- package/src/web/components/connections/OverviewTab.tsx +183 -0
- package/src/web/components/connections/TriggersTab.tsx +690 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Sidebar.tsx +7 -1
- package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
- package/src/web/components/settings/SettingsPage.tsx +96 -2
- package/src/web/components/tasks/TasksPage.tsx +2 -2
- package/src/web/components/tests/TestsPage.tsx +1 -2
- package/src/web/hooks/useAgents.ts +15 -11
- package/src/web/types.ts +1 -1
- package/dist/App.fq4xbpcz.js +0 -228
|
@@ -121,13 +121,16 @@ export function AgentCard({ agent, selected, onSelect, onToggle, showProject }:
|
|
|
121
121
|
|
|
122
122
|
<button
|
|
123
123
|
onClick={onToggle}
|
|
124
|
+
disabled={agent.status === "starting" || agent.status === "stopping"}
|
|
124
125
|
className={`w-full px-3 py-1.5 rounded text-sm font-medium transition mt-auto ${
|
|
125
|
-
agent.status === "
|
|
126
|
-
? "bg-[#
|
|
127
|
-
:
|
|
126
|
+
agent.status === "starting" || agent.status === "stopping"
|
|
127
|
+
? "bg-[#333] text-[#666] cursor-wait"
|
|
128
|
+
: agent.status === "running"
|
|
129
|
+
? "bg-[#f97316]/20 text-[#f97316] hover:bg-[#f97316]/30"
|
|
130
|
+
: "bg-[#3b82f6]/20 text-[#3b82f6] hover:bg-[#3b82f6]/30"
|
|
128
131
|
}`}
|
|
129
132
|
>
|
|
130
|
-
{agent.status === "running" ? "Stop" : "Start"}
|
|
133
|
+
{agent.status === "starting" ? "Starting..." : agent.status === "stopping" ? "Stopping..." : agent.status === "running" ? "Stop" : "Start"}
|
|
131
134
|
</button>
|
|
132
135
|
</div>
|
|
133
136
|
);
|
|
@@ -142,12 +145,16 @@ function StatusBadge({ status, isActive, activityType }: { status: Agent["status
|
|
|
142
145
|
);
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
const isTransitioning = status === "starting" || status === "stopping";
|
|
149
|
+
|
|
145
150
|
return (
|
|
146
151
|
<span
|
|
147
152
|
className={`px-2 py-1 rounded text-xs font-medium ${
|
|
148
|
-
|
|
149
|
-
? "bg-
|
|
150
|
-
:
|
|
153
|
+
isTransitioning
|
|
154
|
+
? "bg-yellow-500/20 text-yellow-400 animate-pulse"
|
|
155
|
+
: status === "running"
|
|
156
|
+
? "bg-[#3b82f6]/20 text-[#3b82f6]"
|
|
157
|
+
: "bg-[#333] text-[#666]"
|
|
151
158
|
}`}
|
|
152
159
|
>
|
|
153
160
|
{status}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Chat } from "@apteva/apteva-kit";
|
|
3
|
-
import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon } from "../common/Icons";
|
|
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
5
|
import { Select } from "../common/Select";
|
|
5
6
|
import { useConfirm } from "../common/Modal";
|
|
6
7
|
import { useTelemetry } from "../../context";
|
|
@@ -152,7 +153,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
152
153
|
const [loading, setLoading] = useState(true);
|
|
153
154
|
const [error, setError] = useState<string | null>(null);
|
|
154
155
|
const [selectedThread, setSelectedThread] = useState<string | null>(null);
|
|
155
|
-
const [
|
|
156
|
+
const [initialMessages, setInitialMessages] = useState<any[]>([]);
|
|
156
157
|
const [loadingMessages, setLoadingMessages] = useState(false);
|
|
157
158
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
158
159
|
|
|
@@ -160,7 +161,6 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
160
161
|
useEffect(() => {
|
|
161
162
|
setThreads([]);
|
|
162
163
|
setSelectedThread(null);
|
|
163
|
-
setMessages([]);
|
|
164
164
|
setError(null);
|
|
165
165
|
setLoading(true);
|
|
166
166
|
}, [agent.id]);
|
|
@@ -188,19 +188,29 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
188
188
|
fetchThreads();
|
|
189
189
|
}, [agent.id, agent.status]);
|
|
190
190
|
|
|
191
|
-
const
|
|
192
|
-
setSelectedThread(threadId);
|
|
191
|
+
const openThread = async (threadId: string) => {
|
|
193
192
|
setLoadingMessages(true);
|
|
193
|
+
setSelectedThread(threadId);
|
|
194
194
|
try {
|
|
195
195
|
const res = await fetch(`/api/agents/${agent.id}/threads/${threadId}/messages`);
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
196
|
+
if (res.ok) {
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
const msgs = (data.messages || [])
|
|
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);
|
|
207
|
+
} else {
|
|
208
|
+
setInitialMessages([]);
|
|
209
|
+
}
|
|
199
210
|
} catch {
|
|
200
|
-
|
|
201
|
-
} finally {
|
|
202
|
-
setLoadingMessages(false);
|
|
211
|
+
setInitialMessages([]);
|
|
203
212
|
}
|
|
213
|
+
setLoadingMessages(false);
|
|
204
214
|
};
|
|
205
215
|
|
|
206
216
|
const deleteThread = async (threadId: string, e: React.MouseEvent) => {
|
|
@@ -213,7 +223,6 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
213
223
|
setThreads(prev => prev.filter(t => t.id !== threadId));
|
|
214
224
|
if (selectedThread === threadId) {
|
|
215
225
|
setSelectedThread(null);
|
|
216
|
-
setMessages([]);
|
|
217
226
|
}
|
|
218
227
|
} catch {
|
|
219
228
|
// Ignore errors
|
|
@@ -244,7 +253,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
244
253
|
);
|
|
245
254
|
}
|
|
246
255
|
|
|
247
|
-
// Show
|
|
256
|
+
// Show live chat for selected thread
|
|
248
257
|
if (selectedThread) {
|
|
249
258
|
const selectedThreadData = threads.find(t => t.id === selectedThread);
|
|
250
259
|
return (
|
|
@@ -252,15 +261,15 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
252
261
|
{ConfirmDialog}
|
|
253
262
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
254
263
|
{/* Header with back button */}
|
|
255
|
-
<div className="flex items-center gap-3 px-4 py-
|
|
264
|
+
<div className="flex items-center gap-3 px-4 py-2 border-b border-[#1a1a1a] flex-shrink-0">
|
|
256
265
|
<button
|
|
257
|
-
onClick={() => { setSelectedThread(null);
|
|
266
|
+
onClick={() => { setSelectedThread(null); setInitialMessages([]); }}
|
|
258
267
|
className="text-[#666] hover:text-[#e0e0e0] transition text-lg"
|
|
259
268
|
>
|
|
260
269
|
←
|
|
261
270
|
</button>
|
|
262
|
-
<div className="flex-1">
|
|
263
|
-
<p className="text-sm font-medium">
|
|
271
|
+
<div className="flex-1 min-w-0">
|
|
272
|
+
<p className="text-sm font-medium truncate">
|
|
264
273
|
{selectedThreadData?.title || `Thread ${selectedThread.slice(0, 8)}`}
|
|
265
274
|
</p>
|
|
266
275
|
<p className="text-xs text-[#666]">
|
|
@@ -275,54 +284,22 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
275
284
|
</button>
|
|
276
285
|
</div>
|
|
277
286
|
|
|
278
|
-
{/*
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
>
|
|
295
|
-
<div className="text-sm whitespace-pre-wrap">
|
|
296
|
-
{typeof msg.content === "string"
|
|
297
|
-
? msg.content
|
|
298
|
-
: Array.isArray(msg.content)
|
|
299
|
-
? msg.content.map((block: any, j: number) => (
|
|
300
|
-
<div key={j}>
|
|
301
|
-
{block.type === "text" && block.text}
|
|
302
|
-
{block.type === "tool_use" && (
|
|
303
|
-
<div className="bg-[#222] p-2 rounded mt-1 text-xs text-[#888]">
|
|
304
|
-
🔧 Tool: {block.name}
|
|
305
|
-
</div>
|
|
306
|
-
)}
|
|
307
|
-
{block.type === "tool_result" && (
|
|
308
|
-
<div className="bg-[#222] p-2 rounded mt-1 text-xs text-[#888]">
|
|
309
|
-
📋 Result: {typeof block.content === "string" ? block.content.slice(0, 200) : "..."}
|
|
310
|
-
</div>
|
|
311
|
-
)}
|
|
312
|
-
</div>
|
|
313
|
-
))
|
|
314
|
-
: JSON.stringify(msg.content)
|
|
315
|
-
}
|
|
316
|
-
</div>
|
|
317
|
-
<p className="text-xs text-[#666] mt-1">
|
|
318
|
-
{new Date(msg.created_at).toLocaleTimeString()}
|
|
319
|
-
</p>
|
|
320
|
-
</div>
|
|
321
|
-
</div>
|
|
322
|
-
))}
|
|
323
|
-
</div>
|
|
324
|
-
)}
|
|
325
|
-
</div>
|
|
287
|
+
{/* Live chat in this thread */}
|
|
288
|
+
{loadingMessages ? (
|
|
289
|
+
<div className="flex-1 flex items-center justify-center text-[#666]">Loading messages...</div>
|
|
290
|
+
) : (
|
|
291
|
+
<Chat
|
|
292
|
+
key={selectedThread}
|
|
293
|
+
agentId="default"
|
|
294
|
+
apiUrl={`/api/agents/${agent.id}`}
|
|
295
|
+
threadId={selectedThread}
|
|
296
|
+
initialMessages={initialMessages}
|
|
297
|
+
placeholder="Continue this conversation..."
|
|
298
|
+
context={agent.systemPrompt}
|
|
299
|
+
variant="terminal"
|
|
300
|
+
showHeader={false}
|
|
301
|
+
/>
|
|
302
|
+
)}
|
|
326
303
|
</div>
|
|
327
304
|
</>
|
|
328
305
|
);
|
|
@@ -342,7 +319,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
342
319
|
{threads.map(thread => (
|
|
343
320
|
<div
|
|
344
321
|
key={thread.id}
|
|
345
|
-
onClick={() =>
|
|
322
|
+
onClick={() => openThread(thread.id)}
|
|
346
323
|
className="p-4 cursor-pointer hover:bg-[#111] transition flex items-center justify-between"
|
|
347
324
|
>
|
|
348
325
|
<div className="flex-1 min-w-0">
|
|
@@ -369,24 +346,11 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
369
346
|
);
|
|
370
347
|
}
|
|
371
348
|
|
|
372
|
-
interface Task {
|
|
373
|
-
id: string;
|
|
374
|
-
name: string;
|
|
375
|
-
description?: string;
|
|
376
|
-
status: "pending" | "running" | "completed" | "failed";
|
|
377
|
-
created_at: string;
|
|
378
|
-
updated_at?: string;
|
|
379
|
-
scheduled_at?: string;
|
|
380
|
-
completed_at?: string;
|
|
381
|
-
result?: string;
|
|
382
|
-
error?: string;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
349
|
function TasksTab({ agent }: { agent: Agent }) {
|
|
386
|
-
const [tasks, setTasks] = useState<
|
|
350
|
+
const [tasks, setTasks] = useState<any[]>([]);
|
|
387
351
|
const [loading, setLoading] = useState(true);
|
|
388
352
|
const [error, setError] = useState<string | null>(null);
|
|
389
|
-
const [filter, setFilter] = useState<
|
|
353
|
+
const [filter, setFilter] = useState<string>("all");
|
|
390
354
|
const { events } = useTelemetry({ agent_id: agent.id, category: "task" });
|
|
391
355
|
|
|
392
356
|
// Reset state when agent changes
|
|
@@ -421,14 +385,12 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
421
385
|
fetchTasks();
|
|
422
386
|
}, [agent.id, agent.status, filter, events.length]);
|
|
423
387
|
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
default: return "bg-[#222] text-[#666]";
|
|
431
|
-
}
|
|
388
|
+
const statusColors: Record<string, string> = {
|
|
389
|
+
pending: "bg-yellow-500/20 text-yellow-400",
|
|
390
|
+
running: "bg-blue-500/20 text-blue-400",
|
|
391
|
+
completed: "bg-green-500/20 text-green-400",
|
|
392
|
+
failed: "bg-red-500/20 text-red-400",
|
|
393
|
+
cancelled: "bg-gray-500/20 text-gray-400",
|
|
432
394
|
};
|
|
433
395
|
|
|
434
396
|
if (agent.status !== "running") {
|
|
@@ -466,63 +428,91 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
466
428
|
);
|
|
467
429
|
}
|
|
468
430
|
|
|
431
|
+
const filterOptions = [
|
|
432
|
+
{ value: "all", label: "All" },
|
|
433
|
+
{ value: "pending", label: "Pending" },
|
|
434
|
+
{ value: "running", label: "Running" },
|
|
435
|
+
{ value: "completed", label: "Completed" },
|
|
436
|
+
{ value: "failed", label: "Failed" },
|
|
437
|
+
];
|
|
438
|
+
|
|
469
439
|
return (
|
|
470
440
|
<div className="flex-1 overflow-auto p-4">
|
|
471
441
|
{/* Filter tabs */}
|
|
472
442
|
<div className="flex gap-2 mb-4">
|
|
473
|
-
{
|
|
443
|
+
{filterOptions.map(opt => (
|
|
474
444
|
<button
|
|
475
|
-
key={
|
|
476
|
-
onClick={() => setFilter(
|
|
477
|
-
className={`px-3 py-1 text-
|
|
478
|
-
filter ===
|
|
445
|
+
key={opt.value}
|
|
446
|
+
onClick={() => setFilter(opt.value)}
|
|
447
|
+
className={`px-3 py-1.5 rounded text-sm transition ${
|
|
448
|
+
filter === opt.value
|
|
479
449
|
? "bg-[#f97316] text-black"
|
|
480
|
-
: "bg-[#1a1a1a]
|
|
450
|
+
: "bg-[#1a1a1a] hover:bg-[#222]"
|
|
481
451
|
}`}
|
|
482
452
|
>
|
|
483
|
-
{
|
|
453
|
+
{opt.label}
|
|
484
454
|
</button>
|
|
485
455
|
))}
|
|
486
456
|
</div>
|
|
487
457
|
|
|
488
458
|
{tasks.length === 0 ? (
|
|
489
|
-
<div className="text-center py-10
|
|
490
|
-
<
|
|
459
|
+
<div className="text-center py-10">
|
|
460
|
+
<TasksIcon className="w-10 h-10 mx-auto mb-3 text-[#333]" />
|
|
461
|
+
<p className="text-[#666]">No {filter === "all" ? "" : filter + " "}tasks</p>
|
|
462
|
+
<p className="text-sm text-[#444] mt-1">Tasks will appear here when created</p>
|
|
491
463
|
</div>
|
|
492
464
|
) : (
|
|
493
465
|
<div className="space-y-3">
|
|
494
466
|
{tasks.map(task => (
|
|
495
|
-
<div key={task.id} className="bg-[#111] border border-[#1a1a1a] rounded p-
|
|
496
|
-
<div className="flex items-start justify-between">
|
|
467
|
+
<div key={task.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
|
|
468
|
+
<div className="flex items-start justify-between mb-2">
|
|
497
469
|
<div className="flex-1 min-w-0">
|
|
498
|
-
<
|
|
499
|
-
{task.description && (
|
|
500
|
-
<p className="text-xs text-[#666] mt-1 line-clamp-2">{task.description}</p>
|
|
501
|
-
)}
|
|
470
|
+
<h3 className="font-medium">{task.title || task.name}</h3>
|
|
502
471
|
</div>
|
|
503
|
-
<span className={`
|
|
472
|
+
<span className={`px-2 py-1 rounded text-xs font-medium ml-2 ${statusColors[task.status] || statusColors.pending}`}>
|
|
504
473
|
{task.status}
|
|
505
474
|
</span>
|
|
506
475
|
</div>
|
|
507
476
|
|
|
508
|
-
|
|
509
|
-
<
|
|
510
|
-
|
|
511
|
-
|
|
477
|
+
{task.description && (
|
|
478
|
+
<p className="text-sm text-[#888] mb-2 line-clamp-2">{task.description}</p>
|
|
479
|
+
)}
|
|
480
|
+
|
|
481
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[#555]">
|
|
482
|
+
<span className="flex items-center gap-1">
|
|
483
|
+
{task.type === "recurring"
|
|
484
|
+
? <RecurringIcon className="w-3.5 h-3.5" />
|
|
485
|
+
: task.execute_at
|
|
486
|
+
? <ScheduledIcon className="w-3.5 h-3.5" />
|
|
487
|
+
: <TaskOnceIcon className="w-3.5 h-3.5" />
|
|
488
|
+
}
|
|
489
|
+
{task.type === "recurring" && task.recurrence ? formatCron(task.recurrence) : task.type || "once"}
|
|
490
|
+
</span>
|
|
491
|
+
{task.priority !== undefined && (
|
|
492
|
+
<span>Priority: {task.priority}</span>
|
|
493
|
+
)}
|
|
494
|
+
{task.next_run && (
|
|
495
|
+
<span className="text-[#f97316]">{formatRelativeTime(task.next_run)}</span>
|
|
496
|
+
)}
|
|
497
|
+
{!task.next_run && task.execute_at && (
|
|
498
|
+
<span className="text-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
|
|
512
499
|
)}
|
|
500
|
+
<span>Created: {new Date(task.created_at).toLocaleDateString()}</span>
|
|
513
501
|
</div>
|
|
514
502
|
|
|
515
503
|
{task.status === "completed" && task.result && (
|
|
516
|
-
<div className="mt-
|
|
517
|
-
<
|
|
518
|
-
<
|
|
504
|
+
<div className="mt-3 bg-green-500/10 border border-green-500/20 rounded p-3">
|
|
505
|
+
<h4 className="text-xs text-green-400 uppercase tracking-wider mb-1">Result</h4>
|
|
506
|
+
<pre className="text-sm text-green-400 whitespace-pre-wrap break-words">
|
|
507
|
+
{typeof task.result === "string" ? task.result : JSON.stringify(task.result, null, 2)}
|
|
508
|
+
</pre>
|
|
519
509
|
</div>
|
|
520
510
|
)}
|
|
521
511
|
|
|
522
512
|
{task.status === "failed" && task.error && (
|
|
523
|
-
<div className="mt-
|
|
524
|
-
<
|
|
525
|
-
<
|
|
513
|
+
<div className="mt-3 bg-red-500/10 border border-red-500/20 rounded p-3">
|
|
514
|
+
<h4 className="text-xs text-red-400 uppercase tracking-wider mb-1">Error</h4>
|
|
515
|
+
<pre className="text-sm text-red-400 whitespace-pre-wrap break-words">{task.error}</pre>
|
|
526
516
|
</div>
|
|
527
517
|
)}
|
|
528
518
|
</div>
|
|
@@ -182,6 +182,14 @@ export function TestsIcon({ className = "w-4 h-4" }: IconProps) {
|
|
|
182
182
|
);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
export function ConnectionsIcon({ className = "w-5 h-5" }: IconProps) {
|
|
186
|
+
return (
|
|
187
|
+
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
188
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
|
189
|
+
</svg>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
185
193
|
export function ActivityIcon({ className = "w-5 h-5" }: IconProps) {
|
|
186
194
|
return (
|
|
187
195
|
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { OverviewTab } from "./OverviewTab";
|
|
3
|
+
import { TriggersTab } from "./TriggersTab";
|
|
4
|
+
import { IntegrationsTab } from "./IntegrationsTab";
|
|
5
|
+
|
|
6
|
+
type Tab = "overview" | "triggers" | "integrations";
|
|
7
|
+
|
|
8
|
+
export function ConnectionsPage() {
|
|
9
|
+
const [activeTab, setActiveTab] = useState<Tab>("overview");
|
|
10
|
+
|
|
11
|
+
const tabs: { id: Tab; label: string }[] = [
|
|
12
|
+
{ id: "overview", label: "Overview" },
|
|
13
|
+
{ id: "triggers", label: "Triggers" },
|
|
14
|
+
{ id: "integrations", label: "Integrations" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex-1 overflow-auto p-6">
|
|
19
|
+
<div className="max-w-6xl">
|
|
20
|
+
{/* Header */}
|
|
21
|
+
<div className="flex items-center justify-between mb-6">
|
|
22
|
+
<div>
|
|
23
|
+
<h1 className="text-2xl font-semibold mb-1">Connections</h1>
|
|
24
|
+
<p className="text-[#666]">
|
|
25
|
+
Manage external app connections, triggers, and webhooks.
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
{/* Tabs */}
|
|
31
|
+
<div className="flex gap-1 mb-6 bg-[#111] border border-[#1a1a1a] rounded-lg p-1 w-fit">
|
|
32
|
+
{tabs.map(tab => (
|
|
33
|
+
<button
|
|
34
|
+
key={tab.id}
|
|
35
|
+
onClick={() => setActiveTab(tab.id)}
|
|
36
|
+
className={`px-4 py-2 rounded text-sm font-medium transition ${
|
|
37
|
+
activeTab === tab.id
|
|
38
|
+
? "bg-[#1a1a1a] text-white"
|
|
39
|
+
: "text-[#666] hover:text-[#888]"
|
|
40
|
+
}`}
|
|
41
|
+
>
|
|
42
|
+
{tab.label}
|
|
43
|
+
</button>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{/* Tab Content */}
|
|
48
|
+
{activeTab === "overview" && <OverviewTab />}
|
|
49
|
+
{activeTab === "triggers" && <TriggersTab />}
|
|
50
|
+
{activeTab === "integrations" && <IntegrationsTab />}
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React, { useState, useCallback } from "react";
|
|
2
|
+
import { useAuth, useProjects } from "../../context";
|
|
3
|
+
import { IntegrationsPanel } from "../mcp/IntegrationsPanel";
|
|
4
|
+
|
|
5
|
+
interface TriggerType {
|
|
6
|
+
slug: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
type: "webhook" | "poll";
|
|
10
|
+
toolkit_slug: string;
|
|
11
|
+
toolkit_name: string;
|
|
12
|
+
logo: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function IntegrationsTab() {
|
|
16
|
+
const { authFetch } = useAuth();
|
|
17
|
+
const { currentProjectId } = useProjects();
|
|
18
|
+
|
|
19
|
+
const projectId = currentProjectId && currentProjectId !== "unassigned" ? currentProjectId : null;
|
|
20
|
+
|
|
21
|
+
// Provider selection
|
|
22
|
+
const [selectedProvider, setSelectedProvider] = useState("composio");
|
|
23
|
+
const providerOptions = [
|
|
24
|
+
{ id: "composio", name: "Composio" },
|
|
25
|
+
{ id: "agentdojo", name: "AgentDojo" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Trigger type browsing
|
|
29
|
+
const [browsingToolkit, setBrowsingToolkit] = useState<string | null>(null);
|
|
30
|
+
const [triggerTypes, setTriggerTypes] = useState<TriggerType[]>([]);
|
|
31
|
+
const [typesLoading, setTypesLoading] = useState(false);
|
|
32
|
+
|
|
33
|
+
const handleBrowseTriggers = useCallback(async (toolkitSlug: string) => {
|
|
34
|
+
setBrowsingToolkit(toolkitSlug);
|
|
35
|
+
setTypesLoading(true);
|
|
36
|
+
try {
|
|
37
|
+
let url = `/api/triggers/types?provider=${selectedProvider}&toolkit_slugs=${toolkitSlug}`;
|
|
38
|
+
if (projectId) url += `&project_id=${projectId}`;
|
|
39
|
+
const res = await authFetch(url);
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
setTriggerTypes(data.types || []);
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("Failed to fetch trigger types:", e);
|
|
46
|
+
}
|
|
47
|
+
setTypesLoading(false);
|
|
48
|
+
}, [authFetch, projectId, selectedProvider]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div>
|
|
52
|
+
<p className="text-sm text-[#666] mb-4">
|
|
53
|
+
Connect external apps via OAuth or API Key. Connected apps can be used for triggers and MCP integrations.
|
|
54
|
+
</p>
|
|
55
|
+
|
|
56
|
+
{/* Provider Selector */}
|
|
57
|
+
<div className="flex items-center gap-2 mb-4">
|
|
58
|
+
<span className="text-xs text-[#666]">Provider:</span>
|
|
59
|
+
<div className="flex gap-1 bg-[#111] border border-[#1a1a1a] rounded-lg p-0.5">
|
|
60
|
+
{providerOptions.map(p => (
|
|
61
|
+
<button
|
|
62
|
+
key={p.id}
|
|
63
|
+
onClick={() => setSelectedProvider(p.id)}
|
|
64
|
+
className={`px-3 py-1 rounded text-xs font-medium transition ${
|
|
65
|
+
selectedProvider === p.id
|
|
66
|
+
? "bg-[#1a1a1a] text-white"
|
|
67
|
+
: "text-[#666] hover:text-[#888]"
|
|
68
|
+
}`}
|
|
69
|
+
>
|
|
70
|
+
{p.name}
|
|
71
|
+
</button>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<IntegrationsPanel
|
|
77
|
+
providerId={selectedProvider}
|
|
78
|
+
projectId={projectId}
|
|
79
|
+
hideMcpConfig
|
|
80
|
+
onBrowseTriggers={handleBrowseTriggers}
|
|
81
|
+
/>
|
|
82
|
+
|
|
83
|
+
{/* Trigger Types Panel */}
|
|
84
|
+
{browsingToolkit && (
|
|
85
|
+
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
|
86
|
+
<div className="bg-[#111] border border-[#333] rounded-lg w-full max-w-2xl mx-4 max-h-[80vh] flex flex-col">
|
|
87
|
+
<div className="p-4 border-b border-[#1a1a1a] flex items-center justify-between">
|
|
88
|
+
<div>
|
|
89
|
+
<h3 className="font-medium">Trigger Types</h3>
|
|
90
|
+
<p className="text-xs text-[#666]">{browsingToolkit}</p>
|
|
91
|
+
</div>
|
|
92
|
+
<button
|
|
93
|
+
onClick={() => { setBrowsingToolkit(null); setTriggerTypes([]); }}
|
|
94
|
+
className="text-[#666] hover:text-white transition text-lg px-2"
|
|
95
|
+
>
|
|
96
|
+
x
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div className="flex-1 overflow-auto p-4">
|
|
101
|
+
{typesLoading ? (
|
|
102
|
+
<div className="text-center py-8 text-[#666]">Loading trigger types...</div>
|
|
103
|
+
) : triggerTypes.length === 0 ? (
|
|
104
|
+
<div className="text-center py-8 text-[#666]">
|
|
105
|
+
No trigger types available for this app.
|
|
106
|
+
</div>
|
|
107
|
+
) : (
|
|
108
|
+
<div className="space-y-2">
|
|
109
|
+
{triggerTypes.map(tt => (
|
|
110
|
+
<div key={tt.slug} className="bg-[#0a0a0a] border border-[#1a1a1a] rounded-lg p-3">
|
|
111
|
+
<div className="flex items-start gap-3">
|
|
112
|
+
{tt.logo ? (
|
|
113
|
+
<img src={tt.logo} alt={tt.toolkit_name} className="w-6 h-6 rounded object-contain flex-shrink-0 mt-0.5" />
|
|
114
|
+
) : (
|
|
115
|
+
<div className="w-6 h-6 rounded bg-[#1a1a1a] flex items-center justify-center text-[10px] flex-shrink-0 mt-0.5">
|
|
116
|
+
{tt.toolkit_name?.[0]?.toUpperCase() || "?"}
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
<div className="flex-1 min-w-0">
|
|
120
|
+
<div className="text-sm font-medium">{tt.name}</div>
|
|
121
|
+
<div className="text-xs text-[#666] mt-0.5">{tt.description}</div>
|
|
122
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
123
|
+
<span className="text-[10px] bg-[#1a1a1a] text-[#555] px-1.5 py-0.5 rounded font-mono">
|
|
124
|
+
{tt.slug}
|
|
125
|
+
</span>
|
|
126
|
+
<span className={`text-[10px] px-1.5 py-0.5 rounded ${
|
|
127
|
+
tt.type === "webhook" ? "bg-blue-500/10 text-blue-400" : "bg-yellow-500/10 text-yellow-400"
|
|
128
|
+
}`}>
|
|
129
|
+
{tt.type}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
)}
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|