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.
Files changed (78) hide show
  1. package/dist/ActivityPage.9a1qg4bp.js +3 -0
  2. package/dist/ApiDocsPage.rfpf7ws1.js +4 -0
  3. package/dist/App.1nmg2h01.js +4 -0
  4. package/dist/App.5qw2dtxs.js +4 -0
  5. package/dist/App.6nc5acvk.js +4 -0
  6. package/dist/App.7vzbaz56.js +4 -0
  7. package/dist/App.8rfz30p1.js +4 -0
  8. package/dist/App.amwp54wf.js +4 -0
  9. package/dist/App.e4202qb4.js +267 -0
  10. package/dist/App.errxz2q4.js +4 -0
  11. package/dist/App.f8qsyhpr.js +4 -0
  12. package/dist/App.g8vq68n0.js +20 -0
  13. package/dist/App.kfyrnznw.js +13 -0
  14. package/dist/{App.mq6jqare.js → App.p02f4ret.js} +1 -1
  15. package/dist/App.p93mmyqw.js +4 -0
  16. package/dist/App.qmg33p02.js +4 -0
  17. package/dist/App.sdsc0258.js +4 -0
  18. package/dist/ConnectionsPage.7zqba1r0.js +3 -0
  19. package/dist/McpPage.kf2g327t.js +3 -0
  20. package/dist/SettingsPage.472c15ep.js +3 -0
  21. package/dist/SkillsPage.xdxnh68a.js +3 -0
  22. package/dist/TasksPage.7g0b8xwc.js +3 -0
  23. package/dist/TelemetryPage.pr7rbz4r.js +3 -0
  24. package/dist/TestsPage.zhc6rqjm.js +3 -0
  25. package/dist/apteva-kit.css +1 -1
  26. package/dist/index.html +1 -1
  27. package/dist/styles.css +1 -1
  28. package/package.json +9 -4
  29. package/src/auth/middleware.ts +2 -0
  30. package/src/channels/index.ts +40 -0
  31. package/src/channels/telegram.ts +306 -0
  32. package/src/db.ts +342 -11
  33. package/src/integrations/agentdojo.ts +1 -1
  34. package/src/mcp-handler.ts +31 -24
  35. package/src/mcp-platform.ts +41 -1
  36. package/src/providers.ts +22 -9
  37. package/src/routes/api/agent-utils.ts +38 -2
  38. package/src/routes/api/agents.ts +65 -2
  39. package/src/routes/api/channels.ts +182 -0
  40. package/src/routes/api/integrations.ts +13 -5
  41. package/src/routes/api/mcp.ts +27 -9
  42. package/src/routes/api/projects.ts +19 -2
  43. package/src/routes/api/system.ts +26 -12
  44. package/src/routes/api/telemetry.ts +30 -0
  45. package/src/routes/api/triggers.ts +478 -0
  46. package/src/routes/api/webhooks.ts +171 -0
  47. package/src/routes/api.ts +7 -1
  48. package/src/routes/static.ts +12 -3
  49. package/src/server.ts +43 -6
  50. package/src/triggers/agentdojo.ts +253 -0
  51. package/src/triggers/composio.ts +264 -0
  52. package/src/triggers/index.ts +71 -0
  53. package/src/tui/AgentList.tsx +145 -0
  54. package/src/tui/App.tsx +102 -0
  55. package/src/tui/Login.tsx +104 -0
  56. package/src/tui/api.ts +72 -0
  57. package/src/tui/index.tsx +7 -0
  58. package/src/web/App.tsx +18 -11
  59. package/src/web/components/agents/AgentCard.tsx +14 -7
  60. package/src/web/components/agents/AgentPanel.tsx +94 -137
  61. package/src/web/components/common/Icons.tsx +16 -0
  62. package/src/web/components/common/index.ts +1 -0
  63. package/src/web/components/connections/ConnectionsPage.tsx +54 -0
  64. package/src/web/components/connections/IntegrationsTab.tsx +144 -0
  65. package/src/web/components/connections/OverviewTab.tsx +137 -0
  66. package/src/web/components/connections/TriggersTab.tsx +1169 -0
  67. package/src/web/components/index.ts +1 -0
  68. package/src/web/components/layout/Header.tsx +196 -4
  69. package/src/web/components/layout/Sidebar.tsx +7 -1
  70. package/src/web/components/mcp/IntegrationsPanel.tsx +19 -3
  71. package/src/web/components/settings/SettingsPage.tsx +364 -2
  72. package/src/web/components/tasks/TasksPage.tsx +2 -2
  73. package/src/web/components/tests/TestsPage.tsx +1 -2
  74. package/src/web/context/TelemetryContext.tsx +14 -1
  75. package/src/web/context/index.ts +1 -1
  76. package/src/web/hooks/useAgents.ts +15 -11
  77. package/src/web/types.ts +1 -1
  78. 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 [messages, setMessages] = useState<Array<{ role: string; content: string; created_at: string }>>([]);
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 loadMessages = async (threadId: string) => {
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 (!res.ok) throw new Error("Failed to fetch messages");
197
- const data = await res.json();
198
- setMessages(data.messages || []);
196
+ if (res.ok) {
197
+ const data = await res.json();
198
+ setInitialMessages(convertApiMessages(data.messages || []));
199
+ } else {
200
+ setInitialMessages([]);
201
+ }
199
202
  } catch {
200
- setMessages([]);
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 messages view when a thread is selected
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
- {/* Header with back button */}
255
- <div className="flex items-center gap-3 px-4 py-3 border-b border-[#1a1a1a]">
256
- <button
257
- onClick={() => { setSelectedThread(null); setMessages([]); }}
258
- className="text-[#666] hover:text-[#e0e0e0] transition text-lg"
259
- >
260
-
261
- </button>
262
- <div className="flex-1">
263
- <p className="text-sm font-medium">
264
- {selectedThreadData?.title || `Thread ${selectedThread.slice(0, 8)}`}
265
- </p>
266
- <p className="text-xs text-[#666]">
267
- {selectedThreadData && new Date(selectedThreadData.updated_at || selectedThreadData.created_at).toLocaleString()}
268
- </p>
269
- </div>
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={() => loadMessages(thread.id)}
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<Task[]>([]);
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<"all" | "pending" | "running" | "completed">("all");
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 getStatusColor = (status: Task["status"]) => {
425
- switch (status) {
426
- case "pending": return "bg-yellow-500/20 text-yellow-400";
427
- case "running": return "bg-blue-500/20 text-blue-400";
428
- case "completed": return "bg-green-500/20 text-green-400";
429
- case "failed": return "bg-red-500/20 text-red-400";
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
- {(["all", "pending", "running", "completed"] as const).map(status => (
410
+ {filterOptions.map(opt => (
474
411
  <button
475
- key={status}
476
- onClick={() => setFilter(status)}
477
- className={`px-3 py-1 text-xs rounded transition ${
478
- filter === status
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] text-[#666] hover:text-[#888]"
417
+ : "bg-[#1a1a1a] hover:bg-[#222]"
481
418
  }`}
482
419
  >
483
- {status.charAt(0).toUpperCase() + status.slice(1)}
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 text-[#666]">
490
- <p>No {filter === "all" ? "" : filter + " "}tasks</p>
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-3">
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
- <p className="text-sm font-medium text-[#e0e0e0]">{task.name}</p>
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={`text-xs px-2 py-0.5 rounded ml-2 ${getStatusColor(task.status)}`}>
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
- <div className="flex items-center gap-4 mt-2 text-xs text-[#666]">
509
- <span>Created: {new Date(task.created_at).toLocaleString()}</span>
510
- {task.scheduled_at && (
511
- <span>Scheduled: {new Date(task.scheduled_at).toLocaleString()}</span>
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-2 p-2 bg-[#0a0a0a] rounded text-xs text-[#888]">
517
- <p className="text-[#666] mb-1">Result:</p>
518
- <p className="whitespace-pre-wrap">{task.result}</p>
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-2 p-2 bg-red-500/10 rounded text-xs text-red-400">
524
- <p className="text-red-400/70 mb-1">Error:</p>
525
- <p className="whitespace-pre-wrap">{task.error}</p>
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
+ }
@@ -16,4 +16,5 @@ export {
16
16
  RealtimeIcon,
17
17
  TelemetryIcon,
18
18
  ActivityIcon,
19
+ ConnectionsIcon,
19
20
  } from "./Icons";
@@ -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
+ }