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
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
|
1
|
+
import React, { useState, useEffect, useCallback, useMemo, useRef } from "react";
|
|
2
2
|
import { useAgentActivity, useAuth, useProjects, useTelemetryContext } from "../../context";
|
|
3
|
+
import { useTelemetry } from "../../context/TelemetryContext";
|
|
3
4
|
import type { TelemetryEvent } from "../../context";
|
|
4
5
|
import type { Agent, Provider, Route, DashboardStats, Task } from "../../types";
|
|
6
|
+
import { CloseIcon } from "../common/Icons";
|
|
5
7
|
|
|
6
8
|
interface DashboardProps {
|
|
7
9
|
agents: Agent[];
|
|
@@ -23,9 +25,12 @@ export function Dashboard({
|
|
|
23
25
|
const { authFetch } = useAuth();
|
|
24
26
|
const { currentProjectId } = useProjects();
|
|
25
27
|
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
28
|
+
const { events: taskTelemetryEvents } = useTelemetry({ category: "TASK" });
|
|
29
|
+
const lastProcessedTaskEventRef = useRef<string | null>(null);
|
|
26
30
|
const [stats, setStats] = useState<DashboardStats | null>(null);
|
|
27
31
|
const [recentTasks, setRecentTasks] = useState<Task[]>([]);
|
|
28
32
|
const [historicalActivities, setHistoricalActivities] = useState<TelemetryEvent[]>([]);
|
|
33
|
+
const [quickMessageAgent, setQuickMessageAgent] = useState<Agent | null>(null);
|
|
29
34
|
|
|
30
35
|
// Filter agents by current project
|
|
31
36
|
const filteredAgents = useMemo(() => {
|
|
@@ -75,6 +80,17 @@ export function Dashboard({
|
|
|
75
80
|
fetchDashboardData();
|
|
76
81
|
}, [fetchDashboardData, statusChangeCounter]);
|
|
77
82
|
|
|
83
|
+
// Real-time task updates from telemetry
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
if (!taskTelemetryEvents.length) return;
|
|
86
|
+
const latestEvent = taskTelemetryEvents[0];
|
|
87
|
+
if (!latestEvent || latestEvent.id === lastProcessedTaskEventRef.current) return;
|
|
88
|
+
if (latestEvent.type === "task_created" || latestEvent.type === "task_updated" || latestEvent.type === "task_deleted") {
|
|
89
|
+
lastProcessedTaskEventRef.current = latestEvent.id;
|
|
90
|
+
fetchDashboardData();
|
|
91
|
+
}
|
|
92
|
+
}, [taskTelemetryEvents, fetchDashboardData]);
|
|
93
|
+
|
|
78
94
|
// Filter tasks by project agents and sort by next execution (soonest first)
|
|
79
95
|
const filteredTasks = useMemo(() => {
|
|
80
96
|
let list = currentProjectId
|
|
@@ -98,11 +114,11 @@ export function Dashboard({
|
|
|
98
114
|
|
|
99
115
|
// Merge real-time + historical thread_activity events, deduplicate
|
|
100
116
|
const activities = useMemo(() => {
|
|
101
|
-
const realtimeActivities = realtimeEvents.filter(e => e.type === "thread_activity");
|
|
117
|
+
const realtimeActivities = realtimeEvents.filter(e => e.type === "thread_activity" && !e.data?.parent_id);
|
|
102
118
|
const seen = new Set(realtimeActivities.map(e => e.id));
|
|
103
119
|
const merged = [...realtimeActivities];
|
|
104
120
|
for (const evt of historicalActivities) {
|
|
105
|
-
if (!seen.has(evt.id)) {
|
|
121
|
+
if (!seen.has(evt.id) && !evt.data?.parent_id) {
|
|
106
122
|
merged.push(evt);
|
|
107
123
|
seen.add(evt.id);
|
|
108
124
|
}
|
|
@@ -114,7 +130,7 @@ export function Dashboard({
|
|
|
114
130
|
}
|
|
115
131
|
// Sort newest first
|
|
116
132
|
filtered.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
117
|
-
return filtered.slice(0,
|
|
133
|
+
return filtered.slice(0, 12);
|
|
118
134
|
}, [realtimeEvents, historicalActivities, currentProjectId, projectAgentIds]);
|
|
119
135
|
|
|
120
136
|
// Build agent name lookup
|
|
@@ -133,7 +149,7 @@ export function Dashboard({
|
|
|
133
149
|
<StatCard label="Agents" value={filteredAgents.length} subValue={`${filteredRunningCount} running`} />
|
|
134
150
|
<StatCard label="Tasks" value={taskStats.total} subValue={`${taskStats.pending} pending`} />
|
|
135
151
|
<StatCard label="Completed" value={taskStats.completed} color="text-green-400" />
|
|
136
|
-
<StatCard label="Providers" value={configuredProviders.length} color="text-[
|
|
152
|
+
<StatCard label="Providers" value={configuredProviders.length} color="text-[var(--color-accent)]" />
|
|
137
153
|
</div>
|
|
138
154
|
|
|
139
155
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
@@ -144,13 +160,19 @@ export function Dashboard({
|
|
|
144
160
|
onAction={() => onNavigate("agents")}
|
|
145
161
|
>
|
|
146
162
|
{loading ? (
|
|
147
|
-
<div className="p-4 text-center text-[
|
|
163
|
+
<div className="p-4 text-center text-[var(--color-text-muted)]">Loading...</div>
|
|
148
164
|
) : filteredAgents.length === 0 ? (
|
|
149
|
-
<div className="p-4 text-center text-[
|
|
165
|
+
<div className="p-4 text-center text-[var(--color-text-muted)]">No agents yet</div>
|
|
150
166
|
) : (
|
|
151
|
-
<div className="divide-y divide-[
|
|
167
|
+
<div className="divide-y divide-[var(--color-border)]">
|
|
152
168
|
{filteredAgents.slice(0, 5).map((agent) => (
|
|
153
|
-
<AgentListItem
|
|
169
|
+
<AgentListItem
|
|
170
|
+
key={agent.id}
|
|
171
|
+
agent={agent}
|
|
172
|
+
onSelect={() => onSelectAgent(agent)}
|
|
173
|
+
onMessage={agent.status === "running" ? () => setQuickMessageAgent(agent) : undefined}
|
|
174
|
+
showProject={!currentProjectId}
|
|
175
|
+
/>
|
|
154
176
|
))}
|
|
155
177
|
</div>
|
|
156
178
|
)}
|
|
@@ -163,12 +185,12 @@ export function Dashboard({
|
|
|
163
185
|
onAction={() => onNavigate("telemetry")}
|
|
164
186
|
>
|
|
165
187
|
{activities.length === 0 ? (
|
|
166
|
-
<div className="p-4 text-center text-[
|
|
188
|
+
<div className="p-4 text-center text-[var(--color-text-muted)]">
|
|
167
189
|
<p>No activity yet</p>
|
|
168
|
-
<p className="text-sm text-[
|
|
190
|
+
<p className="text-sm text-[var(--color-text-faint)] mt-1">Agent activity will appear here in real-time</p>
|
|
169
191
|
</div>
|
|
170
192
|
) : (
|
|
171
|
-
<div className="divide-y divide-[
|
|
193
|
+
<div className="divide-y divide-[var(--color-border)]">
|
|
172
194
|
{activities.map((evt) => (
|
|
173
195
|
<ActivityItem
|
|
174
196
|
key={evt.id}
|
|
@@ -188,12 +210,12 @@ export function Dashboard({
|
|
|
188
210
|
onAction={() => onNavigate("tasks")}
|
|
189
211
|
>
|
|
190
212
|
{filteredTasks.length === 0 ? (
|
|
191
|
-
<div className="p-4 text-center text-[
|
|
213
|
+
<div className="p-4 text-center text-[var(--color-text-muted)]">
|
|
192
214
|
<p>No tasks yet</p>
|
|
193
|
-
<p className="text-sm text-[
|
|
215
|
+
<p className="text-sm text-[var(--color-text-faint)] mt-1">Tasks will appear when agents create them</p>
|
|
194
216
|
</div>
|
|
195
217
|
) : (
|
|
196
|
-
<div className="divide-y divide-[
|
|
218
|
+
<div className="divide-y divide-[var(--color-border)]">
|
|
197
219
|
{filteredTasks.slice(0, 5).map((task) => (
|
|
198
220
|
<div
|
|
199
221
|
key={`${task.agentId}-${task.id}`}
|
|
@@ -201,16 +223,16 @@ export function Dashboard({
|
|
|
201
223
|
>
|
|
202
224
|
<div className="flex-1 min-w-0">
|
|
203
225
|
<p className="font-medium truncate">{task.title}</p>
|
|
204
|
-
<p className="text-sm text-[
|
|
226
|
+
<p className="text-sm text-[var(--color-text-muted)]">
|
|
205
227
|
{task.agentName}
|
|
206
228
|
{task.recurrence && (
|
|
207
|
-
<span className="ml-1 text-[
|
|
229
|
+
<span className="ml-1 text-[var(--color-text-faint)]">· {formatCronShort(task.recurrence)}</span>
|
|
208
230
|
)}
|
|
209
231
|
{task.next_run && (
|
|
210
|
-
<span className="ml-1 text-[
|
|
232
|
+
<span className="ml-1 text-[var(--color-accent)]">· {formatRelativeShort(task.next_run)}</span>
|
|
211
233
|
)}
|
|
212
234
|
{!task.next_run && task.execute_at && (
|
|
213
|
-
<span className="ml-1 text-[
|
|
235
|
+
<span className="ml-1 text-[var(--color-accent)]">· {formatRelativeShort(task.execute_at)}</span>
|
|
214
236
|
)}
|
|
215
237
|
</p>
|
|
216
238
|
</div>
|
|
@@ -221,6 +243,14 @@ export function Dashboard({
|
|
|
221
243
|
)}
|
|
222
244
|
</DashboardCard>
|
|
223
245
|
</div>
|
|
246
|
+
|
|
247
|
+
{/* Quick Message Modal */}
|
|
248
|
+
{quickMessageAgent && (
|
|
249
|
+
<QuickMessageModal
|
|
250
|
+
agent={quickMessageAgent}
|
|
251
|
+
onClose={() => setQuickMessageAgent(null)}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
224
254
|
</div>
|
|
225
255
|
);
|
|
226
256
|
}
|
|
@@ -234,10 +264,10 @@ interface StatCardProps {
|
|
|
234
264
|
|
|
235
265
|
function StatCard({ label, value, subValue, color }: StatCardProps) {
|
|
236
266
|
return (
|
|
237
|
-
<div className="bg-[
|
|
238
|
-
<p className="text-sm text-[
|
|
267
|
+
<div className="bg-[var(--color-surface)] rounded p-4 border border-[var(--color-border)]">
|
|
268
|
+
<p className="text-sm text-[var(--color-text-muted)] mb-1">{label}</p>
|
|
239
269
|
<p className={`text-2xl font-semibold ${color || ''}`}>{value}</p>
|
|
240
|
-
{subValue && <p className="text-xs text-[
|
|
270
|
+
{subValue && <p className="text-xs text-[var(--color-text-faint)] mt-1">{subValue}</p>}
|
|
241
271
|
</div>
|
|
242
272
|
);
|
|
243
273
|
}
|
|
@@ -251,8 +281,8 @@ interface DashboardCardProps {
|
|
|
251
281
|
|
|
252
282
|
function DashboardCard({ title, actionLabel, onAction, children }: DashboardCardProps) {
|
|
253
283
|
return (
|
|
254
|
-
<div className="bg-[
|
|
255
|
-
<div className="px-4 py-3 border-b border-[
|
|
284
|
+
<div className="bg-[var(--color-surface)] rounded border border-[var(--color-border)] overflow-hidden">
|
|
285
|
+
<div className="px-4 py-3 border-b border-[var(--color-border)] flex items-center justify-between">
|
|
256
286
|
<h3 className="font-semibold">{title}</h3>
|
|
257
287
|
<button
|
|
258
288
|
onClick={onAction}
|
|
@@ -266,40 +296,57 @@ function DashboardCard({ title, actionLabel, onAction, children }: DashboardCard
|
|
|
266
296
|
);
|
|
267
297
|
}
|
|
268
298
|
|
|
269
|
-
function AgentListItem({ agent, onSelect, showProject }: { agent: Agent; onSelect: () => void; showProject?: boolean }) {
|
|
270
|
-
const { isActive } = useAgentActivity(agent.id);
|
|
299
|
+
function AgentListItem({ agent, onSelect, onMessage, showProject }: { agent: Agent; onSelect: () => void; onMessage?: () => void; showProject?: boolean }) {
|
|
300
|
+
const { isActive, label } = useAgentActivity(agent.id);
|
|
271
301
|
const { projects } = useProjects();
|
|
272
302
|
const project = agent.projectId ? projects.find(p => p.id === agent.projectId) : null;
|
|
273
303
|
|
|
274
304
|
return (
|
|
275
305
|
<div
|
|
276
306
|
onClick={onSelect}
|
|
277
|
-
className="px-4 py-3 hover:bg-[
|
|
307
|
+
className="px-4 py-3 hover:bg-[var(--color-surface-raised)] cursor-pointer flex items-center justify-between group"
|
|
278
308
|
>
|
|
279
|
-
<div className="flex-1 min-w-0">
|
|
280
|
-
<
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
)
|
|
309
|
+
<div className="flex items-center gap-3 flex-1 min-w-0">
|
|
310
|
+
<span
|
|
311
|
+
className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
|
312
|
+
agent.status === "running"
|
|
313
|
+
? isActive
|
|
314
|
+
? "bg-green-400 animate-pulse"
|
|
315
|
+
: "bg-[#3b82f6]"
|
|
316
|
+
: "bg-[var(--color-scrollbar)]"
|
|
317
|
+
}`}
|
|
318
|
+
/>
|
|
319
|
+
<div className="flex-1 min-w-0">
|
|
320
|
+
<p className="font-medium truncate">{agent.name}</p>
|
|
321
|
+
<div className="flex items-center gap-2 text-sm text-[var(--color-text-muted)]">
|
|
322
|
+
{isActive && label ? (
|
|
323
|
+
<span className="text-green-400 truncate">{label}</span>
|
|
324
|
+
) : (
|
|
325
|
+
<span>{agent.provider} · {agent.status === "running" ? "idle" : "stopped"}</span>
|
|
326
|
+
)}
|
|
327
|
+
{showProject && project && (
|
|
328
|
+
<>
|
|
329
|
+
<span className="text-[var(--color-text-faint)]">·</span>
|
|
330
|
+
<span className="flex items-center gap-1">
|
|
331
|
+
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: project.color }} />
|
|
332
|
+
{project.name}
|
|
333
|
+
</span>
|
|
334
|
+
</>
|
|
335
|
+
)}
|
|
336
|
+
</div>
|
|
292
337
|
</div>
|
|
293
338
|
</div>
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
339
|
+
{onMessage && (
|
|
340
|
+
<button
|
|
341
|
+
onClick={(e) => { e.stopPropagation(); onMessage(); }}
|
|
342
|
+
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"
|
|
343
|
+
title="Send message"
|
|
344
|
+
>
|
|
345
|
+
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
346
|
+
<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" />
|
|
347
|
+
</svg>
|
|
348
|
+
</button>
|
|
349
|
+
)}
|
|
303
350
|
</div>
|
|
304
351
|
);
|
|
305
352
|
}
|
|
@@ -320,9 +367,9 @@ function ActivityItem({ activity, agentName, timestamp }: { activity: string; ag
|
|
|
320
367
|
return (
|
|
321
368
|
<div className="px-4 py-3">
|
|
322
369
|
<p className="text-sm truncate">{activity}</p>
|
|
323
|
-
<div className="flex items-center gap-2 text-xs text-[
|
|
324
|
-
<span className="text-[
|
|
325
|
-
<span className="text-[
|
|
370
|
+
<div className="flex items-center gap-2 text-xs text-[var(--color-text-faint)] mt-1">
|
|
371
|
+
<span className="text-[var(--color-text-muted)]">{agentName}</span>
|
|
372
|
+
<span className="text-[var(--color-text-faint)]">·</span>
|
|
326
373
|
<span>{timeAgo(timestamp)}</span>
|
|
327
374
|
</div>
|
|
328
375
|
</div>
|
|
@@ -345,6 +392,84 @@ function TaskStatusBadge({ status }: { status: Task["status"] }) {
|
|
|
345
392
|
);
|
|
346
393
|
}
|
|
347
394
|
|
|
395
|
+
// --- Quick Message Modal ---
|
|
396
|
+
|
|
397
|
+
function QuickMessageModal({ agent, onClose }: { agent: Agent; onClose: () => void }) {
|
|
398
|
+
const { authFetch } = useAuth();
|
|
399
|
+
const [message, setMessage] = useState("");
|
|
400
|
+
const [sending, setSending] = useState(false);
|
|
401
|
+
const [sent, setSent] = useState(false);
|
|
402
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
403
|
+
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
inputRef.current?.focus();
|
|
406
|
+
}, []);
|
|
407
|
+
|
|
408
|
+
const handleSend = async () => {
|
|
409
|
+
if (!message.trim() || sending) return;
|
|
410
|
+
setSending(true);
|
|
411
|
+
try {
|
|
412
|
+
const res = await authFetch(`/api/agents/${agent.id}/chat`, {
|
|
413
|
+
method: "POST",
|
|
414
|
+
headers: { "Content-Type": "application/json" },
|
|
415
|
+
body: JSON.stringify({ message: message.trim(), agent_id: agent.id }),
|
|
416
|
+
});
|
|
417
|
+
if (res.ok) {
|
|
418
|
+
setSent(true);
|
|
419
|
+
setTimeout(onClose, 1200);
|
|
420
|
+
}
|
|
421
|
+
} catch {
|
|
422
|
+
// ignore
|
|
423
|
+
} finally {
|
|
424
|
+
setSending(false);
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
return (
|
|
429
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
430
|
+
<div className="absolute inset-0 bg-black/60" onClick={onClose} />
|
|
431
|
+
<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">
|
|
432
|
+
<div className="flex items-center justify-between mb-4">
|
|
433
|
+
<div className="flex items-center gap-3">
|
|
434
|
+
<span className="w-2.5 h-2.5 rounded-full bg-green-400 animate-pulse" />
|
|
435
|
+
<h3 className="font-medium">{agent.name}</h3>
|
|
436
|
+
</div>
|
|
437
|
+
<button onClick={onClose} className="text-[var(--color-text-muted)] hover:text-[var(--color-text)] transition">
|
|
438
|
+
<CloseIcon />
|
|
439
|
+
</button>
|
|
440
|
+
</div>
|
|
441
|
+
|
|
442
|
+
{sent ? (
|
|
443
|
+
<div className="py-6 text-center">
|
|
444
|
+
<p className="text-green-400 font-medium">Message sent</p>
|
|
445
|
+
<p className="text-sm text-[var(--color-text-faint)] mt-1">The agent will process your message</p>
|
|
446
|
+
</div>
|
|
447
|
+
) : (
|
|
448
|
+
<div className="flex gap-2">
|
|
449
|
+
<input
|
|
450
|
+
ref={inputRef}
|
|
451
|
+
type="text"
|
|
452
|
+
value={message}
|
|
453
|
+
onChange={e => setMessage(e.target.value)}
|
|
454
|
+
onKeyDown={e => e.key === "Enter" && handleSend()}
|
|
455
|
+
placeholder={`Message ${agent.name}...`}
|
|
456
|
+
disabled={sending}
|
|
457
|
+
className="flex-1 bg-[var(--color-bg)] border border-[var(--color-border-light)] rounded-lg px-3 py-2.5 text-sm focus:outline-none focus:border-[var(--color-accent)] placeholder-[#444] disabled:opacity-50"
|
|
458
|
+
/>
|
|
459
|
+
<button
|
|
460
|
+
onClick={handleSend}
|
|
461
|
+
disabled={sending || !message.trim()}
|
|
462
|
+
className="px-4 py-2.5 bg-[var(--color-accent)] text-black rounded-lg text-sm font-medium hover:bg-[var(--color-accent-hover)] transition disabled:opacity-30"
|
|
463
|
+
>
|
|
464
|
+
{sending ? "..." : "Send"}
|
|
465
|
+
</button>
|
|
466
|
+
</div>
|
|
467
|
+
)}
|
|
468
|
+
</div>
|
|
469
|
+
</div>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
348
473
|
// --- Task sorting helper ---
|
|
349
474
|
|
|
350
475
|
function statusPriority(task: Task): number {
|
|
@@ -12,7 +12,7 @@ export { OnboardingWizard } from "./onboarding";
|
|
|
12
12
|
export { SettingsPage } from "./settings";
|
|
13
13
|
export { AgentCard, CreateAgentModal, AgentPanel, AgentsView } from "./agents";
|
|
14
14
|
export { Dashboard } from "./dashboard";
|
|
15
|
-
export {
|
|
15
|
+
export { ThreadsPage } from "./threads/ThreadsPage";
|
|
16
16
|
export { TasksPage } from "./tasks";
|
|
17
17
|
export { McpPage } from "./mcp";
|
|
18
18
|
export { SkillsPage } from "./skills/SkillsPage";
|
|
@@ -134,24 +134,25 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
const getProjectColor = () => {
|
|
137
|
-
if (currentProjectId === null) return "
|
|
138
|
-
if (currentProjectId === "unassigned") return "
|
|
137
|
+
if (currentProjectId === null) return "var(--color-text-muted)";
|
|
138
|
+
if (currentProjectId === "unassigned") return "var(--color-text-secondary)";
|
|
139
139
|
return currentProject?.color || "#6366f1";
|
|
140
140
|
};
|
|
141
141
|
|
|
142
142
|
return (
|
|
143
|
-
<header className="
|
|
143
|
+
<header className="px-4 md:px-6 py-4 flex-shrink-0" style={{ borderBottom: "1px solid var(--color-border)" }}>
|
|
144
144
|
<div className="flex items-center justify-between">
|
|
145
145
|
<div className="flex items-center gap-3">
|
|
146
146
|
{/* Hamburger menu button - mobile only */}
|
|
147
147
|
<button
|
|
148
148
|
onClick={onMenuClick}
|
|
149
|
-
className="p-2 -ml-2
|
|
149
|
+
className="p-2 -ml-2 transition md:hidden"
|
|
150
|
+
style={{ color: "var(--color-text-muted)" }}
|
|
150
151
|
>
|
|
151
152
|
<MenuIcon />
|
|
152
153
|
</button>
|
|
153
154
|
<div className="flex items-center gap-2">
|
|
154
|
-
<span
|
|
155
|
+
<span style={{ color: "var(--color-accent)" }}>>_</span>
|
|
155
156
|
<span className="text-xl tracking-wider">apteva</span>
|
|
156
157
|
</div>
|
|
157
158
|
|
|
@@ -160,7 +161,8 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
|
160
161
|
<div className="relative ml-2 md:ml-4">
|
|
161
162
|
<button
|
|
162
163
|
onClick={() => setShowProjectMenu(!showProjectMenu)}
|
|
163
|
-
className="flex items-center gap-2 px-3 py-1.5 rounded
|
|
164
|
+
className="flex items-center gap-2 px-3 py-1.5 rounded transition text-sm"
|
|
165
|
+
style={{ border: "1px solid var(--color-border-light)", backgroundColor: "var(--color-surface)" }}
|
|
164
166
|
>
|
|
165
167
|
<span
|
|
166
168
|
className="w-2.5 h-2.5 rounded-full"
|
|
@@ -172,43 +174,49 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
|
172
174
|
<ChevronDownIcon />
|
|
173
175
|
</button>
|
|
174
176
|
{showProjectMenu && (
|
|
175
|
-
<div className="absolute left-0 top-full mt-1 w-56
|
|
177
|
+
<div className="absolute left-0 top-full mt-1 w-56 rounded-lg shadow-xl z-50" style={{ backgroundColor: "var(--color-surface)", border: "1px solid var(--color-border-light)" }}>
|
|
176
178
|
<div className="py-1 max-h-64 overflow-y-auto">
|
|
177
179
|
<button
|
|
178
180
|
onClick={() => handleProjectSelect(null)}
|
|
179
|
-
className=
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
|
|
182
|
+
style={{
|
|
183
|
+
backgroundColor: currentProjectId === null ? "var(--color-surface-raised)" : "transparent",
|
|
184
|
+
color: currentProjectId === null ? "var(--color-accent)" : "var(--color-text)",
|
|
185
|
+
}}
|
|
182
186
|
>
|
|
183
|
-
<span className="w-2.5 h-2.5 rounded-full
|
|
187
|
+
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: "var(--color-text-muted)" }} />
|
|
184
188
|
All Projects
|
|
185
189
|
</button>
|
|
186
190
|
{projects.map(project => (
|
|
187
191
|
<button
|
|
188
192
|
key={project.id}
|
|
189
193
|
onClick={() => handleProjectSelect(project.id)}
|
|
190
|
-
className=
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
|
|
195
|
+
style={{
|
|
196
|
+
backgroundColor: currentProjectId === project.id ? "var(--color-surface-raised)" : "transparent",
|
|
197
|
+
color: currentProjectId === project.id ? "var(--color-accent)" : "var(--color-text)",
|
|
198
|
+
}}
|
|
193
199
|
>
|
|
194
200
|
<span
|
|
195
201
|
className="w-2.5 h-2.5 rounded-full flex-shrink-0"
|
|
196
202
|
style={{ backgroundColor: project.color }}
|
|
197
203
|
/>
|
|
198
204
|
<span className="truncate">{project.name}</span>
|
|
199
|
-
<span className="ml-auto text-xs text-
|
|
205
|
+
<span className="ml-auto text-xs" style={{ color: "var(--color-text-muted)" }}>{project.agentCount}</span>
|
|
200
206
|
</button>
|
|
201
207
|
))}
|
|
202
208
|
{unassignedCount > 0 && (
|
|
203
209
|
<button
|
|
204
210
|
onClick={() => handleProjectSelect("unassigned")}
|
|
205
|
-
className=
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
className="w-full px-4 py-2 text-left text-sm flex items-center gap-2 transition"
|
|
212
|
+
style={{
|
|
213
|
+
backgroundColor: currentProjectId === "unassigned" ? "var(--color-surface-raised)" : "transparent",
|
|
214
|
+
color: currentProjectId === "unassigned" ? "var(--color-accent)" : "var(--color-text)",
|
|
215
|
+
}}
|
|
208
216
|
>
|
|
209
|
-
<span className="w-2.5 h-2.5 rounded-full
|
|
217
|
+
<span className="w-2.5 h-2.5 rounded-full" style={{ backgroundColor: "var(--color-text-secondary)" }} />
|
|
210
218
|
<span className="truncate">Unassigned</span>
|
|
211
|
-
<span className="ml-auto text-xs text-
|
|
219
|
+
<span className="ml-auto text-xs" style={{ color: "var(--color-text-muted)" }}>{unassignedCount}</span>
|
|
212
220
|
</button>
|
|
213
221
|
)}
|
|
214
222
|
</div>
|
|
@@ -222,7 +230,7 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
|
222
230
|
<span
|
|
223
231
|
className={`w-2 h-2 rounded-full ${connected ? "bg-green-400" : "bg-red-400"}`}
|
|
224
232
|
/>
|
|
225
|
-
<span className="text-xs
|
|
233
|
+
<span className="text-xs hidden sm:inline" style={{ color: "var(--color-text-muted)" }}>
|
|
226
234
|
{connected ? "Live" : "Offline"}
|
|
227
235
|
</span>
|
|
228
236
|
</div>
|
|
@@ -230,7 +238,8 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
|
230
238
|
<div className="relative">
|
|
231
239
|
<button
|
|
232
240
|
onClick={openNotifications}
|
|
233
|
-
className="relative p-2
|
|
241
|
+
className="relative p-2 transition rounded"
|
|
242
|
+
style={{ color: "var(--color-text-muted)" }}
|
|
234
243
|
>
|
|
235
244
|
<BellIcon className="w-5 h-5" />
|
|
236
245
|
{unseenCount > 0 && (
|
|
@@ -253,40 +262,47 @@ export function Header({ onMenuClick, agents = [] }: HeaderProps) {
|
|
|
253
262
|
{showNotifications && (
|
|
254
263
|
<>
|
|
255
264
|
<div className="fixed inset-0 z-40" onClick={() => setShowNotifications(false)} />
|
|
256
|
-
<div className="absolute right-0 top-full mt-1 w-80
|
|
257
|
-
<div className="px-4 py-3
|
|
265
|
+
<div className="absolute right-0 top-full mt-1 w-80 rounded-lg shadow-xl z-50 max-h-96 overflow-y-auto" style={{ backgroundColor: "var(--color-surface)", border: "1px solid var(--color-border-light)" }}>
|
|
266
|
+
<div className="px-4 py-3 flex items-center justify-between" style={{ borderBottom: "1px solid var(--color-border-light)" }}>
|
|
258
267
|
<span className="text-sm font-medium">Notifications</span>
|
|
259
268
|
{notifications.length > 0 && (
|
|
260
|
-
<span className="text-xs text-
|
|
269
|
+
<span className="text-xs" style={{ color: "var(--color-text-muted)" }}>{notifications.length} recent</span>
|
|
261
270
|
)}
|
|
262
271
|
</div>
|
|
263
272
|
{notifications.length === 0 ? (
|
|
264
|
-
<div className="px-4 py-8 text-center text-sm text-
|
|
273
|
+
<div className="px-4 py-8 text-center text-sm" style={{ color: "var(--color-text-muted)" }}>
|
|
265
274
|
No notifications
|
|
266
275
|
</div>
|
|
267
276
|
) : (
|
|
268
277
|
<div className="py-1">
|
|
269
278
|
{notifications.map(n => (
|
|
270
|
-
<div key={n.id} className=
|
|
279
|
+
<div key={n.id} className="px-4 py-3 transition" style={{
|
|
280
|
+
borderBottom: "1px solid var(--color-border)",
|
|
281
|
+
backgroundColor: !n.seen ? "var(--color-bg-secondary)" : "transparent",
|
|
282
|
+
}}>
|
|
271
283
|
<div className="flex items-center gap-2 mb-1">
|
|
272
284
|
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${
|
|
273
285
|
!n.seen
|
|
274
|
-
? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "
|
|
275
|
-
: "
|
|
276
|
-
}`}
|
|
277
|
-
|
|
286
|
+
? (n.level === "error" || n.category === "ERROR" ? "bg-red-400" : "")
|
|
287
|
+
: ""
|
|
288
|
+
}`} style={{
|
|
289
|
+
backgroundColor: !n.seen
|
|
290
|
+
? (n.level === "error" || n.category === "ERROR" ? undefined : "var(--color-accent)")
|
|
291
|
+
: "var(--color-surface-raised)",
|
|
292
|
+
}} />
|
|
293
|
+
<span className="text-xs font-medium truncate" style={{ color: !n.seen ? "var(--color-text)" : "var(--color-text-muted)" }}>
|
|
278
294
|
{n.category === "system" && n.type === "agent_stopped" ? "Agent Stopped" :
|
|
279
295
|
n.category === "ERROR" ? "Error" :
|
|
280
296
|
`${n.category} / ${n.type}`}
|
|
281
297
|
</span>
|
|
282
|
-
<span className="text-[10px]
|
|
298
|
+
<span className="text-[10px] ml-auto flex-shrink-0" style={{ color: "var(--color-text-faint)" }}>
|
|
283
299
|
{formatNotifTime(n.timestamp)}
|
|
284
300
|
</span>
|
|
285
301
|
</div>
|
|
286
|
-
<div className=
|
|
302
|
+
<div className="text-xs truncate" style={{ color: !n.seen ? "var(--color-text-secondary)" : "var(--color-text-muted)" }}>
|
|
287
303
|
{n.error || (n.data as any)?.message || (n.data as any)?.error || `${n.type} event`}
|
|
288
304
|
</div>
|
|
289
|
-
<div className="text-[10px]
|
|
305
|
+
<div className="text-[10px] mt-1" style={{ color: "var(--color-text-faint)" }}>
|
|
290
306
|
{agentNames[n.agent_id] || n.agent_id.slice(0, 8)}
|
|
291
307
|
</div>
|
|
292
308
|
</div>
|