apteva 0.4.32 → 0.4.44
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.c48n83h2.js +3 -0
- package/dist/ApiDocsPage.yzcxx5ax.js +4 -0
- package/dist/App.09yb8t0b.js +1 -0
- package/dist/App.152mbs1r.js +4 -0
- package/dist/App.3a67nx9w.js +4 -0
- package/dist/App.9epx6785.js +4 -0
- package/dist/App.d8955awp.js +4 -0
- package/dist/App.drwb57jq.js +4 -0
- package/dist/App.gssbmajb.js +4 -0
- package/dist/App.qw70pc29.js +53 -0
- package/dist/App.qzbx5wtj.js +4 -0
- package/dist/App.r5serxkt.js +8 -0
- package/dist/App.tpmp9020.js +20 -0
- package/dist/App.v2wb4d7d.js +61 -0
- package/dist/App.vxmaaj0m.js +13 -0
- package/dist/App.w4p2tda9.js +4 -0
- package/dist/App.wv2ng55q.js +221 -0
- package/dist/App.yncnrn0f.js +4 -0
- package/dist/ConnectionsPage.k6cspyqq.js +3 -0
- package/dist/McpPage.cdxm48xj.js +3 -0
- package/dist/SettingsPage.evpv7c2y.js +3 -0
- package/dist/SkillsPage.pvzp6c1a.js +3 -0
- package/dist/TasksPage.6jnvbpsy.js +3 -0
- package/dist/TelemetryPage.t7vk24zc.js +3 -0
- package/dist/TestsPage.5x6658aa.js +3 -0
- package/dist/ThreadsPage.3fvhtevh.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 +10 -9
- package/src/crypto.ts +4 -3
- package/src/db.ts +171 -36
- package/src/integrations/agentdojo.ts +95 -12
- package/src/integrations/index.ts +7 -0
- package/src/mcp-platform.ts +870 -142
- package/src/openapi.ts +96 -0
- package/src/providers.ts +60 -34
- package/src/routes/api/agent-utils.ts +59 -47
- package/src/routes/api/agents.ts +71 -2
- package/src/routes/api/integrations.ts +11 -5
- package/src/routes/api/mcp.ts +5 -4
- package/src/routes/api/meta-agent.ts +37 -1
- package/src/routes/api/projects.ts +3 -3
- package/src/routes/api/providers.ts +121 -30
- package/src/routes/api/skills.ts +2 -3
- package/src/routes/api/system.ts +98 -14
- package/src/routes/api/telemetry.ts +19 -1
- package/src/routes/share.ts +85 -0
- package/src/server.ts +43 -32
- package/src/triggers/agentdojo.ts +2 -2
- package/src/web/App.tsx +107 -21
- package/src/web/components/activity/ActivityPage.tsx +242 -389
- package/src/web/components/agents/AgentCard.tsx +19 -27
- package/src/web/components/agents/AgentPanel.tsx +358 -198
- package/src/web/components/agents/AgentsView.tsx +4 -4
- package/src/web/components/agents/CreateAgentModal.tsx +21 -79
- package/src/web/components/api/ApiDocsPage.tsx +66 -66
- package/src/web/components/auth/CreateAccountStep.tsx +16 -16
- package/src/web/components/auth/LoginPage.tsx +10 -10
- package/src/web/components/common/Icons.tsx +8 -0
- package/src/web/components/common/LoadingSpinner.tsx +2 -2
- package/src/web/components/common/Modal.tsx +8 -8
- package/src/web/components/common/Select.tsx +11 -10
- package/src/web/components/connections/ConnectionsPage.tsx +4 -4
- package/src/web/components/connections/IntegrationsTab.tsx +18 -18
- package/src/web/components/connections/OverviewTab.tsx +13 -13
- package/src/web/components/connections/TriggersTab.tsx +99 -99
- package/src/web/components/dashboard/Dashboard.tsx +177 -52
- package/src/web/components/index.ts +1 -1
- package/src/web/components/layout/Header.tsx +50 -34
- package/src/web/components/layout/Sidebar.tsx +41 -16
- package/src/web/components/mcp/IntegrationsPanel.tsx +160 -69
- package/src/web/components/mcp/McpPage.tsx +218 -209
- package/src/web/components/meta-agent/MetaAgent.tsx +15 -11
- package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
- package/src/web/components/settings/SettingsPage.tsx +389 -221
- package/src/web/components/skills/SkillsPage.tsx +88 -88
- package/src/web/components/tasks/TasksPage.tsx +385 -68
- package/src/web/components/telemetry/TelemetryPage.tsx +294 -39
- package/src/web/components/tests/TestsPage.tsx +50 -50
- package/src/web/components/threads/ThreadsPage.tsx +315 -0
- package/src/web/context/AuthContext.tsx +3 -3
- package/src/web/context/ProjectContext.tsx +8 -3
- package/src/web/context/TelemetryContext.tsx +24 -6
- package/src/web/context/ThemeContext.tsx +69 -0
- package/src/web/context/index.ts +3 -1
- package/src/web/styles.css +25 -7
- package/src/web/themes.ts +99 -0
- package/src/web/types.ts +4 -7
- package/dist/ActivityPage.41nbye4r.js +0 -3
- package/dist/ApiDocsPage.4smnt8m3.js +0 -4
- package/dist/App.0sbax9et.js +0 -4
- package/dist/App.0ws427h8.js +0 -4
- package/dist/App.6q6bar8b.js +0 -4
- package/dist/App.80301vdb.js +0 -4
- package/dist/App.af2wg84v.js +0 -267
- package/dist/App.ca1rz1ph.js +0 -4
- package/dist/App.ensa6z0r.js +0 -4
- package/dist/App.f8g7tych.js +0 -13
- package/dist/App.mvtqv6qc.js +0 -20
- package/dist/App.ncgc9cxy.js +0 -4
- package/dist/App.p02f4ret.js +0 -1
- package/dist/App.p0fb1pds.js +0 -4
- package/dist/App.pmaq48sj.js +0 -4
- package/dist/App.yv87t9m5.js +0 -4
- package/dist/App.zjmfm8p6.js +0 -4
- package/dist/ConnectionsPage.anb3rv9a.js +0 -3
- package/dist/McpPage.y396h6fy.js +0 -3
- package/dist/SettingsPage.p1hc60gk.js +0 -3
- package/dist/SkillsPage.yj3xdsay.js +0 -3
- package/dist/TasksPage.sjv0khtv.js +0 -3
- package/dist/TelemetryPage.2qm4w16r.js +0 -3
- package/dist/TestsPage.zzs4qfj8.js +0 -3
|
@@ -271,7 +271,7 @@ export function TestsPage() {
|
|
|
271
271
|
running: "bg-blue-900/50 text-blue-400",
|
|
272
272
|
};
|
|
273
273
|
return (
|
|
274
|
-
<span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[status] || "bg-[
|
|
274
|
+
<span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[status] || "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]"}`}>
|
|
275
275
|
{status.toUpperCase()}
|
|
276
276
|
</span>
|
|
277
277
|
);
|
|
@@ -297,7 +297,7 @@ export function TestsPage() {
|
|
|
297
297
|
<div className="flex items-center justify-between mb-6">
|
|
298
298
|
<div>
|
|
299
299
|
<h1 className="text-xl font-bold">Tests</h1>
|
|
300
|
-
<p className="text-sm text-[
|
|
300
|
+
<p className="text-sm text-[var(--color-text-muted)] mt-1">
|
|
301
301
|
Describe behavior, AI handles the rest
|
|
302
302
|
</p>
|
|
303
303
|
</div>
|
|
@@ -306,14 +306,14 @@ export function TestsPage() {
|
|
|
306
306
|
<button
|
|
307
307
|
onClick={handleRunAll}
|
|
308
308
|
disabled={runningAll}
|
|
309
|
-
className="px-4 py-2 bg-[
|
|
309
|
+
className="px-4 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] text-[var(--color-text)] rounded text-sm font-medium transition disabled:opacity-50"
|
|
310
310
|
>
|
|
311
311
|
{runningAll ? "Running..." : "Run All"}
|
|
312
312
|
</button>
|
|
313
313
|
)}
|
|
314
314
|
<button
|
|
315
315
|
onClick={openCreate}
|
|
316
|
-
className="px-4 py-2 bg-[
|
|
316
|
+
className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white rounded text-sm font-medium transition"
|
|
317
317
|
>
|
|
318
318
|
+ New Test
|
|
319
319
|
</button>
|
|
@@ -322,19 +322,19 @@ export function TestsPage() {
|
|
|
322
322
|
|
|
323
323
|
{/* Test list */}
|
|
324
324
|
{loading ? (
|
|
325
|
-
<div className="text-[
|
|
325
|
+
<div className="text-[var(--color-text-muted)] text-sm">Loading...</div>
|
|
326
326
|
) : tests.length === 0 ? (
|
|
327
327
|
<div className="text-center py-16">
|
|
328
|
-
<div className="text-[
|
|
328
|
+
<div className="text-[var(--color-border-light)] text-4xl mb-4">
|
|
329
329
|
<svg className="w-12 h-12 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
330
330
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4" />
|
|
331
331
|
</svg>
|
|
332
332
|
</div>
|
|
333
|
-
<p className="text-[
|
|
334
|
-
<p className="text-xs text-[
|
|
333
|
+
<p className="text-[var(--color-text-muted)] mb-2">No tests yet</p>
|
|
334
|
+
<p className="text-xs text-[var(--color-text-faint)] mb-4">Describe what your agents should do and let AI verify it</p>
|
|
335
335
|
<button
|
|
336
336
|
onClick={openCreate}
|
|
337
|
-
className="px-4 py-2 bg-[
|
|
337
|
+
className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] text-white rounded text-sm font-medium transition"
|
|
338
338
|
>
|
|
339
339
|
Create your first test
|
|
340
340
|
</button>
|
|
@@ -342,7 +342,7 @@ export function TestsPage() {
|
|
|
342
342
|
) : (
|
|
343
343
|
<div className="space-y-3">
|
|
344
344
|
{tests.map(tc => (
|
|
345
|
-
<div key={tc.id} className="bg-[
|
|
345
|
+
<div key={tc.id} className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
346
346
|
<div className="flex items-start justify-between">
|
|
347
347
|
<div className="flex-1 min-w-0">
|
|
348
348
|
<div className="flex items-center gap-2 mb-1">
|
|
@@ -352,18 +352,18 @@ export function TestsPage() {
|
|
|
352
352
|
: tc.last_run && (<>
|
|
353
353
|
{statusBadge(tc.last_run.status)}
|
|
354
354
|
{tc.last_run.score != null && (
|
|
355
|
-
<span className="text-xs text-[
|
|
355
|
+
<span className="text-xs text-[var(--color-text-secondary)] font-mono">{tc.last_run.score}/10</span>
|
|
356
356
|
)}
|
|
357
357
|
</>)
|
|
358
358
|
}
|
|
359
359
|
</div>
|
|
360
360
|
{tc.behavior && (
|
|
361
|
-
<p className="text-xs text-[
|
|
361
|
+
<p className="text-xs text-[var(--color-text-secondary)] mb-1.5 line-clamp-2">{tc.behavior}</p>
|
|
362
362
|
)}
|
|
363
|
-
<div className="text-xs text-[
|
|
363
|
+
<div className="text-xs text-[var(--color-text-muted)] space-y-0.5">
|
|
364
364
|
<div>
|
|
365
365
|
Agent:{" "}
|
|
366
|
-
<span className="text-[
|
|
366
|
+
<span className="text-[var(--color-text-secondary)]">
|
|
367
367
|
{tc.agent_name || (tc.last_run?.selected_agent_name
|
|
368
368
|
? `${tc.last_run.selected_agent_name} (auto-selected)`
|
|
369
369
|
: "Auto (AI picks)")}
|
|
@@ -371,18 +371,18 @@ export function TestsPage() {
|
|
|
371
371
|
</div>
|
|
372
372
|
{tc.last_run?.generated_message && (
|
|
373
373
|
<div className="truncate">
|
|
374
|
-
Message: <span className="text-[
|
|
374
|
+
Message: <span className="text-[var(--color-text-secondary)]">"{tc.last_run.generated_message}"</span>
|
|
375
375
|
</div>
|
|
376
376
|
)}
|
|
377
377
|
{tc.input_message && !tc.last_run?.generated_message && (
|
|
378
378
|
<div className="truncate">
|
|
379
|
-
Message: <span className="text-[
|
|
379
|
+
Message: <span className="text-[var(--color-text-secondary)]">"{tc.input_message}"</span>
|
|
380
380
|
</div>
|
|
381
381
|
)}
|
|
382
382
|
{tc.last_run && (
|
|
383
383
|
<div>
|
|
384
384
|
Last run:{" "}
|
|
385
|
-
<span className="text-[
|
|
385
|
+
<span className="text-[var(--color-text-secondary)]">
|
|
386
386
|
{tc.last_run.duration_ms ? `${(tc.last_run.duration_ms / 1000).toFixed(1)}s` : "---"}
|
|
387
387
|
{tc.last_run.judge_reasoning && ` --- "${tc.last_run.judge_reasoning.slice(0, 80)}${tc.last_run.judge_reasoning.length > 80 ? "..." : ""}"`}
|
|
388
388
|
</span>
|
|
@@ -393,7 +393,7 @@ export function TestsPage() {
|
|
|
393
393
|
<div className="flex items-center gap-1 ml-3 shrink-0">
|
|
394
394
|
<button
|
|
395
395
|
onClick={() => viewRuns(tc.id)}
|
|
396
|
-
className="px-2 py-1 text-xs text-[
|
|
396
|
+
className="px-2 py-1 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-raised)] rounded transition"
|
|
397
397
|
title="View run history"
|
|
398
398
|
>
|
|
399
399
|
History
|
|
@@ -401,19 +401,19 @@ export function TestsPage() {
|
|
|
401
401
|
<button
|
|
402
402
|
onClick={() => handleRun(tc.id)}
|
|
403
403
|
disabled={runningTests.has(tc.id)}
|
|
404
|
-
className="px-3 py-1 text-xs bg-[
|
|
404
|
+
className="px-3 py-1 text-xs bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] text-[var(--color-text)] rounded transition disabled:opacity-50"
|
|
405
405
|
>
|
|
406
406
|
{runningTests.has(tc.id) ? "Running..." : "Run"}
|
|
407
407
|
</button>
|
|
408
408
|
<button
|
|
409
409
|
onClick={() => openEdit(tc)}
|
|
410
|
-
className="px-2 py-1 text-xs text-[
|
|
410
|
+
className="px-2 py-1 text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-raised)] rounded transition"
|
|
411
411
|
>
|
|
412
412
|
Edit
|
|
413
413
|
</button>
|
|
414
414
|
<button
|
|
415
415
|
onClick={() => handleDelete(tc.id)}
|
|
416
|
-
className="px-2 py-1 text-xs text-[
|
|
416
|
+
className="px-2 py-1 text-xs text-[var(--color-text-muted)] hover:text-red-400 hover:bg-[var(--color-surface-raised)] rounded transition"
|
|
417
417
|
>
|
|
418
418
|
Delete
|
|
419
419
|
</button>
|
|
@@ -428,50 +428,50 @@ export function TestsPage() {
|
|
|
428
428
|
{selectedRuns && (
|
|
429
429
|
<div className="mt-6">
|
|
430
430
|
<div className="flex items-center justify-between mb-3">
|
|
431
|
-
<h2 className="text-sm font-bold text-[
|
|
431
|
+
<h2 className="text-sm font-bold text-[var(--color-text-secondary)]">Run History</h2>
|
|
432
432
|
<button
|
|
433
433
|
onClick={() => { setSelectedRuns(null); setExpandedRun(null); }}
|
|
434
|
-
className="text-xs text-[
|
|
434
|
+
className="text-xs text-[var(--color-text-muted)] hover:text-[var(--color-text-secondary)]"
|
|
435
435
|
>
|
|
436
436
|
Close
|
|
437
437
|
</button>
|
|
438
438
|
</div>
|
|
439
439
|
{selectedRuns.runs.length === 0 ? (
|
|
440
|
-
<p className="text-sm text-[
|
|
440
|
+
<p className="text-sm text-[var(--color-text-muted)]">No runs yet</p>
|
|
441
441
|
) : (
|
|
442
442
|
<div className="space-y-2">
|
|
443
443
|
{selectedRuns.runs.map(run => (
|
|
444
|
-
<div key={run.id} className="bg-[
|
|
444
|
+
<div key={run.id} className="bg-[var(--color-bg-secondary)] border border-[var(--color-border)] rounded p-3">
|
|
445
445
|
<div
|
|
446
446
|
className="flex items-center justify-between cursor-pointer"
|
|
447
447
|
onClick={() => setExpandedRun(expandedRun === run.id ? null : run.id)}
|
|
448
448
|
>
|
|
449
449
|
<div className="flex items-center gap-3">
|
|
450
450
|
{statusBadge(run.status)}
|
|
451
|
-
<span className="text-xs text-[
|
|
451
|
+
<span className="text-xs text-[var(--color-text-muted)]">
|
|
452
452
|
{run.duration_ms ? `${(run.duration_ms / 1000).toFixed(1)}s` : "---"}
|
|
453
453
|
</span>
|
|
454
454
|
{run.score != null && (
|
|
455
|
-
<span className="text-xs text-[
|
|
455
|
+
<span className="text-xs text-[var(--color-text-secondary)] font-mono">{run.score}/10</span>
|
|
456
456
|
)}
|
|
457
457
|
{run.selected_agent_name && (
|
|
458
|
-
<span className="text-xs text-[
|
|
458
|
+
<span className="text-xs text-[var(--color-text-faint)]">
|
|
459
459
|
Agent: {run.selected_agent_name}
|
|
460
460
|
</span>
|
|
461
461
|
)}
|
|
462
|
-
<span className="text-xs text-[
|
|
462
|
+
<span className="text-xs text-[var(--color-text-faint)]">
|
|
463
463
|
{new Date(run.created_at).toLocaleString()}
|
|
464
464
|
</span>
|
|
465
465
|
</div>
|
|
466
|
-
<span className="text-xs text-[
|
|
466
|
+
<span className="text-xs text-[var(--color-text-faint)]">{expandedRun === run.id ? "---" : "+"}</span>
|
|
467
467
|
</div>
|
|
468
468
|
{expandedRun === run.id && (
|
|
469
469
|
<div className="mt-3 space-y-2">
|
|
470
470
|
{run.planner_reasoning && (
|
|
471
471
|
<div>
|
|
472
|
-
<div className="text-xs text-[
|
|
473
|
-
<div className="text-sm text-[
|
|
474
|
-
{run.selected_agent_name && <span className="text-[
|
|
472
|
+
<div className="text-xs text-[var(--color-text-muted)] mb-1">Planner:</div>
|
|
473
|
+
<div className="text-sm text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded">
|
|
474
|
+
{run.selected_agent_name && <span className="text-[var(--color-accent)]">{run.selected_agent_name}</span>}
|
|
475
475
|
{run.selected_agent_name && " --- "}
|
|
476
476
|
{run.planner_reasoning}
|
|
477
477
|
</div>
|
|
@@ -479,26 +479,26 @@ export function TestsPage() {
|
|
|
479
479
|
)}
|
|
480
480
|
{run.generated_message && (
|
|
481
481
|
<div>
|
|
482
|
-
<div className="text-xs text-[
|
|
483
|
-
<div className="text-sm text-[
|
|
482
|
+
<div className="text-xs text-[var(--color-text-muted)] mb-1">Generated Message:</div>
|
|
483
|
+
<div className="text-sm text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded">"{run.generated_message}"</div>
|
|
484
484
|
</div>
|
|
485
485
|
)}
|
|
486
486
|
{run.judge_reasoning && (
|
|
487
487
|
<div>
|
|
488
|
-
<div className="text-xs text-[
|
|
489
|
-
<div className="text-sm text-[
|
|
488
|
+
<div className="text-xs text-[var(--color-text-muted)] mb-1">Judge:</div>
|
|
489
|
+
<div className="text-sm text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded">{run.judge_reasoning}</div>
|
|
490
490
|
</div>
|
|
491
491
|
)}
|
|
492
492
|
{run.error && (
|
|
493
493
|
<div>
|
|
494
494
|
<div className="text-xs text-red-400 mb-1">Error:</div>
|
|
495
|
-
<div className="text-sm text-red-300 bg-[
|
|
495
|
+
<div className="text-sm text-red-300 bg-[var(--color-bg)] p-2 rounded">{run.error}</div>
|
|
496
496
|
</div>
|
|
497
497
|
)}
|
|
498
498
|
{run.agent_response && (
|
|
499
499
|
<div>
|
|
500
|
-
<div className="text-xs text-[
|
|
501
|
-
<pre className="text-xs text-[
|
|
500
|
+
<div className="text-xs text-[var(--color-text-muted)] mb-1">Agent Response (Thread):</div>
|
|
501
|
+
<pre className="text-xs text-[var(--color-text-secondary)] bg-[var(--color-bg)] p-2 rounded overflow-auto max-h-64">
|
|
502
502
|
{run.agent_response}
|
|
503
503
|
</pre>
|
|
504
504
|
</div>
|
|
@@ -515,34 +515,34 @@ export function TestsPage() {
|
|
|
515
515
|
{/* Create/Edit Form Modal */}
|
|
516
516
|
{showForm && (
|
|
517
517
|
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center" onClick={() => setShowForm(false)}>
|
|
518
|
-
<div className="bg-[
|
|
518
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg w-full max-w-lg mx-4 p-6" onClick={e => e.stopPropagation()}>
|
|
519
519
|
<h2 className="text-lg font-bold mb-4">{editingTest ? "Edit Test" : "New Test"}</h2>
|
|
520
520
|
|
|
521
521
|
<div className="space-y-4">
|
|
522
522
|
<div>
|
|
523
|
-
<label className="block text-xs text-[
|
|
523
|
+
<label className="block text-xs text-[var(--color-text-muted)] mb-1">Name</label>
|
|
524
524
|
<input
|
|
525
525
|
value={formName}
|
|
526
526
|
onChange={e => setFormName(e.target.value)}
|
|
527
527
|
placeholder="e.g. Social Media Posting"
|
|
528
|
-
className="w-full bg-[
|
|
528
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)]"
|
|
529
529
|
/>
|
|
530
530
|
</div>
|
|
531
531
|
|
|
532
532
|
<div>
|
|
533
|
-
<label className="block text-xs text-[
|
|
533
|
+
<label className="block text-xs text-[var(--color-text-muted)] mb-1">Behavior</label>
|
|
534
534
|
<textarea
|
|
535
535
|
value={formBehavior}
|
|
536
536
|
onChange={e => setFormBehavior(e.target.value)}
|
|
537
537
|
placeholder="Describe what should happen, e.g. 'When asked to post on social media, the agent creates a proper post with relevant hashtags and confirms it was published'"
|
|
538
538
|
rows={3}
|
|
539
|
-
className="w-full bg-[
|
|
539
|
+
className="w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-3 py-2 text-sm focus:outline-none focus:border-[var(--color-accent)] resize-none"
|
|
540
540
|
/>
|
|
541
|
-
<p className="text-xs text-[
|
|
541
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">AI will generate the test message and evaluate results based on this</p>
|
|
542
542
|
</div>
|
|
543
543
|
|
|
544
544
|
<div>
|
|
545
|
-
<label className="block text-xs text-[
|
|
545
|
+
<label className="block text-xs text-[var(--color-text-muted)] mb-1">Agent</label>
|
|
546
546
|
<Select
|
|
547
547
|
value={formAgentId}
|
|
548
548
|
onChange={setFormAgentId}
|
|
@@ -552,21 +552,21 @@ export function TestsPage() {
|
|
|
552
552
|
label: `${a.name} (${a.status})`,
|
|
553
553
|
}))}
|
|
554
554
|
/>
|
|
555
|
-
<p className="text-xs text-[
|
|
555
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">Leave empty to let AI choose the right agent</p>
|
|
556
556
|
</div>
|
|
557
557
|
</div>
|
|
558
558
|
|
|
559
559
|
<div className="flex justify-end gap-2 mt-6">
|
|
560
560
|
<button
|
|
561
561
|
onClick={() => setShowForm(false)}
|
|
562
|
-
className="px-4 py-2 text-sm text-[
|
|
562
|
+
className="px-4 py-2 text-sm text-[var(--color-text-secondary)] hover:text-[var(--color-text)] transition"
|
|
563
563
|
>
|
|
564
564
|
Cancel
|
|
565
565
|
</button>
|
|
566
566
|
<button
|
|
567
567
|
onClick={handleSave}
|
|
568
568
|
disabled={!formName || !formBehavior}
|
|
569
|
-
className="px-4 py-2 bg-[
|
|
569
|
+
className="px-4 py-2 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-white rounded text-sm font-medium transition"
|
|
570
570
|
>
|
|
571
571
|
{editingTest ? "Save" : "Create"}
|
|
572
572
|
</button>
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
2
|
+
import { Chat, convertApiMessages } from "@apteva/apteva-kit";
|
|
3
|
+
import { useAgentActivity, useAuth, useProjects, useTelemetryContext, useTheme } from "../../context";
|
|
4
|
+
import type { TelemetryEvent } from "../../context";
|
|
5
|
+
import type { Agent, Route } from "../../types";
|
|
6
|
+
|
|
7
|
+
interface Thread {
|
|
8
|
+
id: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
created_at: string;
|
|
11
|
+
updated_at: string;
|
|
12
|
+
message_count?: number;
|
|
13
|
+
agent_id: string;
|
|
14
|
+
agent_name: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ThreadsPageProps {
|
|
18
|
+
agents: Agent[];
|
|
19
|
+
onNavigate?: (route: Route) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
|
|
23
|
+
const { theme } = useTheme();
|
|
24
|
+
const { authFetch } = useAuth();
|
|
25
|
+
const { currentProjectId } = useProjects();
|
|
26
|
+
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
27
|
+
|
|
28
|
+
const [threads, setThreads] = useState<Thread[]>([]);
|
|
29
|
+
const [selectedThread, setSelectedThread] = useState<Thread | null>(null);
|
|
30
|
+
const [newChatAgent, setNewChatAgent] = useState<Agent | null>(null);
|
|
31
|
+
const [initialMessages, setInitialMessages] = useState<any[]>([]);
|
|
32
|
+
const [loadingThreads, setLoadingThreads] = useState(true);
|
|
33
|
+
const [loadingMessages, setLoadingMessages] = useState(false);
|
|
34
|
+
const [historicalActivities, setHistoricalActivities] = useState<TelemetryEvent[]>([]);
|
|
35
|
+
const [showAgentPicker, setShowAgentPicker] = useState(false);
|
|
36
|
+
const [newChatKey, setNewChatKey] = useState(0);
|
|
37
|
+
|
|
38
|
+
const filteredAgents = useMemo(() => {
|
|
39
|
+
if (currentProjectId === null) return agents;
|
|
40
|
+
if (currentProjectId === "unassigned") return agents.filter(a => !a.projectId);
|
|
41
|
+
return agents.filter(a => a.projectId === currentProjectId);
|
|
42
|
+
}, [agents, currentProjectId]);
|
|
43
|
+
|
|
44
|
+
const runningAgents = useMemo(() => filteredAgents.filter(a => a.status === "running"), [filteredAgents]);
|
|
45
|
+
const agentIds = useMemo(() => new Set(filteredAgents.map(a => a.id)), [filteredAgents]);
|
|
46
|
+
|
|
47
|
+
// Fetch consolidated threads
|
|
48
|
+
const fetchThreads = useCallback(async () => {
|
|
49
|
+
try {
|
|
50
|
+
const projectParam = currentProjectId ? `?project_id=${encodeURIComponent(currentProjectId)}` : "";
|
|
51
|
+
const [threadsRes, activityRes] = await Promise.all([
|
|
52
|
+
authFetch(`/api/threads${projectParam}`).catch(() => null),
|
|
53
|
+
authFetch(`/api/telemetry/events?type=thread_activity&limit=100${projectParam ? `&${projectParam}` : ""}`).catch(() => null),
|
|
54
|
+
]);
|
|
55
|
+
if (threadsRes?.ok) {
|
|
56
|
+
const data = await threadsRes.json();
|
|
57
|
+
setThreads(data.threads || []);
|
|
58
|
+
}
|
|
59
|
+
if (activityRes?.ok) {
|
|
60
|
+
const data = await activityRes.json();
|
|
61
|
+
setHistoricalActivities(data.events || []);
|
|
62
|
+
}
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error("Failed to fetch threads:", e);
|
|
65
|
+
} finally {
|
|
66
|
+
setLoadingThreads(false);
|
|
67
|
+
}
|
|
68
|
+
}, [authFetch, currentProjectId]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => { fetchThreads(); }, [fetchThreads, statusChangeCounter]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const interval = setInterval(fetchThreads, 15000);
|
|
74
|
+
return () => clearInterval(interval);
|
|
75
|
+
}, [fetchThreads]);
|
|
76
|
+
|
|
77
|
+
// Open an existing thread
|
|
78
|
+
const openThread = useCallback(async (thread: Thread) => {
|
|
79
|
+
setNewChatAgent(null);
|
|
80
|
+
setLoadingMessages(true);
|
|
81
|
+
setSelectedThread(thread);
|
|
82
|
+
try {
|
|
83
|
+
const res = await authFetch(`/api/agents/${thread.agent_id}/threads/${thread.id}/messages`);
|
|
84
|
+
if (res.ok) {
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
setInitialMessages(convertApiMessages(data.messages || []));
|
|
87
|
+
} else {
|
|
88
|
+
setInitialMessages([]);
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
setInitialMessages([]);
|
|
92
|
+
}
|
|
93
|
+
setLoadingMessages(false);
|
|
94
|
+
}, [authFetch]);
|
|
95
|
+
|
|
96
|
+
// Start a new conversation with an agent
|
|
97
|
+
const startNewChat = (agent: Agent) => {
|
|
98
|
+
setSelectedThread(null);
|
|
99
|
+
setInitialMessages([]);
|
|
100
|
+
setNewChatAgent(agent);
|
|
101
|
+
setNewChatKey(k => k + 1);
|
|
102
|
+
setShowAgentPicker(false);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Merge real-time + historical activity
|
|
106
|
+
const activities = useMemo(() => {
|
|
107
|
+
const realtimeThreadEvents = realtimeEvents.filter(e => e.type === "thread_activity" && !e.data?.parent_id);
|
|
108
|
+
const seen = new Set(realtimeThreadEvents.map(e => e.id));
|
|
109
|
+
const merged = [...realtimeThreadEvents];
|
|
110
|
+
for (const evt of historicalActivities) {
|
|
111
|
+
if (!seen.has(evt.id) && !evt.data?.parent_id) { merged.push(evt); seen.add(evt.id); }
|
|
112
|
+
}
|
|
113
|
+
return merged
|
|
114
|
+
.filter(e => agentIds.has(e.agent_id))
|
|
115
|
+
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
116
|
+
.slice(0, 100);
|
|
117
|
+
}, [realtimeEvents, historicalActivities, agentIds]);
|
|
118
|
+
|
|
119
|
+
// Group activities by thread_id
|
|
120
|
+
const activityByThread = useMemo(() => {
|
|
121
|
+
const map = new Map<string, TelemetryEvent[]>();
|
|
122
|
+
for (const evt of activities) {
|
|
123
|
+
const tid = evt.thread_id || evt.data?.thread_id as string;
|
|
124
|
+
if (tid) {
|
|
125
|
+
if (!map.has(tid)) map.set(tid, []);
|
|
126
|
+
map.get(tid)!.push(evt);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return map;
|
|
130
|
+
}, [activities]);
|
|
131
|
+
|
|
132
|
+
const runningCount = runningAgents.length;
|
|
133
|
+
|
|
134
|
+
// What's currently shown in chat
|
|
135
|
+
const chatAgentId = selectedThread?.agent_id || newChatAgent?.id;
|
|
136
|
+
const chatAgentName = selectedThread?.agent_name || newChatAgent?.name;
|
|
137
|
+
const chatThreadId = selectedThread?.id;
|
|
138
|
+
const chatKey = selectedThread
|
|
139
|
+
? `${selectedThread.agent_id}-${selectedThread.id}`
|
|
140
|
+
: newChatAgent
|
|
141
|
+
? `new-${newChatAgent.id}-${newChatKey}`
|
|
142
|
+
: null;
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
146
|
+
{/* Header */}
|
|
147
|
+
<div className="px-6 pt-6 pb-4 shrink-0">
|
|
148
|
+
<div className="flex items-center justify-between">
|
|
149
|
+
<h1 className="text-xl font-semibold">Threads</h1>
|
|
150
|
+
<span className="text-sm text-[var(--color-text-muted)]">
|
|
151
|
+
{threads.length} threads from {runningCount} running agents
|
|
152
|
+
</span>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Messenger layout: 1/4 threads | 3/4 chat */}
|
|
157
|
+
<div className="flex-1 flex min-h-0 overflow-hidden">
|
|
158
|
+
{/* Thread list — 1/4 */}
|
|
159
|
+
<div className="w-1/4 min-w-[260px] max-w-[360px] flex flex-col overflow-hidden">
|
|
160
|
+
{/* New conversation button */}
|
|
161
|
+
<div className="p-2 shrink-0">
|
|
162
|
+
<div className="relative">
|
|
163
|
+
<button
|
|
164
|
+
onClick={() => setShowAgentPicker(!showAgentPicker)}
|
|
165
|
+
disabled={runningAgents.length === 0}
|
|
166
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg bg-[var(--color-accent-10)] text-[var(--color-accent)] text-sm font-medium hover:bg-[var(--color-accent-20)] transition disabled:opacity-30 disabled:cursor-not-allowed"
|
|
167
|
+
>
|
|
168
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
169
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
|
170
|
+
</svg>
|
|
171
|
+
New conversation
|
|
172
|
+
</button>
|
|
173
|
+
|
|
174
|
+
{/* Agent picker dropdown */}
|
|
175
|
+
{showAgentPicker && (
|
|
176
|
+
<>
|
|
177
|
+
<div className="fixed inset-0 z-40" onClick={() => setShowAgentPicker(false)} />
|
|
178
|
+
<div className="absolute top-full left-0 right-0 mt-1 bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-lg shadow-xl z-50 max-h-60 overflow-auto">
|
|
179
|
+
{runningAgents.map(agent => (
|
|
180
|
+
<button
|
|
181
|
+
key={agent.id}
|
|
182
|
+
onClick={() => startNewChat(agent)}
|
|
183
|
+
className="w-full text-left px-3 py-2.5 hover:bg-[var(--color-surface-raised)] transition"
|
|
184
|
+
>
|
|
185
|
+
<p className="text-sm font-medium truncate">{agent.name}</p>
|
|
186
|
+
<p className="text-[10px] text-[var(--color-text-faint)]">{agent.provider} · {agent.model}</p>
|
|
187
|
+
</button>
|
|
188
|
+
))}
|
|
189
|
+
</div>
|
|
190
|
+
</>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div className="flex-1 overflow-auto px-2 pb-2">
|
|
196
|
+
{loadingThreads ? (
|
|
197
|
+
<div className="p-6 text-center text-[var(--color-text-faint)] text-sm">Loading threads...</div>
|
|
198
|
+
) : threads.length === 0 ? (
|
|
199
|
+
<div className="p-6 text-center text-[var(--color-text-faint)] text-sm">
|
|
200
|
+
<p>No threads yet</p>
|
|
201
|
+
<p className="mt-1 text-[var(--color-text-faint)]">Start a conversation or wait for agents</p>
|
|
202
|
+
</div>
|
|
203
|
+
) : (
|
|
204
|
+
<div className="space-y-0.5">
|
|
205
|
+
{threads.map(thread => (
|
|
206
|
+
<ThreadRow
|
|
207
|
+
key={`${thread.agent_id}-${thread.id}`}
|
|
208
|
+
thread={thread}
|
|
209
|
+
selected={selectedThread?.id === thread.id && selectedThread?.agent_id === thread.agent_id}
|
|
210
|
+
activities={activityByThread.get(thread.id) || []}
|
|
211
|
+
onSelect={() => openThread(thread)}
|
|
212
|
+
/>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
|
|
219
|
+
{/* Chat — 3/4 */}
|
|
220
|
+
<div className="flex-1 flex flex-col min-h-0 overflow-hidden">
|
|
221
|
+
{chatAgentId && chatKey ? (
|
|
222
|
+
loadingMessages ? (
|
|
223
|
+
<div className="flex-1 flex items-center justify-center text-[var(--color-text-muted)]">Loading messages...</div>
|
|
224
|
+
) : (
|
|
225
|
+
<Chat
|
|
226
|
+
key={chatKey}
|
|
227
|
+
agentId="default"
|
|
228
|
+
apiUrl={`/api/agents/${chatAgentId}`}
|
|
229
|
+
threadId={chatThreadId}
|
|
230
|
+
initialMessages={initialMessages}
|
|
231
|
+
placeholder={`Message ${chatAgentName}...`}
|
|
232
|
+
headerTitle={chatAgentName}
|
|
233
|
+
variant="terminal"
|
|
234
|
+
theme={theme.id as "light" | "dark"}
|
|
235
|
+
showHeader={true}
|
|
236
|
+
/>
|
|
237
|
+
)
|
|
238
|
+
) : (
|
|
239
|
+
<div className="flex-1 flex items-center justify-center">
|
|
240
|
+
<div className="text-center text-[var(--color-text-faint)]">
|
|
241
|
+
<svg className="w-12 h-12 mx-auto mb-3 text-[var(--color-border-light)]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
242
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
|
|
243
|
+
</svg>
|
|
244
|
+
<p className="text-sm">Select a thread or start a new conversation</p>
|
|
245
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">Chat with any running agent</p>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// --- Thread Row ---
|
|
256
|
+
|
|
257
|
+
function ThreadRow({ thread, selected, activities, onSelect }: {
|
|
258
|
+
thread: Thread;
|
|
259
|
+
selected: boolean;
|
|
260
|
+
activities: TelemetryEvent[];
|
|
261
|
+
onSelect: () => void;
|
|
262
|
+
}) {
|
|
263
|
+
const { isActive } = useAgentActivity(thread.agent_id);
|
|
264
|
+
const latestActivity = activities[0];
|
|
265
|
+
const activityText = latestActivity?.data?.activity as string | undefined;
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<button
|
|
269
|
+
onClick={onSelect}
|
|
270
|
+
className={`w-full text-left px-3 py-2.5 rounded-lg transition ${
|
|
271
|
+
selected
|
|
272
|
+
? "bg-[var(--color-accent-10)]"
|
|
273
|
+
: "hover:bg-[var(--color-bg-secondary)]"
|
|
274
|
+
}`}
|
|
275
|
+
>
|
|
276
|
+
<div className="flex items-center justify-between gap-2 mb-1">
|
|
277
|
+
<span className="text-sm font-medium truncate">
|
|
278
|
+
{thread.title || `Thread ${thread.id.slice(0, 8)}`}
|
|
279
|
+
</span>
|
|
280
|
+
<span className="text-[10px] text-[var(--color-text-faint)] shrink-0">{timeAgo(thread.updated_at)}</span>
|
|
281
|
+
</div>
|
|
282
|
+
<div className="flex items-center gap-1.5">
|
|
283
|
+
<span
|
|
284
|
+
className={`w-1.5 h-1.5 rounded-full shrink-0 ${
|
|
285
|
+
isActive ? "bg-green-400 animate-pulse" : "bg-[var(--color-scrollbar)]"
|
|
286
|
+
}`}
|
|
287
|
+
/>
|
|
288
|
+
<span className="text-[11px] text-[var(--color-accent)]">{thread.agent_name}</span>
|
|
289
|
+
{thread.message_count != null && (
|
|
290
|
+
<>
|
|
291
|
+
<span className="text-[var(--color-border-light)]">·</span>
|
|
292
|
+
<span className="text-[10px] text-[var(--color-text-faint)]">{thread.message_count} msgs</span>
|
|
293
|
+
</>
|
|
294
|
+
)}
|
|
295
|
+
</div>
|
|
296
|
+
{activityText && (
|
|
297
|
+
<p className="text-[11px] text-[var(--color-text-faint)] truncate mt-1">{activityText}</p>
|
|
298
|
+
)}
|
|
299
|
+
</button>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// --- Helpers ---
|
|
304
|
+
|
|
305
|
+
function timeAgo(timestamp: string): string {
|
|
306
|
+
const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000);
|
|
307
|
+
if (seconds < 5) return "just now";
|
|
308
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
309
|
+
const minutes = Math.floor(seconds / 60);
|
|
310
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
311
|
+
const hours = Math.floor(minutes / 60);
|
|
312
|
+
if (hours < 24) return `${hours}h ago`;
|
|
313
|
+
const days = Math.floor(hours / 24);
|
|
314
|
+
return `${days}d ago`;
|
|
315
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState, useEffect, useCallback, useRef, type ReactNode } from "react";
|
|
1
|
+
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo, useRef, type ReactNode } from "react";
|
|
2
2
|
|
|
3
3
|
interface User {
|
|
4
4
|
id: string;
|
|
@@ -216,7 +216,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|
|
216
216
|
return () => clearInterval(refreshInterval);
|
|
217
217
|
}, [accessToken, refreshTokenInternal]);
|
|
218
218
|
|
|
219
|
-
const value
|
|
219
|
+
const value = useMemo<AuthContextValue>(() => ({
|
|
220
220
|
user,
|
|
221
221
|
isAuthenticated: !!user,
|
|
222
222
|
isLoading,
|
|
@@ -230,7 +230,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|
|
230
230
|
refreshToken,
|
|
231
231
|
checkAuth,
|
|
232
232
|
authFetch,
|
|
233
|
-
};
|
|
233
|
+
}), [user, isLoading, hasUsers, isDev, accessToken, onboardingComplete, setOnboardingComplete, login, logout, refreshToken, checkAuth, authFetch]);
|
|
234
234
|
|
|
235
235
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
236
236
|
}
|