apteva 0.4.44 → 0.4.48
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 → ActivityPage.sw9p594m.js} +1 -1
- package/dist/{ApiDocsPage.yzcxx5ax.js → ApiDocsPage.90e03bz7.js} +1 -1
- package/dist/App.0ws87fpx.js +53 -0
- package/dist/App.3vnrera5.js +4 -0
- package/dist/App.94x6mh7f.js +20 -0
- package/dist/{App.qzbx5wtj.js → App.9sryp183.js} +1 -1
- package/dist/App.d9tny4t0.js +221 -0
- package/dist/{App.r5serxkt.js → App.jhb45d7r.js} +1 -1
- package/dist/App.p7jjw1zf.js +4 -0
- package/dist/App.pfbdzrhh.js +4 -0
- package/dist/App.stgng5bx.js +13 -0
- package/dist/{App.152mbs1r.js → App.tm3k7h4b.js} +1 -1
- package/dist/App.vkg121c6.js +4 -0
- package/dist/App.wghtdzsk.js +1 -0
- package/dist/App.xf7wsckg.js +4 -0
- package/dist/App.xva0tfzh.js +4 -0
- package/dist/App.ysxy7akk.js +61 -0
- package/dist/App.yzkh4gq2.js +4 -0
- package/dist/ConnectionsPage.q5f9fd37.js +3 -0
- package/dist/McpPage.f3ccrezb.js +3 -0
- package/dist/SettingsPage.3sqx6wm4.js +3 -0
- package/dist/SkillsPage.whxnez67.js +3 -0
- package/dist/TasksPage.zp4jfevw.js +3 -0
- package/dist/TelemetryPage.a9fmxq87.js +3 -0
- package/dist/TestsPage.18krj0d1.js +3 -0
- package/dist/ThreadsPage.nnphgy98.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 +5 -4
- package/src/db.ts +42 -4
- package/src/providers.ts +14 -9
- package/src/routes/api/agent-utils.ts +25 -3
- package/src/routes/api/telemetry.ts +20 -2
- package/src/server.ts +52 -1
- package/src/web/App.tsx +1 -1
- package/src/web/components/agents/AgentCard.tsx +9 -7
- package/src/web/components/agents/AgentPanel.tsx +205 -44
- package/src/web/components/agents/CreateAgentModal.tsx +5 -5
- package/src/web/components/auth/LoginPage.tsx +2 -2
- package/src/web/components/common/LoadingSpinner.tsx +1 -1
- package/src/web/components/common/Modal.tsx +6 -6
- package/src/web/components/common/Select.tsx +2 -2
- package/src/web/components/connections/ConnectionsPage.tsx +1 -1
- package/src/web/components/connections/IntegrationsTab.tsx +3 -3
- package/src/web/components/connections/OverviewTab.tsx +3 -3
- package/src/web/components/connections/TriggersTab.tsx +8 -8
- package/src/web/components/dashboard/Dashboard.tsx +2 -2
- package/src/web/components/layout/Header.tsx +3 -3
- package/src/web/components/layout/Sidebar.tsx +3 -2
- package/src/web/components/mcp/McpPage.tsx +13 -13
- package/src/web/components/onboarding/OnboardingWizard.tsx +2 -2
- package/src/web/components/settings/SettingsPage.tsx +55 -22
- package/src/web/components/skills/SkillsPage.tsx +7 -7
- package/src/web/components/tasks/TasksPage.tsx +212 -36
- package/src/web/components/telemetry/TelemetryPage.tsx +278 -9
- package/src/web/components/tests/TestsPage.tsx +2 -2
- package/src/web/components/threads/ThreadsPage.tsx +2 -2
- package/src/web/context/ThemeContext.tsx +31 -10
- package/src/web/index.html +1 -6
- package/src/web/styles.css +47 -0
- package/src/web/themes.ts +68 -5
- package/dist/App.09yb8t0b.js +0 -1
- package/dist/App.3a67nx9w.js +0 -4
- package/dist/App.9epx6785.js +0 -4
- package/dist/App.d8955awp.js +0 -4
- package/dist/App.drwb57jq.js +0 -4
- package/dist/App.gssbmajb.js +0 -4
- package/dist/App.qw70pc29.js +0 -53
- package/dist/App.tpmp9020.js +0 -20
- package/dist/App.v2wb4d7d.js +0 -61
- package/dist/App.vxmaaj0m.js +0 -13
- package/dist/App.w4p2tda9.js +0 -4
- package/dist/App.wv2ng55q.js +0 -221
- package/dist/App.yncnrn0f.js +0 -4
- package/dist/ConnectionsPage.k6cspyqq.js +0 -3
- package/dist/McpPage.cdxm48xj.js +0 -3
- package/dist/SettingsPage.evpv7c2y.js +0 -3
- package/dist/SkillsPage.pvzp6c1a.js +0 -3
- package/dist/TasksPage.6jnvbpsy.js +0 -3
- package/dist/TelemetryPage.t7vk24zc.js +0 -3
- package/dist/TestsPage.5x6658aa.js +0 -3
- package/dist/ThreadsPage.3fvhtevh.js +0 -3
package/src/server.ts
CHANGED
|
@@ -357,7 +357,7 @@ const server = Bun.serve({
|
|
|
357
357
|
development: false, // Suppress "Started server" message
|
|
358
358
|
idleTimeout: 255, // Max value - prevents SSE connections from timing out
|
|
359
359
|
|
|
360
|
-
async fetch(req: Request): Promise<Response> {
|
|
360
|
+
async fetch(req: Request, bunServer: Server): Promise<Response | undefined> {
|
|
361
361
|
const url = new URL(req.url);
|
|
362
362
|
const path = url.pathname;
|
|
363
363
|
|
|
@@ -367,6 +367,19 @@ const server = Bun.serve({
|
|
|
367
367
|
console.log(`[${req.method}] ${path}${params}`);
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
// WebSocket upgrade: /api/agents/:id/voice → proxy to agent binary
|
|
371
|
+
const voiceMatch = path.match(/^\/api\/agents\/([^/]+)\/voice$/);
|
|
372
|
+
if (voiceMatch && req.headers.get("upgrade")?.toLowerCase() === "websocket") {
|
|
373
|
+
const agentId = voiceMatch[1];
|
|
374
|
+
const agent = AgentDB.findById(agentId);
|
|
375
|
+
if (!agent || agent.status !== "running" || !agent.port) {
|
|
376
|
+
return new Response("Agent not available", { status: 400 });
|
|
377
|
+
}
|
|
378
|
+
const upgraded = bunServer.upgrade(req, { data: { agentId: agent.id, agentPort: agent.port } });
|
|
379
|
+
if (upgraded) return undefined;
|
|
380
|
+
return new Response("WebSocket upgrade failed", { status: 500 });
|
|
381
|
+
}
|
|
382
|
+
|
|
370
383
|
// CORS headers - configurable origins
|
|
371
384
|
const origin = req.headers.get("Origin") || "";
|
|
372
385
|
const allowedOrigins = process.env.CORS_ORIGINS?.split(",") || [];
|
|
@@ -436,6 +449,44 @@ const server = Bun.serve({
|
|
|
436
449
|
// Serve static files (React app)
|
|
437
450
|
return serveStatic(req, path);
|
|
438
451
|
},
|
|
452
|
+
|
|
453
|
+
// WebSocket proxy for agent voice/realtime
|
|
454
|
+
websocket: {
|
|
455
|
+
open(ws: any) {
|
|
456
|
+
const { agentId, agentPort } = ws.data;
|
|
457
|
+
const agentWs = new WebSocket(`ws://localhost:${agentPort}/voice`);
|
|
458
|
+
|
|
459
|
+
agentWs.onopen = () => {
|
|
460
|
+
console.log(`[WS] Voice proxy connected: agent=${agentId} port=${agentPort}`);
|
|
461
|
+
};
|
|
462
|
+
agentWs.onmessage = (event: MessageEvent) => {
|
|
463
|
+
try { ws.send(event.data); } catch {}
|
|
464
|
+
};
|
|
465
|
+
agentWs.onclose = (event: CloseEvent) => {
|
|
466
|
+
console.log(`[WS] Agent disconnected: agent=${agentId} code=${event.code}`);
|
|
467
|
+
ws.close(event.code, event.reason);
|
|
468
|
+
};
|
|
469
|
+
agentWs.onerror = () => {
|
|
470
|
+
ws.close(1011, "Agent WebSocket error");
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
// Store agent WS on the client WS for message/close handlers
|
|
474
|
+
ws.data.agentWs = agentWs;
|
|
475
|
+
},
|
|
476
|
+
message(ws: any, message: string | Buffer) {
|
|
477
|
+
const agentWs = ws.data.agentWs as WebSocket;
|
|
478
|
+
if (agentWs?.readyState === WebSocket.OPEN) {
|
|
479
|
+
agentWs.send(message);
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
close(ws: any, code: number, reason: string) {
|
|
483
|
+
const agentWs = ws.data.agentWs as WebSocket;
|
|
484
|
+
if (agentWs && agentWs.readyState !== WebSocket.CLOSED) {
|
|
485
|
+
agentWs.close(code, reason);
|
|
486
|
+
}
|
|
487
|
+
console.log(`[WS] Client disconnected: agent=${ws.data.agentId} code=${code}`);
|
|
488
|
+
},
|
|
489
|
+
},
|
|
439
490
|
});
|
|
440
491
|
|
|
441
492
|
const serverUrl = `http://localhost:${PORT}`;
|
package/src/web/App.tsx
CHANGED
|
@@ -241,7 +241,7 @@ function AppContent() {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
return (
|
|
244
|
-
<div className="h-screen
|
|
244
|
+
<div className="h-screen flex flex-col overflow-hidden" style={{ backgroundColor: "var(--color-bg)", color: "var(--color-text)" }}>
|
|
245
245
|
<Header onMenuClick={() => setMobileMenuOpen(true)} agents={agents} />
|
|
246
246
|
|
|
247
247
|
{startError && (
|
|
@@ -34,10 +34,10 @@ export const AgentCard = React.memo(function AgentCard({ agent, selected, onSele
|
|
|
34
34
|
return (
|
|
35
35
|
<div
|
|
36
36
|
onClick={onSelect}
|
|
37
|
-
className={`bg-[var(--color-surface)]
|
|
37
|
+
className={`bg-[var(--color-surface)] card p-5 transition cursor-pointer flex flex-col h-full ${
|
|
38
38
|
selected
|
|
39
|
-
? 'border-[var(--color-accent)]'
|
|
40
|
-
: '
|
|
39
|
+
? '!border-[var(--color-accent)]'
|
|
40
|
+
: 'hover:border-[var(--color-border-light)]'
|
|
41
41
|
}`}
|
|
42
42
|
>
|
|
43
43
|
<div className="flex items-start justify-between mb-3">
|
|
@@ -62,7 +62,8 @@ export const AgentCard = React.memo(function AgentCard({ agent, selected, onSele
|
|
|
62
62
|
{enabledFeatures.map(({ key, icon: Icon, label }) => (
|
|
63
63
|
<span
|
|
64
64
|
key={key}
|
|
65
|
-
className="inline-flex items-center gap-1 px-2 py-0.5
|
|
65
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 bg-[var(--color-accent-10)] text-[var(--color-accent-70)] text-xs"
|
|
66
|
+
style={{ borderRadius: "var(--radius-badge)" }}
|
|
66
67
|
title={label}
|
|
67
68
|
>
|
|
68
69
|
<Icon className="w-3 h-3" />
|
|
@@ -143,7 +144,7 @@ export const AgentCard = React.memo(function AgentCard({ agent, selected, onSele
|
|
|
143
144
|
<button
|
|
144
145
|
onClick={onToggle}
|
|
145
146
|
disabled={agent.status === "starting" || agent.status === "stopping"}
|
|
146
|
-
className={`w-full px-3 py-1.5
|
|
147
|
+
className={`w-full px-3 py-1.5 btn text-sm font-medium transition mt-auto ${
|
|
147
148
|
agent.status === "starting" || agent.status === "stopping"
|
|
148
149
|
? "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)] cursor-wait"
|
|
149
150
|
: agent.status === "running"
|
|
@@ -160,7 +161,7 @@ export const AgentCard = React.memo(function AgentCard({ agent, selected, onSele
|
|
|
160
161
|
function StatusBadge({ status, isActive, activityLabel }: { status: Agent["status"]; isActive?: boolean; activityLabel?: string }) {
|
|
161
162
|
if (status === "running" && isActive && activityLabel) {
|
|
162
163
|
return (
|
|
163
|
-
<span className="px-2 py-1
|
|
164
|
+
<span className="px-2 py-1 text-xs font-medium bg-green-500/20 text-green-400 animate-pulse" style={{ borderRadius: "var(--radius-badge)" }}>
|
|
164
165
|
{activityLabel}
|
|
165
166
|
</span>
|
|
166
167
|
);
|
|
@@ -170,13 +171,14 @@ function StatusBadge({ status, isActive, activityLabel }: { status: Agent["statu
|
|
|
170
171
|
|
|
171
172
|
return (
|
|
172
173
|
<span
|
|
173
|
-
className={`px-2 py-1
|
|
174
|
+
className={`px-2 py-1 text-xs font-medium ${
|
|
174
175
|
isTransitioning
|
|
175
176
|
? "bg-yellow-500/20 text-yellow-400 animate-pulse"
|
|
176
177
|
: status === "running"
|
|
177
178
|
? "bg-[#3b82f6]/20 text-[#3b82f6]"
|
|
178
179
|
: "bg-[var(--color-surface-raised)] text-[var(--color-text-muted)]"
|
|
179
180
|
}`}
|
|
181
|
+
style={{ borderRadius: "var(--radius-badge)" }}
|
|
180
182
|
>
|
|
181
183
|
{status}
|
|
182
184
|
</span>
|
|
@@ -123,6 +123,7 @@ function ChatTab({ agent, onStartAgent }: { agent: Agent; onStartAgent: (e?: Rea
|
|
|
123
123
|
variant="terminal"
|
|
124
124
|
theme={theme.id as "light" | "dark"}
|
|
125
125
|
headerTitle={agent.name}
|
|
126
|
+
enableVoice={!!agent.features.realtime}
|
|
126
127
|
/>
|
|
127
128
|
);
|
|
128
129
|
}
|
|
@@ -328,6 +329,9 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
328
329
|
const [showCreateForm, setShowCreateForm] = useState(false);
|
|
329
330
|
const [executing, setExecuting] = useState(false);
|
|
330
331
|
const [deleting, setDeleting] = useState(false);
|
|
332
|
+
const [editing, setEditing] = useState(false);
|
|
333
|
+
const [saving, setSaving] = useState(false);
|
|
334
|
+
const [editForm, setEditForm] = useState({ title: "", description: "", type: "once" as "once" | "recurring", priority: 5, execute_at: "", recurrence: "" });
|
|
331
335
|
const { confirm, ConfirmDialog } = useConfirm();
|
|
332
336
|
const { events } = useTelemetry({ agent_id: agent.id, category: "task" });
|
|
333
337
|
|
|
@@ -428,6 +432,51 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
428
432
|
}
|
|
429
433
|
};
|
|
430
434
|
|
|
435
|
+
const startEditing = (task: Task) => {
|
|
436
|
+
setEditForm({
|
|
437
|
+
title: task.title,
|
|
438
|
+
description: task.description || "",
|
|
439
|
+
type: task.type as "once" | "recurring",
|
|
440
|
+
priority: task.priority,
|
|
441
|
+
execute_at: task.execute_at ? new Date(task.execute_at).toISOString().slice(0, 16) : "",
|
|
442
|
+
recurrence: task.recurrence || "",
|
|
443
|
+
});
|
|
444
|
+
setEditing(true);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const handleUpdateTask = async () => {
|
|
448
|
+
if (!selectedTask || saving) return;
|
|
449
|
+
setSaving(true);
|
|
450
|
+
try {
|
|
451
|
+
const body: Record<string, unknown> = {
|
|
452
|
+
title: editForm.title.trim(),
|
|
453
|
+
description: editForm.description.trim() || undefined,
|
|
454
|
+
type: editForm.type,
|
|
455
|
+
priority: editForm.priority,
|
|
456
|
+
};
|
|
457
|
+
if (editForm.type === "once" && editForm.execute_at) {
|
|
458
|
+
body.execute_at = new Date(editForm.execute_at).toISOString();
|
|
459
|
+
}
|
|
460
|
+
if (editForm.type === "recurring" && editForm.recurrence.trim()) {
|
|
461
|
+
body.recurrence = editForm.recurrence.trim();
|
|
462
|
+
}
|
|
463
|
+
const res = await authFetch(`/api/tasks/${agent.id}/${selectedTask.id}`, {
|
|
464
|
+
method: "PUT",
|
|
465
|
+
headers: { "Content-Type": "application/json" },
|
|
466
|
+
body: JSON.stringify(body),
|
|
467
|
+
});
|
|
468
|
+
if (res.ok) {
|
|
469
|
+
setEditing(false);
|
|
470
|
+
setSelectedTask(null);
|
|
471
|
+
fetchTasks();
|
|
472
|
+
}
|
|
473
|
+
} catch (e) {
|
|
474
|
+
console.error("Failed to update task:", e);
|
|
475
|
+
} finally {
|
|
476
|
+
setSaving(false);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
431
480
|
// Refetch when agent changes, filter changes, or task telemetry arrives
|
|
432
481
|
useEffect(() => {
|
|
433
482
|
setLoading(true);
|
|
@@ -493,73 +542,182 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
493
542
|
{/* Back button + actions */}
|
|
494
543
|
<div className="px-4 pt-3 pb-2 border-b border-[var(--color-border)] shrink-0 flex items-center justify-between">
|
|
495
544
|
<button
|
|
496
|
-
onClick={() => setSelectedTask(null)}
|
|
545
|
+
onClick={() => { setSelectedTask(null); setEditing(false); }}
|
|
497
546
|
className="text-sm text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition flex items-center gap-1"
|
|
498
547
|
>
|
|
499
|
-
<span>←</span> Back to tasks
|
|
548
|
+
<span>←</span> {editing ? "Cancel" : "Back to tasks"}
|
|
500
549
|
</button>
|
|
501
550
|
<div className="flex items-center gap-2">
|
|
502
|
-
{
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
551
|
+
{editing ? (
|
|
552
|
+
<>
|
|
553
|
+
<button
|
|
554
|
+
onClick={() => setEditing(false)}
|
|
555
|
+
className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] text-sm transition"
|
|
556
|
+
>
|
|
557
|
+
Cancel
|
|
558
|
+
</button>
|
|
559
|
+
<button
|
|
560
|
+
onClick={handleUpdateTask}
|
|
561
|
+
disabled={saving || !editForm.title.trim()}
|
|
562
|
+
className="px-3 py-1 rounded text-sm bg-[var(--color-accent)] text-black hover:opacity-90 transition disabled:opacity-50"
|
|
563
|
+
>
|
|
564
|
+
{saving ? "Saving..." : "Save"}
|
|
565
|
+
</button>
|
|
566
|
+
</>
|
|
567
|
+
) : (
|
|
568
|
+
<>
|
|
569
|
+
{(selectedTask.status === "pending" || selectedTask.status === "completed" || selectedTask.status === "failed") && (
|
|
570
|
+
<button
|
|
571
|
+
onClick={() => startEditing(selectedTask)}
|
|
572
|
+
title="Edit task"
|
|
573
|
+
className="text-[var(--color-text-muted)] hover:text-[var(--color-accent)] transition"
|
|
574
|
+
>
|
|
575
|
+
<svg className="w-4.5 h-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /></svg>
|
|
576
|
+
</button>
|
|
577
|
+
)}
|
|
578
|
+
{(selectedTask.status === "pending" || selectedTask.status === "completed") && (
|
|
579
|
+
<button
|
|
580
|
+
onClick={handleExecuteTask}
|
|
581
|
+
disabled={executing}
|
|
582
|
+
title="Execute now"
|
|
583
|
+
className="text-[var(--color-accent)] hover:opacity-80 transition disabled:opacity-50"
|
|
584
|
+
>
|
|
585
|
+
<svg className="w-4.5 h-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
586
|
+
</button>
|
|
587
|
+
)}
|
|
588
|
+
<button
|
|
589
|
+
onClick={handleDeleteTask}
|
|
590
|
+
disabled={deleting}
|
|
591
|
+
title="Delete task"
|
|
592
|
+
className="text-red-400 hover:text-red-300 transition disabled:opacity-50"
|
|
593
|
+
>
|
|
594
|
+
<svg className="w-4.5 h-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
|
595
|
+
</button>
|
|
596
|
+
</>
|
|
511
597
|
)}
|
|
512
|
-
<button
|
|
513
|
-
onClick={handleDeleteTask}
|
|
514
|
-
disabled={deleting}
|
|
515
|
-
title="Delete task"
|
|
516
|
-
className="text-red-400 hover:text-red-300 transition disabled:opacity-50"
|
|
517
|
-
>
|
|
518
|
-
<svg className="w-4.5 h-4.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
|
519
|
-
</button>
|
|
520
598
|
</div>
|
|
521
599
|
</div>
|
|
522
600
|
|
|
523
601
|
{/* Task detail content */}
|
|
524
602
|
<div className="flex-1 overflow-auto p-4 space-y-4">
|
|
603
|
+
{(() => {
|
|
604
|
+
const inputClass = "w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded px-2 py-1.5 text-sm focus:outline-none focus:border-[var(--color-accent)] text-[var(--color-text)]";
|
|
605
|
+
return <>
|
|
525
606
|
{/* Title & Status */}
|
|
526
607
|
<div>
|
|
527
608
|
<div className="flex items-start justify-between gap-2 mb-1">
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
609
|
+
{editing ? (
|
|
610
|
+
<input
|
|
611
|
+
type="text"
|
|
612
|
+
value={editForm.title}
|
|
613
|
+
onChange={e => setEditForm({ ...editForm, title: e.target.value })}
|
|
614
|
+
className={`${inputClass} text-lg font-medium`}
|
|
615
|
+
placeholder="Task title"
|
|
616
|
+
/>
|
|
617
|
+
) : (
|
|
618
|
+
<h3 className="text-lg font-medium">{selectedTask.title}</h3>
|
|
619
|
+
)}
|
|
620
|
+
{!editing && (
|
|
621
|
+
<span className={`px-2 py-1 rounded text-xs font-medium flex-shrink-0 ${statusColors[selectedTask.status]}`}>
|
|
622
|
+
{selectedTask.status}
|
|
623
|
+
</span>
|
|
624
|
+
)}
|
|
532
625
|
</div>
|
|
533
626
|
</div>
|
|
534
627
|
|
|
535
628
|
{/* Description */}
|
|
536
|
-
{
|
|
629
|
+
{editing ? (
|
|
630
|
+
<div>
|
|
631
|
+
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1">Description</h4>
|
|
632
|
+
<textarea
|
|
633
|
+
value={editForm.description}
|
|
634
|
+
onChange={e => setEditForm({ ...editForm, description: e.target.value })}
|
|
635
|
+
className={`${inputClass} resize-none`}
|
|
636
|
+
rows={3}
|
|
637
|
+
placeholder="Task description..."
|
|
638
|
+
/>
|
|
639
|
+
</div>
|
|
640
|
+
) : selectedTask.description ? (
|
|
537
641
|
<div>
|
|
538
642
|
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1">Description</h4>
|
|
539
643
|
<p className="text-sm text-[var(--color-text-secondary)] whitespace-pre-wrap">{selectedTask.description}</p>
|
|
540
644
|
</div>
|
|
541
|
-
)}
|
|
645
|
+
) : null}
|
|
542
646
|
|
|
543
647
|
{/* Metadata */}
|
|
544
|
-
|
|
648
|
+
{editing ? (
|
|
649
|
+
<div className="grid grid-cols-2 gap-3">
|
|
650
|
+
<div>
|
|
651
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Type</label>
|
|
652
|
+
<select
|
|
653
|
+
value={editForm.type}
|
|
654
|
+
onChange={e => setEditForm({ ...editForm, type: e.target.value as "once" | "recurring" })}
|
|
655
|
+
className={inputClass}
|
|
656
|
+
>
|
|
657
|
+
<option value="once">One-time</option>
|
|
658
|
+
<option value="recurring">Recurring</option>
|
|
659
|
+
</select>
|
|
660
|
+
</div>
|
|
661
|
+
<div>
|
|
662
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Priority</label>
|
|
663
|
+
<input
|
|
664
|
+
type="number"
|
|
665
|
+
min={1}
|
|
666
|
+
max={10}
|
|
667
|
+
value={editForm.priority}
|
|
668
|
+
onChange={e => setEditForm({ ...editForm, priority: Number(e.target.value) })}
|
|
669
|
+
className={inputClass}
|
|
670
|
+
/>
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
) : (
|
|
674
|
+
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
675
|
+
<div>
|
|
676
|
+
<span className="text-[var(--color-text-muted)]">Type</span>
|
|
677
|
+
<p className="capitalize">{selectedTask.type}</p>
|
|
678
|
+
</div>
|
|
679
|
+
<div>
|
|
680
|
+
<span className="text-[var(--color-text-muted)]">Priority</span>
|
|
681
|
+
<p>{selectedTask.priority}</p>
|
|
682
|
+
</div>
|
|
683
|
+
{selectedTask.recurrence && (
|
|
684
|
+
<div>
|
|
685
|
+
<span className="text-[var(--color-text-muted)]">Recurrence</span>
|
|
686
|
+
<p>{formatCron(selectedTask.recurrence)}</p>
|
|
687
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-0.5 font-mono">{selectedTask.recurrence}</p>
|
|
688
|
+
</div>
|
|
689
|
+
)}
|
|
690
|
+
</div>
|
|
691
|
+
)}
|
|
692
|
+
|
|
693
|
+
{/* Schedule (edit mode) */}
|
|
694
|
+
{editing && editForm.type === "once" && (
|
|
545
695
|
<div>
|
|
546
|
-
<
|
|
547
|
-
<
|
|
696
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Schedule</label>
|
|
697
|
+
<input
|
|
698
|
+
type="datetime-local"
|
|
699
|
+
value={editForm.execute_at}
|
|
700
|
+
onChange={e => setEditForm({ ...editForm, execute_at: e.target.value })}
|
|
701
|
+
className={inputClass}
|
|
702
|
+
/>
|
|
548
703
|
</div>
|
|
704
|
+
)}
|
|
705
|
+
{editing && editForm.type === "recurring" && (
|
|
549
706
|
<div>
|
|
550
|
-
<
|
|
551
|
-
<
|
|
707
|
+
<label className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-1 block">Cron Schedule</label>
|
|
708
|
+
<input
|
|
709
|
+
type="text"
|
|
710
|
+
value={editForm.recurrence}
|
|
711
|
+
onChange={e => setEditForm({ ...editForm, recurrence: e.target.value })}
|
|
712
|
+
className={`${inputClass} font-mono`}
|
|
713
|
+
placeholder="*/30 * * * *"
|
|
714
|
+
/>
|
|
715
|
+
<p className="text-xs text-[var(--color-text-faint)] mt-1">e.g. */30 * * * * = every 30 min</p>
|
|
552
716
|
</div>
|
|
553
|
-
|
|
554
|
-
<div>
|
|
555
|
-
<span className="text-[var(--color-text-muted)]">Recurrence</span>
|
|
556
|
-
<p>{formatCron(selectedTask.recurrence)}</p>
|
|
557
|
-
<p className="text-xs text-[var(--color-text-faint)] mt-0.5 font-mono">{selectedTask.recurrence}</p>
|
|
558
|
-
</div>
|
|
559
|
-
)}
|
|
560
|
-
</div>
|
|
717
|
+
)}
|
|
561
718
|
|
|
562
|
-
{/* Timestamps */}
|
|
719
|
+
{/* Timestamps (view mode only) */}
|
|
720
|
+
{!editing && (
|
|
563
721
|
<div className="space-y-2 text-sm">
|
|
564
722
|
<div className="flex justify-between">
|
|
565
723
|
<span className="text-[var(--color-text-muted)]">Created</span>
|
|
@@ -590,9 +748,10 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
590
748
|
</div>
|
|
591
749
|
)}
|
|
592
750
|
</div>
|
|
751
|
+
)}
|
|
593
752
|
|
|
594
753
|
{/* Error */}
|
|
595
|
-
{selectedTask.status === "failed" && selectedTask.error && (
|
|
754
|
+
{!editing && selectedTask.status === "failed" && selectedTask.error && (
|
|
596
755
|
<div className="min-w-0">
|
|
597
756
|
<h4 className="text-xs text-red-400 uppercase tracking-wider mb-1">Error</h4>
|
|
598
757
|
<div className="bg-red-500/10 border border-red-500/20 rounded p-3 overflow-x-auto">
|
|
@@ -602,7 +761,7 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
602
761
|
)}
|
|
603
762
|
|
|
604
763
|
{/* Result */}
|
|
605
|
-
{selectedTask.status === "completed" && selectedTask.result && (
|
|
764
|
+
{!editing && selectedTask.status === "completed" && selectedTask.result && (
|
|
606
765
|
<div className="min-w-0">
|
|
607
766
|
<h4 className="text-xs text-green-400 uppercase tracking-wider mb-1">Result</h4>
|
|
608
767
|
<div className="bg-green-500/10 border border-green-500/20 rounded p-3 overflow-x-auto">
|
|
@@ -614,13 +773,13 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
614
773
|
)}
|
|
615
774
|
|
|
616
775
|
{/* Trajectory */}
|
|
617
|
-
{loadingTask && !selectedTask.trajectory && (
|
|
776
|
+
{!editing && loadingTask && !selectedTask.trajectory && (
|
|
618
777
|
<div>
|
|
619
778
|
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-2">Trajectory</h4>
|
|
620
779
|
<div className="text-sm text-[var(--color-text-faint)]">Loading trajectory...</div>
|
|
621
780
|
</div>
|
|
622
781
|
)}
|
|
623
|
-
{selectedTask.trajectory && selectedTask.trajectory.length > 0 && (
|
|
782
|
+
{!editing && selectedTask.trajectory && selectedTask.trajectory.length > 0 && (
|
|
624
783
|
<div>
|
|
625
784
|
<h4 className="text-xs text-[var(--color-text-muted)] uppercase tracking-wider mb-2">
|
|
626
785
|
Trajectory ({selectedTask.trajectory.length} steps)
|
|
@@ -628,6 +787,8 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
628
787
|
<TrajectoryView trajectory={selectedTask.trajectory} />
|
|
629
788
|
</div>
|
|
630
789
|
)}
|
|
790
|
+
</>;
|
|
791
|
+
})()}
|
|
631
792
|
</div>
|
|
632
793
|
</div>
|
|
633
794
|
);
|
|
@@ -681,7 +842,7 @@ function TasksTab({ agent }: { agent: Agent }) {
|
|
|
681
842
|
<div
|
|
682
843
|
key={task.id}
|
|
683
844
|
onClick={() => selectTask(task)}
|
|
684
|
-
className="bg-[var(--color-surface)]
|
|
845
|
+
className="bg-[var(--color-surface)] card p-4 cursor-pointer hover:border-[var(--color-border-light)] transition"
|
|
685
846
|
>
|
|
686
847
|
<div className="flex items-start justify-between mb-2">
|
|
687
848
|
<div className="flex-1 min-w-0">
|
|
@@ -202,7 +202,7 @@ export function CreateAgentModal({
|
|
|
202
202
|
key={key}
|
|
203
203
|
type="button"
|
|
204
204
|
onClick={() => toggleFeature(key)}
|
|
205
|
-
className={`flex items-center gap-3 p-3
|
|
205
|
+
className={`flex items-center gap-3 p-3 btn border text-left transition ${
|
|
206
206
|
isEnabled
|
|
207
207
|
? "border-[var(--color-accent)] bg-[var(--color-accent-10)]"
|
|
208
208
|
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)]"
|
|
@@ -237,7 +237,7 @@ export function CreateAgentModal({
|
|
|
237
237
|
},
|
|
238
238
|
},
|
|
239
239
|
})}
|
|
240
|
-
className={`flex items-center gap-2 px-3 py-2
|
|
240
|
+
className={`flex items-center gap-2 px-3 py-2 btn border transition ${
|
|
241
241
|
form.features.builtinTools?.webSearch
|
|
242
242
|
? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
|
|
243
243
|
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
|
|
@@ -260,7 +260,7 @@ export function CreateAgentModal({
|
|
|
260
260
|
},
|
|
261
261
|
},
|
|
262
262
|
})}
|
|
263
|
-
className={`flex items-center gap-2 px-3 py-2
|
|
263
|
+
className={`flex items-center gap-2 px-3 py-2 btn border transition ${
|
|
264
264
|
form.features.builtinTools?.webFetch
|
|
265
265
|
? "border-[var(--color-accent)] bg-[var(--color-accent-10)] text-[var(--color-accent)]"
|
|
266
266
|
: "border-[var(--color-border-light)] hover:border-[var(--color-border-light)] text-[var(--color-text-secondary)]"
|
|
@@ -282,14 +282,14 @@ export function CreateAgentModal({
|
|
|
282
282
|
<div className="flex gap-3 mt-6">
|
|
283
283
|
<button
|
|
284
284
|
onClick={onClose}
|
|
285
|
-
className="flex-1 border border-[var(--color-border-light)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent)] px-4 py-2
|
|
285
|
+
className="flex-1 border border-[var(--color-border-light)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent)] px-4 py-2 btn font-medium transition"
|
|
286
286
|
>
|
|
287
287
|
Cancel
|
|
288
288
|
</button>
|
|
289
289
|
<button
|
|
290
290
|
onClick={onCreate}
|
|
291
291
|
disabled={!form.name}
|
|
292
|
-
className="flex-1 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-2
|
|
292
|
+
className="flex-1 bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)] disabled:opacity-50 text-black px-4 py-2 btn font-medium transition"
|
|
293
293
|
>
|
|
294
294
|
Create
|
|
295
295
|
</button>
|
|
@@ -23,7 +23,7 @@ export function LoginPage() {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
|
-
<div className="min-h-screen bg-[var(--color-bg)] text-[var(--color-text)]
|
|
26
|
+
<div className="min-h-screen bg-[var(--color-bg)] text-[var(--color-text)] flex items-center justify-center p-8">
|
|
27
27
|
<div className="w-full max-w-md">
|
|
28
28
|
{/* Logo */}
|
|
29
29
|
<div className="text-center mb-8">
|
|
@@ -34,7 +34,7 @@ export function LoginPage() {
|
|
|
34
34
|
<p className="text-[var(--color-text-muted)]">Run AI agents locally</p>
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
|
-
<div className="bg-[var(--color-surface)]
|
|
37
|
+
<div className="bg-[var(--color-surface)] card p-8">
|
|
38
38
|
<h2 className="text-2xl font-semibold mb-2">Welcome back</h2>
|
|
39
39
|
<p className="text-[var(--color-text-muted)] mb-6">Sign in to continue to apteva</p>
|
|
40
40
|
|
|
@@ -30,7 +30,7 @@ export function LoadingSpinner({ message = "Loading...", fullScreen = false }: L
|
|
|
30
30
|
|
|
31
31
|
if (fullScreen) {
|
|
32
32
|
return (
|
|
33
|
-
<div className="min-h-screen bg-[var(--color-bg)] text-[var(--color-text)]
|
|
33
|
+
<div className="min-h-screen bg-[var(--color-bg)] text-[var(--color-text)] flex items-center justify-center">
|
|
34
34
|
{content}
|
|
35
35
|
</div>
|
|
36
36
|
);
|
|
@@ -8,7 +8,7 @@ interface ModalProps {
|
|
|
8
8
|
export function Modal({ children, onClose }: ModalProps) {
|
|
9
9
|
return (
|
|
10
10
|
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
|
|
11
|
-
<div className="bg-[var(--color-surface)]
|
|
11
|
+
<div className="bg-[var(--color-surface)] card p-6 w-full max-w-xl lg:max-w-2xl max-h-[90vh] overflow-y-auto">
|
|
12
12
|
{children}
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
@@ -37,19 +37,19 @@ export function ConfirmModal({
|
|
|
37
37
|
}: ConfirmModalProps) {
|
|
38
38
|
return (
|
|
39
39
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
40
|
-
<div className="bg-[var(--color-surface)]
|
|
40
|
+
<div className="bg-[var(--color-surface)] card p-6 w-full max-w-sm">
|
|
41
41
|
{title && <h3 className="font-medium mb-2">{title}</h3>}
|
|
42
42
|
<p className="text-sm text-[var(--color-text)] mb-4">{message}</p>
|
|
43
43
|
<div className="flex gap-2">
|
|
44
44
|
<button
|
|
45
45
|
onClick={onCancel}
|
|
46
|
-
className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2
|
|
46
|
+
className="flex-1 text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] btn px-4 py-2 transition"
|
|
47
47
|
>
|
|
48
48
|
{cancelText}
|
|
49
49
|
</button>
|
|
50
50
|
<button
|
|
51
51
|
onClick={onConfirm}
|
|
52
|
-
className={`flex-1 text-sm text-white px-4 py-2
|
|
52
|
+
className={`flex-1 text-sm text-white px-4 py-2 btn transition ${
|
|
53
53
|
confirmVariant === "danger"
|
|
54
54
|
? "bg-red-500 hover:bg-red-600"
|
|
55
55
|
: "bg-[var(--color-accent)] hover:bg-[var(--color-accent-hover)]"
|
|
@@ -93,7 +93,7 @@ export function AlertModal({
|
|
|
93
93
|
|
|
94
94
|
return (
|
|
95
95
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
|
96
|
-
<div className="bg-[var(--color-surface)]
|
|
96
|
+
<div className="bg-[var(--color-surface)] card p-6 w-full max-w-sm text-center">
|
|
97
97
|
<div
|
|
98
98
|
className={`w-12 h-12 rounded-full flex items-center justify-center mx-auto mb-3 ${iconColors[variant]}`}
|
|
99
99
|
>
|
|
@@ -103,7 +103,7 @@ export function AlertModal({
|
|
|
103
103
|
<p className="text-sm text-[var(--color-text)] mb-4">{message}</p>
|
|
104
104
|
<button
|
|
105
105
|
onClick={onClose}
|
|
106
|
-
className="w-full text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2
|
|
106
|
+
className="w-full text-sm bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] px-4 py-2 btn transition"
|
|
107
107
|
>
|
|
108
108
|
{buttonText}
|
|
109
109
|
</button>
|
|
@@ -35,7 +35,7 @@ export function Select({ value, options, onChange, placeholder = "Select...", co
|
|
|
35
35
|
<button
|
|
36
36
|
type="button"
|
|
37
37
|
onClick={() => setIsOpen(!isOpen)}
|
|
38
|
-
className={`w-full bg-[var(--color-bg)] border border-[var(--color-border-light)]
|
|
38
|
+
className={`w-full bg-[var(--color-bg)] border border-[var(--color-border-light)] btn ${compact ? "px-2.5 py-1.5 text-sm" : "px-3 py-2"} text-left flex items-center justify-between focus:outline-none focus:border-[var(--color-accent)] text-[var(--color-text)] hover:border-[var(--color-border-light)] transition`}
|
|
39
39
|
>
|
|
40
40
|
<span className={selectedOption ? "text-[var(--color-text)]" : "text-[var(--color-text-muted)]"}>
|
|
41
41
|
{selectedOption ? (
|
|
@@ -51,7 +51,7 @@ export function Select({ value, options, onChange, placeholder = "Select...", co
|
|
|
51
51
|
</button>
|
|
52
52
|
|
|
53
53
|
{isOpen && (
|
|
54
|
-
<div className="absolute z-50 w-full min-w-max mt-1 bg-[var(--color-surface)] border border-[var(--color-border-light)]
|
|
54
|
+
<div className="absolute z-50 w-full min-w-max mt-1 bg-[var(--color-surface)] border border-[var(--color-border-light)] shadow-lg max-h-60 overflow-y-auto scrollbar-hide" style={{ borderRadius: "var(--radius-button)" }}>
|
|
55
55
|
{options.map((option) => (
|
|
56
56
|
<button
|
|
57
57
|
key={option.value}
|
|
@@ -28,7 +28,7 @@ export function ConnectionsPage() {
|
|
|
28
28
|
</div>
|
|
29
29
|
|
|
30
30
|
{/* Tabs */}
|
|
31
|
-
<div className="flex gap-1 mb-6 bg-[var(--color-surface)]
|
|
31
|
+
<div className="flex gap-1 mb-6 bg-[var(--color-surface)] card p-1 w-fit">
|
|
32
32
|
{tabs.map(tab => (
|
|
33
33
|
<button
|
|
34
34
|
key={tab.id}
|