apteva 0.4.17 → 0.4.19
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.9a1qg4bp.js +3 -0
- package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
- package/dist/App.1nmg2h01.js +4 -0
- package/dist/App.5qw2dtxs.js +4 -0
- package/dist/App.6nc5acvk.js +4 -0
- package/dist/App.7vzbaz56.js +4 -0
- package/dist/App.8rfz30p1.js +4 -0
- package/dist/App.amwp54wf.js +4 -0
- package/dist/App.e4202qb4.js +267 -0
- package/dist/App.errxz2q4.js +4 -0
- package/dist/App.f8qsyhpr.js +4 -0
- package/dist/App.g8vq68n0.js +20 -0
- package/dist/App.kfyrnznw.js +13 -0
- package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
- package/dist/App.p93mmyqw.js +4 -0
- package/dist/App.qmg33p02.js +4 -0
- package/dist/App.sdsc0258.js +4 -0
- package/dist/ConnectionsPage.7zqba1r0.js +3 -0
- package/dist/McpPage.kf2g327t.js +3 -0
- package/dist/SettingsPage.472c15ep.js +3 -0
- package/dist/SkillsPage.xdxnh68a.js +3 -0
- package/dist/TasksPage.7g0b8xwc.js +3 -0
- package/dist/TelemetryPage.pr7rbz4r.js +3 -0
- package/dist/TestsPage.zhc6rqjm.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +9 -4
- package/src/auth/middleware.ts +2 -0
- package/src/channels/index.ts +40 -0
- package/src/channels/telegram.ts +306 -0
- package/src/db.ts +342 -11
- package/src/integrations/agentdojo.ts +1 -1
- package/src/mcp-handler.ts +31 -24
- package/src/mcp-platform.ts +41 -1
- package/src/providers.ts +22 -9
- package/src/routes/api/agent-utils.ts +38 -2
- package/src/routes/api/agents.ts +65 -2
- package/src/routes/api/channels.ts +182 -0
- package/src/routes/api/integrations.ts +13 -5
- package/src/routes/api/mcp.ts +27 -9
- package/src/routes/api/projects.ts +19 -2
- package/src/routes/api/system.ts +26 -12
- package/src/routes/api/telemetry.ts +30 -0
- package/src/routes/api/triggers.ts +478 -0
- package/src/routes/api/webhooks.ts +171 -0
- package/src/routes/api.ts +7 -1
- package/src/routes/static.ts +12 -3
- package/src/server.ts +43 -6
- package/src/triggers/agentdojo.ts +253 -0
- package/src/triggers/composio.ts +264 -0
- package/src/triggers/index.ts +71 -0
- package/src/tui/AgentList.tsx +145 -0
- package/src/tui/App.tsx +102 -0
- package/src/tui/Login.tsx +104 -0
- package/src/tui/api.ts +72 -0
- package/src/tui/index.tsx +7 -0
- package/src/web/App.tsx +18 -11
- package/src/web/components/agents/AgentCard.tsx +14 -7
- package/src/web/components/agents/AgentPanel.tsx +94 -137
- package/src/web/components/common/Icons.tsx +16 -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 +137 -0
- package/src/web/components/connections/TriggersTab.tsx +1169 -0
- package/src/web/components/index.ts +1 -0
- package/src/web/components/layout/Header.tsx +196 -4
- 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 +364 -2
- package/src/web/components/tasks/TasksPage.tsx +2 -2
- package/src/web/components/tests/TestsPage.tsx +1 -2
- package/src/web/context/TelemetryContext.tsx +14 -1
- package/src/web/context/index.ts +1 -1
- package/src/web/hooks/useAgents.ts +15 -11
- package/src/web/types.ts +1 -1
- package/dist/App.fq4xbpcz.js +0 -228
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Chat } from "@apteva/apteva-kit";
|
|
3
|
-
import { CloseIcon, MemoryIcon, TasksIcon, VisionIcon, OperatorIcon, McpIcon, RealtimeIcon, FilesIcon, MultiAgentIcon } from "../common/Icons";
|
|
2
|
+
import { Chat, convertApiMessages } from "@apteva/apteva-kit";
|
|
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,21 @@ 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
|
+
setInitialMessages(convertApiMessages(data.messages || []));
|
|
199
|
+
} else {
|
|
200
|
+
setInitialMessages([]);
|
|
201
|
+
}
|
|
199
202
|
} catch {
|
|
200
|
-
|
|
201
|
-
} finally {
|
|
202
|
-
setLoadingMessages(false);
|
|
203
|
+
setInitialMessages([]);
|
|
203
204
|
}
|
|
205
|
+
setLoadingMessages(false);
|
|
204
206
|
};
|
|
205
207
|
|
|
206
208
|
const deleteThread = async (threadId: string, e: React.MouseEvent) => {
|
|
@@ -213,7 +215,6 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
213
215
|
setThreads(prev => prev.filter(t => t.id !== threadId));
|
|
214
216
|
if (selectedThread === threadId) {
|
|
215
217
|
setSelectedThread(null);
|
|
216
|
-
setMessages([]);
|
|
217
218
|
}
|
|
218
219
|
} catch {
|
|
219
220
|
// Ignore errors
|
|
@@ -244,85 +245,28 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
244
245
|
);
|
|
245
246
|
}
|
|
246
247
|
|
|
247
|
-
// Show
|
|
248
|
+
// Show live chat for selected thread
|
|
248
249
|
if (selectedThread) {
|
|
249
|
-
const selectedThreadData = threads.find(t => t.id === selectedThread);
|
|
250
250
|
return (
|
|
251
251
|
<>
|
|
252
252
|
{ConfirmDialog}
|
|
253
253
|
<div className="flex-1 flex flex-col overflow-hidden">
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<button
|
|
271
|
-
onClick={(e) => deleteThread(selectedThread, e)}
|
|
272
|
-
className="text-[#666] hover:text-red-400 text-sm px-2 py-1"
|
|
273
|
-
>
|
|
274
|
-
Delete
|
|
275
|
-
</button>
|
|
276
|
-
</div>
|
|
277
|
-
|
|
278
|
-
{/* Messages */}
|
|
279
|
-
<div className="flex-1 overflow-auto p-4">
|
|
280
|
-
{loadingMessages ? (
|
|
281
|
-
<p className="text-[#666]">Loading messages...</p>
|
|
282
|
-
) : messages.length === 0 ? (
|
|
283
|
-
<p className="text-[#666]">No messages in this thread</p>
|
|
284
|
-
) : (
|
|
285
|
-
<div className="space-y-4">
|
|
286
|
-
{messages.map((msg, i) => (
|
|
287
|
-
<div key={i} className={`${msg.role === "user" ? "text-right" : ""}`}>
|
|
288
|
-
<div
|
|
289
|
-
className={`inline-block max-w-[80%] p-3 rounded ${
|
|
290
|
-
msg.role === "user"
|
|
291
|
-
? "bg-[#f97316]/20 text-[#f97316]"
|
|
292
|
-
: "bg-[#1a1a1a] text-[#e0e0e0]"
|
|
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>
|
|
254
|
+
{loadingMessages ? (
|
|
255
|
+
<div className="flex-1 flex items-center justify-center text-[#666]">Loading messages...</div>
|
|
256
|
+
) : (
|
|
257
|
+
<Chat
|
|
258
|
+
key={selectedThread}
|
|
259
|
+
agentId="default"
|
|
260
|
+
apiUrl={`/api/agents/${agent.id}`}
|
|
261
|
+
threadId={selectedThread}
|
|
262
|
+
initialMessages={initialMessages}
|
|
263
|
+
placeholder="Continue this conversation..."
|
|
264
|
+
context={agent.systemPrompt}
|
|
265
|
+
variant="terminal"
|
|
266
|
+
showHeader={true}
|
|
267
|
+
onHeaderBack={() => { setSelectedThread(null); setInitialMessages([]); }}
|
|
268
|
+
/>
|
|
269
|
+
)}
|
|
326
270
|
</div>
|
|
327
271
|
</>
|
|
328
272
|
);
|
|
@@ -342,7 +286,7 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
342
286
|
{threads.map(thread => (
|
|
343
287
|
<div
|
|
344
288
|
key={thread.id}
|
|
345
|
-
onClick={() =>
|
|
289
|
+
onClick={() => openThread(thread.id)}
|
|
346
290
|
className="p-4 cursor-pointer hover:bg-[#111] transition flex items-center justify-between"
|
|
347
291
|
>
|
|
348
292
|
<div className="flex-1 min-w-0">
|
|
@@ -369,24 +313,11 @@ function ThreadsTab({ agent }: { agent: Agent }) {
|
|
|
369
313
|
);
|
|
370
314
|
}
|
|
371
315
|
|
|
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
316
|
function TasksTab({ agent }: { agent: Agent }) {
|
|
386
|
-
const [tasks, setTasks] = useState<
|
|
317
|
+
const [tasks, setTasks] = useState<any[]>([]);
|
|
387
318
|
const [loading, setLoading] = useState(true);
|
|
388
319
|
const [error, setError] = useState<string | null>(null);
|
|
389
|
-
const [filter, setFilter] = useState<
|
|
320
|
+
const [filter, setFilter] = useState<string>("all");
|
|
390
321
|
const { events } = useTelemetry({ agent_id: agent.id, category: "task" });
|
|
391
322
|
|
|
392
323
|
// Reset state when agent changes
|
|
@@ -421,14 +352,12 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
421
352
|
fetchTasks();
|
|
422
353
|
}, [agent.id, agent.status, filter, events.length]);
|
|
423
354
|
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
default: return "bg-[#222] text-[#666]";
|
|
431
|
-
}
|
|
355
|
+
const statusColors: Record<string, string> = {
|
|
356
|
+
pending: "bg-yellow-500/20 text-yellow-400",
|
|
357
|
+
running: "bg-blue-500/20 text-blue-400",
|
|
358
|
+
completed: "bg-green-500/20 text-green-400",
|
|
359
|
+
failed: "bg-red-500/20 text-red-400",
|
|
360
|
+
cancelled: "bg-gray-500/20 text-gray-400",
|
|
432
361
|
};
|
|
433
362
|
|
|
434
363
|
if (agent.status !== "running") {
|
|
@@ -466,63 +395,91 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
466
395
|
);
|
|
467
396
|
}
|
|
468
397
|
|
|
398
|
+
const filterOptions = [
|
|
399
|
+
{ value: "all", label: "All" },
|
|
400
|
+
{ value: "pending", label: "Pending" },
|
|
401
|
+
{ value: "running", label: "Running" },
|
|
402
|
+
{ value: "completed", label: "Completed" },
|
|
403
|
+
{ value: "failed", label: "Failed" },
|
|
404
|
+
];
|
|
405
|
+
|
|
469
406
|
return (
|
|
470
407
|
<div className="flex-1 overflow-auto p-4">
|
|
471
408
|
{/* Filter tabs */}
|
|
472
409
|
<div className="flex gap-2 mb-4">
|
|
473
|
-
{
|
|
410
|
+
{filterOptions.map(opt => (
|
|
474
411
|
<button
|
|
475
|
-
key={
|
|
476
|
-
onClick={() => setFilter(
|
|
477
|
-
className={`px-3 py-1 text-
|
|
478
|
-
filter ===
|
|
412
|
+
key={opt.value}
|
|
413
|
+
onClick={() => setFilter(opt.value)}
|
|
414
|
+
className={`px-3 py-1.5 rounded text-sm transition ${
|
|
415
|
+
filter === opt.value
|
|
479
416
|
? "bg-[#f97316] text-black"
|
|
480
|
-
: "bg-[#1a1a1a]
|
|
417
|
+
: "bg-[#1a1a1a] hover:bg-[#222]"
|
|
481
418
|
}`}
|
|
482
419
|
>
|
|
483
|
-
{
|
|
420
|
+
{opt.label}
|
|
484
421
|
</button>
|
|
485
422
|
))}
|
|
486
423
|
</div>
|
|
487
424
|
|
|
488
425
|
{tasks.length === 0 ? (
|
|
489
|
-
<div className="text-center py-10
|
|
490
|
-
<
|
|
426
|
+
<div className="text-center py-10">
|
|
427
|
+
<TasksIcon className="w-10 h-10 mx-auto mb-3 text-[#333]" />
|
|
428
|
+
<p className="text-[#666]">No {filter === "all" ? "" : filter + " "}tasks</p>
|
|
429
|
+
<p className="text-sm text-[#444] mt-1">Tasks will appear here when created</p>
|
|
491
430
|
</div>
|
|
492
431
|
) : (
|
|
493
432
|
<div className="space-y-3">
|
|
494
433
|
{tasks.map(task => (
|
|
495
|
-
<div key={task.id} className="bg-[#111] border border-[#1a1a1a] rounded p-
|
|
496
|
-
<div className="flex items-start justify-between">
|
|
434
|
+
<div key={task.id} className="bg-[#111] border border-[#1a1a1a] rounded-lg p-4">
|
|
435
|
+
<div className="flex items-start justify-between mb-2">
|
|
497
436
|
<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
|
-
)}
|
|
437
|
+
<h3 className="font-medium">{task.title || task.name}</h3>
|
|
502
438
|
</div>
|
|
503
|
-
<span className={`
|
|
439
|
+
<span className={`px-2 py-1 rounded text-xs font-medium ml-2 ${statusColors[task.status] || statusColors.pending}`}>
|
|
504
440
|
{task.status}
|
|
505
441
|
</span>
|
|
506
442
|
</div>
|
|
507
443
|
|
|
508
|
-
|
|
509
|
-
<
|
|
510
|
-
|
|
511
|
-
|
|
444
|
+
{task.description && (
|
|
445
|
+
<p className="text-sm text-[#888] mb-2 line-clamp-2">{task.description}</p>
|
|
446
|
+
)}
|
|
447
|
+
|
|
448
|
+
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-[#555]">
|
|
449
|
+
<span className="flex items-center gap-1">
|
|
450
|
+
{task.type === "recurring"
|
|
451
|
+
? <RecurringIcon className="w-3.5 h-3.5" />
|
|
452
|
+
: task.execute_at
|
|
453
|
+
? <ScheduledIcon className="w-3.5 h-3.5" />
|
|
454
|
+
: <TaskOnceIcon className="w-3.5 h-3.5" />
|
|
455
|
+
}
|
|
456
|
+
{task.type === "recurring" && task.recurrence ? formatCron(task.recurrence) : task.type || "once"}
|
|
457
|
+
</span>
|
|
458
|
+
{task.priority !== undefined && (
|
|
459
|
+
<span>Priority: {task.priority}</span>
|
|
460
|
+
)}
|
|
461
|
+
{task.next_run && (
|
|
462
|
+
<span className="text-[#f97316]">{formatRelativeTime(task.next_run)}</span>
|
|
463
|
+
)}
|
|
464
|
+
{!task.next_run && task.execute_at && (
|
|
465
|
+
<span className="text-[#f97316]">{formatRelativeTime(task.execute_at)}</span>
|
|
512
466
|
)}
|
|
467
|
+
<span>Created: {new Date(task.created_at).toLocaleDateString()}</span>
|
|
513
468
|
</div>
|
|
514
469
|
|
|
515
470
|
{task.status === "completed" && task.result && (
|
|
516
|
-
<div className="mt-
|
|
517
|
-
<
|
|
518
|
-
<
|
|
471
|
+
<div className="mt-3 bg-green-500/10 border border-green-500/20 rounded p-3">
|
|
472
|
+
<h4 className="text-xs text-green-400 uppercase tracking-wider mb-1">Result</h4>
|
|
473
|
+
<pre className="text-sm text-green-400 whitespace-pre-wrap break-words">
|
|
474
|
+
{typeof task.result === "string" ? task.result : JSON.stringify(task.result, null, 2)}
|
|
475
|
+
</pre>
|
|
519
476
|
</div>
|
|
520
477
|
)}
|
|
521
478
|
|
|
522
479
|
{task.status === "failed" && task.error && (
|
|
523
|
-
<div className="mt-
|
|
524
|
-
<
|
|
525
|
-
<
|
|
480
|
+
<div className="mt-3 bg-red-500/10 border border-red-500/20 rounded p-3">
|
|
481
|
+
<h4 className="text-xs text-red-400 uppercase tracking-wider mb-1">Error</h4>
|
|
482
|
+
<pre className="text-sm text-red-400 whitespace-pre-wrap break-words">{task.error}</pre>
|
|
526
483
|
</div>
|
|
527
484
|
)}
|
|
528
485
|
</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">
|
|
@@ -224,3 +232,11 @@ export function TaskOnceIcon({ className = "w-4 h-4" }: IconProps) {
|
|
|
224
232
|
</svg>
|
|
225
233
|
);
|
|
226
234
|
}
|
|
235
|
+
|
|
236
|
+
export function BellIcon({ className = "w-5 h-5" }: IconProps) {
|
|
237
|
+
return (
|
|
238
|
+
<svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
239
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
240
|
+
</svg>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -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
|
+
}
|