apteva 0.4.57 → 0.7.0
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/README.md +216 -54
- package/cli.js +35 -0
- package/install.js +92 -0
- package/package.json +12 -79
- package/LICENSE +0 -63
- package/bin/apteva.js +0 -196
- package/dist/ActivityPage.kxzzb4yc.js +0 -3
- package/dist/ApiDocsPage.zq998hbm.js +0 -4
- package/dist/App.55rea8mn.js +0 -61
- package/dist/App.5ywb23z4.js +0 -53
- package/dist/App.6thds120.js +0 -4
- package/dist/App.9tctxzqm.js +0 -8
- package/dist/App.a8r8ttaz.js +0 -4
- package/dist/App.agsv5bje.js +0 -4
- package/dist/App.cepapqmx.js +0 -4
- package/dist/App.dp041gb3.js +0 -221
- package/dist/App.fds72zb5.js +0 -4
- package/dist/App.fg9qj2dq.js +0 -4
- package/dist/App.ndfejbm9.js +0 -4
- package/dist/App.nxmfmq1h.js +0 -13
- package/dist/App.qdfyt8ba.js +0 -4
- package/dist/App.x2d0ygt6.js +0 -4
- package/dist/App.yt9p4nr3.js +0 -20
- package/dist/App.zn4mw16t.js +0 -1
- package/dist/ConnectionsPage.8r96ryw7.js +0 -3
- package/dist/McpPage.3cwh0gnd.js +0 -3
- package/dist/SettingsPage.ykgdh5ev.js +0 -3
- package/dist/SkillsPage.4np1s65b.js +0 -3
- package/dist/TasksPage.4g08t7p6.js +0 -3
- package/dist/TelemetryPage.72w9pwcp.js +0 -3
- package/dist/TestsPage.z4fk3r7r.js +0 -3
- package/dist/ThreadsPage.63tcajeh.js +0 -3
- package/dist/apteva-kit.css +0 -1
- package/dist/icon.png +0 -0
- package/dist/index.html +0 -16
- package/dist/styles.css +0 -1
- package/scripts/postinstall.mjs +0 -102
- package/src/auth/index.ts +0 -394
- package/src/auth/middleware.ts +0 -213
- package/src/binary.ts +0 -536
- package/src/channels/index.ts +0 -40
- package/src/channels/telegram.ts +0 -311
- package/src/crypto.ts +0 -301
- package/src/db-tests.ts +0 -174
- package/src/db.ts +0 -3133
- package/src/integrations/agentdojo.ts +0 -559
- package/src/integrations/composio.ts +0 -437
- package/src/integrations/index.ts +0 -87
- package/src/integrations/skillsmp.ts +0 -318
- package/src/mcp-client.ts +0 -605
- package/src/mcp-handler.ts +0 -394
- package/src/mcp-platform.ts +0 -2403
- package/src/openapi.ts +0 -2410
- package/src/providers.ts +0 -597
- package/src/routes/api/agent-utils.ts +0 -890
- package/src/routes/api/agents.ts +0 -916
- package/src/routes/api/api-keys.ts +0 -95
- package/src/routes/api/channels.ts +0 -182
- package/src/routes/api/helpers.ts +0 -12
- package/src/routes/api/integrations.ts +0 -639
- package/src/routes/api/mcp.ts +0 -574
- package/src/routes/api/meta-agent.ts +0 -195
- package/src/routes/api/projects.ts +0 -112
- package/src/routes/api/providers.ts +0 -424
- package/src/routes/api/skills.ts +0 -537
- package/src/routes/api/system.ts +0 -333
- package/src/routes/api/telemetry.ts +0 -203
- package/src/routes/api/tests.ts +0 -148
- package/src/routes/api/triggers.ts +0 -518
- package/src/routes/api/users.ts +0 -148
- package/src/routes/api/webhooks.ts +0 -171
- package/src/routes/api.ts +0 -53
- package/src/routes/auth.ts +0 -251
- package/src/routes/share.ts +0 -86
- package/src/routes/static.ts +0 -131
- package/src/server.ts +0 -642
- package/src/test-runner.ts +0 -598
- package/src/triggers/agentdojo.ts +0 -253
- package/src/triggers/composio.ts +0 -264
- package/src/triggers/index.ts +0 -71
- package/src/tui/AgentList.tsx +0 -145
- package/src/tui/App.tsx +0 -102
- package/src/tui/Login.tsx +0 -104
- package/src/tui/api.ts +0 -72
- package/src/tui/index.tsx +0 -7
- package/src/web/App.tsx +0 -455
- package/src/web/components/activity/ActivityPage.tsx +0 -314
- package/src/web/components/activity/index.ts +0 -1
- package/src/web/components/agents/AgentCard.tsx +0 -189
- package/src/web/components/agents/AgentPanel.tsx +0 -2244
- package/src/web/components/agents/AgentsView.tsx +0 -180
- package/src/web/components/agents/CreateAgentModal.tsx +0 -475
- package/src/web/components/agents/index.ts +0 -4
- package/src/web/components/api/ApiDocsPage.tsx +0 -842
- package/src/web/components/auth/CreateAccountStep.tsx +0 -176
- package/src/web/components/auth/LoginPage.tsx +0 -91
- package/src/web/components/auth/index.ts +0 -2
- package/src/web/components/common/Icons.tsx +0 -250
- package/src/web/components/common/LoadingSpinner.tsx +0 -44
- package/src/web/components/common/Modal.tsx +0 -199
- package/src/web/components/common/Select.tsx +0 -97
- package/src/web/components/common/index.ts +0 -20
- package/src/web/components/connections/ConnectionsPage.tsx +0 -54
- package/src/web/components/connections/IntegrationsTab.tsx +0 -170
- package/src/web/components/connections/OverviewTab.tsx +0 -137
- package/src/web/components/connections/TriggersTab.tsx +0 -1346
- package/src/web/components/dashboard/Dashboard.tsx +0 -572
- package/src/web/components/dashboard/index.ts +0 -1
- package/src/web/components/index.ts +0 -21
- package/src/web/components/layout/ErrorBanner.tsx +0 -18
- package/src/web/components/layout/Header.tsx +0 -332
- package/src/web/components/layout/Sidebar.tsx +0 -231
- package/src/web/components/layout/index.ts +0 -3
- package/src/web/components/mcp/IntegrationsPanel.tsx +0 -857
- package/src/web/components/mcp/McpPage.tsx +0 -2515
- package/src/web/components/mcp/index.ts +0 -1
- package/src/web/components/meta-agent/MetaAgent.tsx +0 -245
- package/src/web/components/onboarding/OnboardingWizard.tsx +0 -404
- package/src/web/components/onboarding/index.ts +0 -1
- package/src/web/components/settings/SettingsPage.tsx +0 -2776
- package/src/web/components/settings/index.ts +0 -1
- package/src/web/components/skills/SkillsPage.tsx +0 -1200
- package/src/web/components/tasks/TasksPage.tsx +0 -1116
- package/src/web/components/tasks/index.ts +0 -1
- package/src/web/components/telemetry/TelemetryPage.tsx +0 -1129
- package/src/web/components/tests/TestsPage.tsx +0 -594
- package/src/web/components/threads/ThreadsPage.tsx +0 -315
- package/src/web/context/AuthContext.tsx +0 -242
- package/src/web/context/ProjectContext.tsx +0 -214
- package/src/web/context/TelemetryContext.tsx +0 -299
- package/src/web/context/ThemeContext.tsx +0 -90
- package/src/web/context/UIModeContext.tsx +0 -49
- package/src/web/context/index.ts +0 -12
- package/src/web/hooks/index.ts +0 -3
- package/src/web/hooks/useAgents.ts +0 -115
- package/src/web/hooks/useOnboarding.ts +0 -20
- package/src/web/hooks/useProviders.ts +0 -75
- package/src/web/icon.png +0 -0
- package/src/web/index.html +0 -16
- package/src/web/styles.css +0 -118
- package/src/web/themes.ts +0 -162
- package/src/web/types.ts +0 -298
|
@@ -1,572 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
2
|
-
import { useAgentActivity, useAuth, useProjects, useTelemetryContext, useUIMode } from "../../context";
|
|
3
|
-
import { useTelemetry } from "../../context/TelemetryContext";
|
|
4
|
-
import type { TelemetryEvent } from "../../context";
|
|
5
|
-
import type { Agent, Provider, Route, DashboardStats, Task } from "../../types";
|
|
6
|
-
import { CloseIcon } from "../common/Icons";
|
|
7
|
-
|
|
8
|
-
interface DashboardProps {
|
|
9
|
-
agents: Agent[];
|
|
10
|
-
loading: boolean;
|
|
11
|
-
runningCount: number;
|
|
12
|
-
configuredProviders: Provider[];
|
|
13
|
-
onNavigate: (route: Route) => void;
|
|
14
|
-
onSelectAgent: (agent: Agent) => void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function Dashboard({
|
|
18
|
-
agents,
|
|
19
|
-
loading,
|
|
20
|
-
runningCount,
|
|
21
|
-
configuredProviders,
|
|
22
|
-
onNavigate,
|
|
23
|
-
onSelectAgent,
|
|
24
|
-
}: DashboardProps) {
|
|
25
|
-
const { authFetch } = useAuth();
|
|
26
|
-
const { currentProjectId } = useProjects();
|
|
27
|
-
const { isDev, t } = useUIMode();
|
|
28
|
-
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
29
|
-
const { events: taskTelemetryEvents } = useTelemetry({ category: "TASK" });
|
|
30
|
-
const lastProcessedTaskEventRef = useRef<string | null>(null);
|
|
31
|
-
const [stats, setStats] = useState<DashboardStats | null>(null);
|
|
32
|
-
const [recentTasks, setRecentTasks] = useState<Task[]>([]);
|
|
33
|
-
const [historicalActivities, setHistoricalActivities] = useState<TelemetryEvent[]>([]);
|
|
34
|
-
const [quickMessageAgent, setQuickMessageAgent] = useState<Agent | null>(null);
|
|
35
|
-
|
|
36
|
-
// Filter agents by current project
|
|
37
|
-
const filteredAgents = useMemo(() => {
|
|
38
|
-
if (!currentProjectId) return agents; // "All Projects"
|
|
39
|
-
if (currentProjectId === "unassigned") return agents.filter(a => !a.projectId);
|
|
40
|
-
return agents.filter(a => a.projectId === currentProjectId);
|
|
41
|
-
}, [agents, currentProjectId]);
|
|
42
|
-
|
|
43
|
-
const filteredRunningCount = useMemo(() => {
|
|
44
|
-
return filteredAgents.filter(a => a.status === "running").length;
|
|
45
|
-
}, [filteredAgents]);
|
|
46
|
-
|
|
47
|
-
// Get agent IDs for filtering tasks
|
|
48
|
-
const projectAgentIds = useMemo(() => {
|
|
49
|
-
return new Set(filteredAgents.map(a => a.id));
|
|
50
|
-
}, [filteredAgents]);
|
|
51
|
-
|
|
52
|
-
const fetchDashboardData = useCallback(async () => {
|
|
53
|
-
try {
|
|
54
|
-
const projectParam = currentProjectId ? `project_id=${encodeURIComponent(currentProjectId)}` : "";
|
|
55
|
-
const [dashRes, tasksRes, activityRes] = await Promise.all([
|
|
56
|
-
authFetch(`/api/dashboard${projectParam ? `?${projectParam}` : ""}`),
|
|
57
|
-
authFetch(`/api/tasks?status=all${projectParam ? `&${projectParam}` : ""}`),
|
|
58
|
-
authFetch(`/api/telemetry/events?type=thread_activity&limit=20${projectParam ? `&${projectParam}` : ""}`),
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
if (dashRes.ok) {
|
|
62
|
-
const data = await dashRes.json();
|
|
63
|
-
setStats(data);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (tasksRes.ok) {
|
|
67
|
-
const data = await tasksRes.json();
|
|
68
|
-
setRecentTasks(data.tasks || []);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (activityRes.ok) {
|
|
72
|
-
const data = await activityRes.json();
|
|
73
|
-
setHistoricalActivities(data.events || []);
|
|
74
|
-
}
|
|
75
|
-
} catch (e) {
|
|
76
|
-
console.error("Failed to fetch dashboard data:", e);
|
|
77
|
-
}
|
|
78
|
-
}, [authFetch, currentProjectId]);
|
|
79
|
-
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
fetchDashboardData();
|
|
82
|
-
}, [fetchDashboardData, statusChangeCounter]);
|
|
83
|
-
|
|
84
|
-
// Real-time task updates from telemetry
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
if (!taskTelemetryEvents.length) return;
|
|
87
|
-
const latestEvent = taskTelemetryEvents[0];
|
|
88
|
-
if (!latestEvent || latestEvent.id === lastProcessedTaskEventRef.current) return;
|
|
89
|
-
if (latestEvent.type === "task_created" || latestEvent.type === "task_updated" || latestEvent.type === "task_deleted") {
|
|
90
|
-
lastProcessedTaskEventRef.current = latestEvent.id;
|
|
91
|
-
fetchDashboardData();
|
|
92
|
-
}
|
|
93
|
-
}, [taskTelemetryEvents, fetchDashboardData]);
|
|
94
|
-
|
|
95
|
-
// Filter tasks by project agents and sort by next execution (soonest first)
|
|
96
|
-
const filteredTasks = useMemo(() => {
|
|
97
|
-
let list = currentProjectId
|
|
98
|
-
? recentTasks.filter(t => projectAgentIds.has(t.agentId))
|
|
99
|
-
: recentTasks;
|
|
100
|
-
return sortTasksByNextExecution(list);
|
|
101
|
-
}, [recentTasks, currentProjectId, projectAgentIds]);
|
|
102
|
-
|
|
103
|
-
// Calculate task stats from filtered tasks
|
|
104
|
-
const taskStats = useMemo(() => {
|
|
105
|
-
if (!currentProjectId) {
|
|
106
|
-
return stats?.tasks || { total: 0, pending: 0, running: 0, completed: 0 };
|
|
107
|
-
}
|
|
108
|
-
// When filtering by project, calculate from filtered tasks
|
|
109
|
-
const total = filteredTasks.length;
|
|
110
|
-
const pending = filteredTasks.filter(t => t.status === "pending").length;
|
|
111
|
-
const running = filteredTasks.filter(t => t.status === "running").length;
|
|
112
|
-
const completed = filteredTasks.filter(t => t.status === "completed").length;
|
|
113
|
-
return { total, pending, running, completed };
|
|
114
|
-
}, [stats, currentProjectId, filteredTasks]);
|
|
115
|
-
|
|
116
|
-
// Merge real-time + historical thread_activity events, deduplicate
|
|
117
|
-
const activities = useMemo(() => {
|
|
118
|
-
const realtimeActivities = realtimeEvents.filter(e => e.type === "thread_activity" && !e.data?.parent_id);
|
|
119
|
-
const seen = new Set(realtimeActivities.map(e => e.id));
|
|
120
|
-
const merged = [...realtimeActivities];
|
|
121
|
-
for (const evt of historicalActivities) {
|
|
122
|
-
if (!seen.has(evt.id) && !evt.data?.parent_id) {
|
|
123
|
-
merged.push(evt);
|
|
124
|
-
seen.add(evt.id);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Filter by project
|
|
128
|
-
let filtered = merged;
|
|
129
|
-
if (currentProjectId) {
|
|
130
|
-
filtered = merged.filter(e => projectAgentIds.has(e.agent_id));
|
|
131
|
-
}
|
|
132
|
-
// Sort newest first
|
|
133
|
-
filtered.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
134
|
-
return filtered.slice(0, 12);
|
|
135
|
-
}, [realtimeEvents, historicalActivities, currentProjectId, projectAgentIds]);
|
|
136
|
-
|
|
137
|
-
// Build agent name lookup
|
|
138
|
-
const agentNameMap = useMemo(() => {
|
|
139
|
-
const map = new Map<string, string>();
|
|
140
|
-
for (const a of agents) {
|
|
141
|
-
map.set(a.id, a.name);
|
|
142
|
-
}
|
|
143
|
-
return map;
|
|
144
|
-
}, [agents]);
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<div className="flex-1 overflow-auto p-6">
|
|
148
|
-
{/* Stats Cards */}
|
|
149
|
-
<div className={`grid grid-cols-2 ${isDev ? 'sm:grid-cols-4' : 'sm:grid-cols-3'} gap-4 mb-6`}>
|
|
150
|
-
<StatCard label={t("Agents", "Employees")} value={filteredAgents.length} subValue={`${filteredRunningCount} running`} />
|
|
151
|
-
<StatCard label="Tasks" value={taskStats.total} subValue={`${taskStats.pending} pending`} />
|
|
152
|
-
<StatCard label="Completed" value={taskStats.completed} color="text-green-400" />
|
|
153
|
-
{isDev && <StatCard label="Providers" value={configuredProviders.length} color="text-[var(--color-accent)]" />}
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
157
|
-
{/* Agents List */}
|
|
158
|
-
<DashboardCard
|
|
159
|
-
title={t("Agents", "Employees")}
|
|
160
|
-
actionLabel="View All"
|
|
161
|
-
onAction={() => onNavigate("agents")}
|
|
162
|
-
>
|
|
163
|
-
{loading ? (
|
|
164
|
-
<div className="p-4 text-center text-[var(--color-text-muted)]">Loading...</div>
|
|
165
|
-
) : filteredAgents.length === 0 ? (
|
|
166
|
-
<div className="p-4 text-center text-[var(--color-text-muted)]">{t("No agents yet", "No employees yet")}</div>
|
|
167
|
-
) : (
|
|
168
|
-
<div className="divide-y divide-[var(--color-border)]">
|
|
169
|
-
{filteredAgents.slice(0, 5).map((agent) => (
|
|
170
|
-
<AgentListItem
|
|
171
|
-
key={agent.id}
|
|
172
|
-
agent={agent}
|
|
173
|
-
onSelect={() => onSelectAgent(agent)}
|
|
174
|
-
onMessage={agent.status === "running" ? () => setQuickMessageAgent(agent) : undefined}
|
|
175
|
-
showProject={!currentProjectId}
|
|
176
|
-
/>
|
|
177
|
-
))}
|
|
178
|
-
</div>
|
|
179
|
-
)}
|
|
180
|
-
</DashboardCard>
|
|
181
|
-
|
|
182
|
-
{/* Activity Feed */}
|
|
183
|
-
<DashboardCard
|
|
184
|
-
title="Activity"
|
|
185
|
-
actionLabel="Analytics"
|
|
186
|
-
onAction={() => onNavigate("analytics")}
|
|
187
|
-
>
|
|
188
|
-
{activities.length === 0 ? (
|
|
189
|
-
<div className="p-4 text-center text-[var(--color-text-muted)]">
|
|
190
|
-
<p>No activity yet</p>
|
|
191
|
-
<p className="text-sm text-[var(--color-text-faint)] mt-1">Agent activity will appear here in real-time</p>
|
|
192
|
-
</div>
|
|
193
|
-
) : (
|
|
194
|
-
<div className="divide-y divide-[var(--color-border)]">
|
|
195
|
-
{activities.map((evt) => (
|
|
196
|
-
<ActivityItem
|
|
197
|
-
key={evt.id}
|
|
198
|
-
activity={(evt.data?.activity as string) || "Working..."}
|
|
199
|
-
agentName={agentNameMap.get(evt.agent_id) || evt.agent_id}
|
|
200
|
-
timestamp={evt.timestamp}
|
|
201
|
-
/>
|
|
202
|
-
))}
|
|
203
|
-
</div>
|
|
204
|
-
)}
|
|
205
|
-
</DashboardCard>
|
|
206
|
-
|
|
207
|
-
{/* Tasks */}
|
|
208
|
-
<DashboardCard
|
|
209
|
-
title="Tasks"
|
|
210
|
-
actionLabel="View All"
|
|
211
|
-
onAction={() => onNavigate("tasks")}
|
|
212
|
-
>
|
|
213
|
-
{filteredTasks.length === 0 ? (
|
|
214
|
-
<div className="p-4 text-center text-[var(--color-text-muted)]">
|
|
215
|
-
<p>No tasks yet</p>
|
|
216
|
-
<p className="text-sm text-[var(--color-text-faint)] mt-1">Tasks will appear when agents create them</p>
|
|
217
|
-
</div>
|
|
218
|
-
) : (
|
|
219
|
-
<div className="divide-y divide-[var(--color-border)]">
|
|
220
|
-
{filteredTasks.slice(0, 5).map((task) => (
|
|
221
|
-
<div
|
|
222
|
-
key={`${task.agentId}-${task.id}`}
|
|
223
|
-
className="px-4 py-3 flex items-center justify-between"
|
|
224
|
-
>
|
|
225
|
-
<div className="flex-1 min-w-0">
|
|
226
|
-
<p className="font-medium truncate">{task.title}</p>
|
|
227
|
-
<p className="text-sm text-[var(--color-text-muted)]">
|
|
228
|
-
{task.agentName}
|
|
229
|
-
{task.recurrence && (
|
|
230
|
-
<span className="ml-1 text-[var(--color-text-faint)]">· {formatCronShort(task.recurrence)}</span>
|
|
231
|
-
)}
|
|
232
|
-
{task.next_run && (
|
|
233
|
-
<span className="ml-1 text-[var(--color-accent)]">· {formatRelativeShort(task.next_run)}</span>
|
|
234
|
-
)}
|
|
235
|
-
{!task.next_run && task.execute_at && (
|
|
236
|
-
<span className="ml-1 text-[var(--color-accent)]">· {formatRelativeShort(task.execute_at)}</span>
|
|
237
|
-
)}
|
|
238
|
-
</p>
|
|
239
|
-
</div>
|
|
240
|
-
<TaskStatusBadge status={task.status} />
|
|
241
|
-
</div>
|
|
242
|
-
))}
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
</DashboardCard>
|
|
246
|
-
</div>
|
|
247
|
-
|
|
248
|
-
{/* Quick Message Modal */}
|
|
249
|
-
{quickMessageAgent && (
|
|
250
|
-
<QuickMessageModal
|
|
251
|
-
agent={quickMessageAgent}
|
|
252
|
-
onClose={() => setQuickMessageAgent(null)}
|
|
253
|
-
/>
|
|
254
|
-
)}
|
|
255
|
-
</div>
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
interface StatCardProps {
|
|
260
|
-
label: string;
|
|
261
|
-
value: number;
|
|
262
|
-
subValue?: string;
|
|
263
|
-
color?: string;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function StatCard({ label, value, subValue, color }: StatCardProps) {
|
|
267
|
-
return (
|
|
268
|
-
<div className="bg-[var(--color-surface)] rounded p-4 border border-[var(--color-border)]">
|
|
269
|
-
<p className="text-sm text-[var(--color-text-muted)] mb-1">{label}</p>
|
|
270
|
-
<p className={`text-2xl font-semibold ${color || ''}`}>{value}</p>
|
|
271
|
-
{subValue && <p className="text-xs text-[var(--color-text-faint)] mt-1">{subValue}</p>}
|
|
272
|
-
</div>
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
interface DashboardCardProps {
|
|
277
|
-
title: string;
|
|
278
|
-
actionLabel: string;
|
|
279
|
-
onAction: () => void;
|
|
280
|
-
children: React.ReactNode;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function DashboardCard({ title, actionLabel, onAction, children }: DashboardCardProps) {
|
|
284
|
-
return (
|
|
285
|
-
<div className="bg-[var(--color-surface)] rounded border border-[var(--color-border)] overflow-hidden">
|
|
286
|
-
<div className="px-4 py-3 border-b border-[var(--color-border)] flex items-center justify-between">
|
|
287
|
-
<h3 className="font-semibold">{title}</h3>
|
|
288
|
-
<button
|
|
289
|
-
onClick={onAction}
|
|
290
|
-
className="text-sm text-[#3b82f6] hover:text-[#60a5fa]"
|
|
291
|
-
>
|
|
292
|
-
{actionLabel}
|
|
293
|
-
</button>
|
|
294
|
-
</div>
|
|
295
|
-
{children}
|
|
296
|
-
</div>
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function AgentListItem({ agent, onSelect, onMessage, showProject }: { agent: Agent; onSelect: () => void; onMessage?: () => void; showProject?: boolean }) {
|
|
301
|
-
const { isActive, label } = useAgentActivity(agent.id);
|
|
302
|
-
const { projects } = useProjects();
|
|
303
|
-
const { isDev } = useUIMode();
|
|
304
|
-
const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
|
|
305
|
-
|
|
306
|
-
return (
|
|
307
|
-
<div
|
|
308
|
-
onClick={onSelect}
|
|
309
|
-
className="px-4 py-3 hover:bg-[var(--color-surface-raised)] cursor-pointer flex items-center justify-between group"
|
|
310
|
-
>
|
|
311
|
-
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
312
|
-
<span
|
|
313
|
-
className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
|
314
|
-
agent.status === "running"
|
|
315
|
-
? isActive
|
|
316
|
-
? "bg-green-400 animate-pulse"
|
|
317
|
-
: "bg-[#3b82f6]"
|
|
318
|
-
: "bg-[var(--color-scrollbar)]"
|
|
319
|
-
}`}
|
|
320
|
-
/>
|
|
321
|
-
<div className="flex-1 min-w-0">
|
|
322
|
-
<p className="font-medium truncate">{agent.name}</p>
|
|
323
|
-
<div className="flex items-center gap-2 text-sm text-[var(--color-text-muted)]">
|
|
324
|
-
{isActive && label ? (
|
|
325
|
-
<span className="text-green-400 truncate">{label}</span>
|
|
326
|
-
) : (
|
|
327
|
-
<span>{isDev ? `${agent.provider} · ` : ""}{agent.status === "running" ? "idle" : "stopped"}</span>
|
|
328
|
-
)}
|
|
329
|
-
{showProject && project && (
|
|
330
|
-
<>
|
|
331
|
-
<span className="text-[var(--color-text-faint)]">·</span>
|
|
332
|
-
<span className="flex items-center gap-1">
|
|
333
|
-
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: project.color }} />
|
|
334
|
-
{project.name}
|
|
335
|
-
</span>
|
|
336
|
-
</>
|
|
337
|
-
)}
|
|
338
|
-
</div>
|
|
339
|
-
</div>
|
|
340
|
-
</div>
|
|
341
|
-
{onMessage && (
|
|
342
|
-
<button
|
|
343
|
-
onClick={(e) => { e.stopPropagation(); onMessage(); }}
|
|
344
|
-
className="opacity-0 group-hover:opacity-100 transition px-2 py-1 text-xs text-[var(--color-accent)] hover:bg-[var(--color-accent-10)] rounded"
|
|
345
|
-
title="Send message"
|
|
346
|
-
>
|
|
347
|
-
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
348
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} 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" />
|
|
349
|
-
</svg>
|
|
350
|
-
</button>
|
|
351
|
-
)}
|
|
352
|
-
</div>
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function timeAgo(timestamp: string): string {
|
|
357
|
-
const seconds = Math.floor((Date.now() - new Date(timestamp).getTime()) / 1000);
|
|
358
|
-
if (seconds < 5) return "just now";
|
|
359
|
-
if (seconds < 60) return `${seconds}s ago`;
|
|
360
|
-
const minutes = Math.floor(seconds / 60);
|
|
361
|
-
if (minutes < 60) return `${minutes}m ago`;
|
|
362
|
-
const hours = Math.floor(minutes / 60);
|
|
363
|
-
if (hours < 24) return `${hours}h ago`;
|
|
364
|
-
const days = Math.floor(hours / 24);
|
|
365
|
-
return `${days}d ago`;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function ActivityItem({ activity, agentName, timestamp }: { activity: string; agentName: string; timestamp: string }) {
|
|
369
|
-
return (
|
|
370
|
-
<div className="px-4 py-3">
|
|
371
|
-
<p className="text-sm truncate">{activity}</p>
|
|
372
|
-
<div className="flex items-center gap-2 text-xs text-[var(--color-text-faint)] mt-1">
|
|
373
|
-
<span className="text-[var(--color-text-muted)]">{agentName}</span>
|
|
374
|
-
<span className="text-[var(--color-text-faint)]">·</span>
|
|
375
|
-
<span>{timeAgo(timestamp)}</span>
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function TaskStatusBadge({ status }: { status: Task["status"] }) {
|
|
382
|
-
const colors: Record<string, string> = {
|
|
383
|
-
pending: "bg-yellow-500/20 text-yellow-400",
|
|
384
|
-
running: "bg-blue-500/20 text-blue-400",
|
|
385
|
-
completed: "bg-green-500/20 text-green-400",
|
|
386
|
-
failed: "bg-red-500/20 text-red-400",
|
|
387
|
-
cancelled: "bg-gray-500/20 text-gray-400",
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
return (
|
|
391
|
-
<span className={`px-2 py-0.5 rounded text-xs font-medium ${colors[status] || colors.pending}`}>
|
|
392
|
-
{status}
|
|
393
|
-
</span>
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// --- Quick Message Modal ---
|
|
398
|
-
|
|
399
|
-
function QuickMessageModal({ agent, onClose }: { agent: Agent; onClose: () => void }) {
|
|
400
|
-
const { authFetch } = useAuth();
|
|
401
|
-
const [message, setMessage] = useState("");
|
|
402
|
-
const [sending, setSending] = useState(false);
|
|
403
|
-
const [sent, setSent] = useState(false);
|
|
404
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
405
|
-
|
|
406
|
-
useEffect(() => {
|
|
407
|
-
inputRef.current?.focus();
|
|
408
|
-
}, []);
|
|
409
|
-
|
|
410
|
-
const handleSend = async () => {
|
|
411
|
-
if (!message.trim() || sending) return;
|
|
412
|
-
setSending(true);
|
|
413
|
-
try {
|
|
414
|
-
const res = await authFetch(`/api/agents/${agent.id}/chat`, {
|
|
415
|
-
method: "POST",
|
|
416
|
-
headers: { "Content-Type": "application/json" },
|
|
417
|
-
body: JSON.stringify({ message: message.trim(), agent_id: agent.id }),
|
|
418
|
-
});
|
|
419
|
-
if (res.ok) {
|
|
420
|
-
setSent(true);
|
|
421
|
-
setTimeout(onClose, 1200);
|
|
422
|
-
}
|
|
423
|
-
} catch {
|
|
424
|
-
// ignore
|
|
425
|
-
} finally {
|
|
426
|
-
setSending(false);
|
|
427
|
-
}
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
return (
|
|
431
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
432
|
-
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
|
|
433
|
-
<div className="relative bg-[var(--color-surface)] border border-[var(--color-border-light)] rounded-xl shadow-2xl w-full max-w-md mx-4 p-5">
|
|
434
|
-
<div className="flex items-center justify-between mb-4">
|
|
435
|
-
<div className="flex items-center gap-3">
|
|
436
|
-
<span className="w-2.5 h-2.5 rounded-full bg-green-400 animate-pulse" />
|
|
437
|
-
<h3 className="font-medium">{agent.name}</h3>
|
|
438
|
-
</div>
|
|
439
|
-
<button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition">
|
|
440
|
-
<CloseIcon />
|
|
441
|
-
</button>
|
|
442
|
-
</div>
|
|
443
|
-
|
|
444
|
-
{sent ? (
|
|
445
|
-
<div className="py-6 text-center">
|
|
446
|
-
<p className="text-green-400 font-medium">Message sent</p>
|
|
447
|
-
<p className="text-sm text-[var(--color-text-faint)] mt-1">The agent will process your message</p>
|
|
448
|
-
</div>
|
|
449
|
-
) : (
|
|
450
|
-
<div className="flex gap-2">
|
|
451
|
-
<input
|
|
452
|
-
ref={inputRef}
|
|
453
|
-
type="text"
|
|
454
|
-
value={message}
|
|
455
|
-
onChange={e => setMessage(e.target.value)}
|
|
456
|
-
onKeyDown={e => e.key === "Enter" && handleSend()}
|
|
457
|
-
placeholder={`Message ${agent.name}...`}
|
|
458
|
-
disabled={sending}
|
|
459
|
-
className="flex-1 bg-[var(--color-bg)] border border-[var(--color-border-light)] btn px-3 py-2.5 text-sm focus:outline-none focus:border-[var(--color-accent)] placeholder-[#444] disabled:opacity-50"
|
|
460
|
-
/>
|
|
461
|
-
<button
|
|
462
|
-
onClick={handleSend}
|
|
463
|
-
disabled={sending || !message.trim()}
|
|
464
|
-
className="px-4 py-2.5 bg-[var(--color-accent)] text-black btn text-sm font-medium hover:bg-[var(--color-accent-hover)] transition disabled:opacity-30"
|
|
465
|
-
>
|
|
466
|
-
{sending ? "..." : "Send"}
|
|
467
|
-
</button>
|
|
468
|
-
</div>
|
|
469
|
-
)}
|
|
470
|
-
</div>
|
|
471
|
-
</div>
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// --- Task sorting helper ---
|
|
476
|
-
|
|
477
|
-
function statusPriority(task: Task): number {
|
|
478
|
-
if (task.status === "running") return 0;
|
|
479
|
-
if (task.status === "pending") return 1;
|
|
480
|
-
if (task.status === "completed") return 2;
|
|
481
|
-
if (task.status === "failed") return 3;
|
|
482
|
-
return 4; // cancelled etc
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
function sortTasksByNextExecution(tasks: Task[]): Task[] {
|
|
486
|
-
return [...tasks].sort((a, b) => {
|
|
487
|
-
const aPri = statusPriority(a);
|
|
488
|
-
const bPri = statusPriority(b);
|
|
489
|
-
if (aPri !== bPri) return aPri - bPri;
|
|
490
|
-
// Within running/pending: soonest next execution first
|
|
491
|
-
if (aPri <= 1) {
|
|
492
|
-
const aTime = a.next_run || a.execute_at || null;
|
|
493
|
-
const bTime = b.next_run || b.execute_at || null;
|
|
494
|
-
const aTs = aTime ? new Date(aTime).getTime() : Infinity;
|
|
495
|
-
const bTs = bTime ? new Date(bTime).getTime() : Infinity;
|
|
496
|
-
return aTs - bTs;
|
|
497
|
-
}
|
|
498
|
-
// Within completed/failed: most recent first
|
|
499
|
-
const aDate = a.completed_at || a.executed_at || a.created_at;
|
|
500
|
-
const bDate = b.completed_at || b.executed_at || b.created_at;
|
|
501
|
-
return new Date(bDate).getTime() - new Date(aDate).getTime();
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// --- Schedule formatting helpers (compact versions for dashboard) ---
|
|
506
|
-
|
|
507
|
-
const DASH_DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
508
|
-
|
|
509
|
-
function formatCronShort(cron: string): string {
|
|
510
|
-
try {
|
|
511
|
-
const parts = cron.trim().split(/\s+/);
|
|
512
|
-
if (parts.length !== 5) return cron;
|
|
513
|
-
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
|
514
|
-
|
|
515
|
-
if (minute.startsWith("*/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
516
|
-
const n = parseInt(minute.slice(2));
|
|
517
|
-
return n === 1 ? "Every min" : `Every ${n}min`;
|
|
518
|
-
}
|
|
519
|
-
if (minute !== "*" && !minute.includes("/") && hour === "*" && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
520
|
-
return "Hourly";
|
|
521
|
-
}
|
|
522
|
-
if (hour.startsWith("*/") && dayOfMonth === "*" && month === "*" && dayOfWeek === "*") {
|
|
523
|
-
const n = parseInt(hour.slice(2));
|
|
524
|
-
return n === 1 ? "Hourly" : `Every ${n}h`;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const formatTime = (h: string, m: string): string => {
|
|
528
|
-
const hr = parseInt(h);
|
|
529
|
-
const mn = parseInt(m);
|
|
530
|
-
if (isNaN(hr)) return "";
|
|
531
|
-
const ampm = hr >= 12 ? "PM" : "AM";
|
|
532
|
-
const h12 = hr === 0 ? 12 : hr > 12 ? hr - 12 : hr;
|
|
533
|
-
return `${h12}:${mn.toString().padStart(2, "0")} ${ampm}`;
|
|
534
|
-
};
|
|
535
|
-
|
|
536
|
-
if (hour !== "*" && !hour.includes("/") && dayOfMonth === "*" && month === "*") {
|
|
537
|
-
const timeStr = formatTime(hour, minute);
|
|
538
|
-
if (dayOfWeek === "*") return `Daily ${timeStr}`;
|
|
539
|
-
const days = dayOfWeek.split(",").map(d => DASH_DAY_NAMES[parseInt(d.trim())] || d);
|
|
540
|
-
if (days.length === 1) return `${days[0]} ${timeStr}`;
|
|
541
|
-
return `${days.join(" & ")} ${timeStr}`;
|
|
542
|
-
}
|
|
543
|
-
return cron;
|
|
544
|
-
} catch {
|
|
545
|
-
return cron;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function formatRelativeShort(dateStr: string): string {
|
|
550
|
-
const date = new Date(dateStr);
|
|
551
|
-
const now = new Date();
|
|
552
|
-
const diffMs = date.getTime() - now.getTime();
|
|
553
|
-
const isFuture = diffMs > 0;
|
|
554
|
-
const absDiffMs = Math.abs(diffMs);
|
|
555
|
-
const minutes = Math.floor(absDiffMs / 60000);
|
|
556
|
-
const hours = Math.floor(absDiffMs / 3600000);
|
|
557
|
-
|
|
558
|
-
const timeStr = date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
559
|
-
|
|
560
|
-
const isToday = date.toDateString() === now.toDateString();
|
|
561
|
-
const tomorrow = new Date(now);
|
|
562
|
-
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
563
|
-
const isTomorrow = date.toDateString() === tomorrow.toDateString();
|
|
564
|
-
|
|
565
|
-
if (isToday) {
|
|
566
|
-
if (minutes < 1) return "now";
|
|
567
|
-
if (minutes < 60) return isFuture ? `in ${minutes}m` : `${minutes}m ago`;
|
|
568
|
-
return isFuture ? `in ${hours}h` : `${hours}h ago`;
|
|
569
|
-
}
|
|
570
|
-
if (isTomorrow) return `Tomorrow ${timeStr}`;
|
|
571
|
-
return `${DASH_DAY_NAMES[date.getDay()]} ${timeStr}`;
|
|
572
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Dashboard } from "./Dashboard";
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Common components
|
|
2
|
-
export { LoadingSpinner, Modal, Select, CheckIcon, CloseIcon, DashboardIcon, AgentsIcon, SettingsIcon, TasksIcon } from "./common";
|
|
3
|
-
|
|
4
|
-
// Layout components
|
|
5
|
-
export { Header, Sidebar, ErrorBanner } from "./layout";
|
|
6
|
-
|
|
7
|
-
// Auth components
|
|
8
|
-
export { LoginPage, CreateAccountStep } from "./auth";
|
|
9
|
-
|
|
10
|
-
// Feature components
|
|
11
|
-
export { OnboardingWizard } from "./onboarding";
|
|
12
|
-
export { SettingsPage } from "./settings";
|
|
13
|
-
export { AgentCard, CreateAgentModal, AgentPanel, AgentsView } from "./agents";
|
|
14
|
-
export { Dashboard } from "./dashboard";
|
|
15
|
-
export { ThreadsPage } from "./threads/ThreadsPage";
|
|
16
|
-
export { TasksPage } from "./tasks";
|
|
17
|
-
export { McpPage } from "./mcp";
|
|
18
|
-
export { SkillsPage } from "./skills/SkillsPage";
|
|
19
|
-
export { TestsPage } from "./tests/TestsPage";
|
|
20
|
-
export { TelemetryPage } from "./telemetry/TelemetryPage";
|
|
21
|
-
export { ConnectionsPage } from "./connections/ConnectionsPage";
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { CloseIcon } from "../common/Icons";
|
|
3
|
-
|
|
4
|
-
interface ErrorBannerProps {
|
|
5
|
-
message: string;
|
|
6
|
-
onDismiss: () => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function ErrorBanner({ message, onDismiss }: ErrorBannerProps) {
|
|
10
|
-
return (
|
|
11
|
-
<div className="bg-red-500/10 border-b border-red-500/30 px-6 py-3 text-red-400 text-sm flex items-center justify-between">
|
|
12
|
-
<span>{message}</span>
|
|
13
|
-
<button onClick={onDismiss} className="hover:text-red-300">
|
|
14
|
-
<CloseIcon className="w-4 h-4" />
|
|
15
|
-
</button>
|
|
16
|
-
</div>
|
|
17
|
-
);
|
|
18
|
-
}
|