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,6 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useMemo, useRef, useCallback } from "react";
|
|
2
2
|
import { Select } from "../common/Select";
|
|
3
3
|
import { useTelemetryContext, useProjects, useAuth, type TelemetryEvent } from "../../context";
|
|
4
|
+
import {
|
|
5
|
+
AreaChart, Area, BarChart, Bar,
|
|
6
|
+
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
|
7
|
+
} from "recharts";
|
|
4
8
|
|
|
5
9
|
interface TelemetryStats {
|
|
6
10
|
total_events: number;
|
|
@@ -9,6 +13,7 @@ interface TelemetryStats {
|
|
|
9
13
|
total_errors: number;
|
|
10
14
|
total_input_tokens: number;
|
|
11
15
|
total_output_tokens: number;
|
|
16
|
+
total_cost: number;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
19
|
interface UsageByAgent {
|
|
@@ -18,6 +23,16 @@ interface UsageByAgent {
|
|
|
18
23
|
llm_calls: number;
|
|
19
24
|
tool_calls: number;
|
|
20
25
|
errors: number;
|
|
26
|
+
cost: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface DailyUsage {
|
|
30
|
+
date: string;
|
|
31
|
+
input_tokens: number;
|
|
32
|
+
output_tokens: number;
|
|
33
|
+
llm_calls: number;
|
|
34
|
+
tool_calls: number;
|
|
35
|
+
errors: number;
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
// Helper to extract stats from a single event
|
|
@@ -45,11 +60,12 @@ function extractEventStats(event: TelemetryEvent): {
|
|
|
45
60
|
|
|
46
61
|
export function TelemetryPage() {
|
|
47
62
|
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
48
|
-
const { currentProjectId, currentProject } = useProjects();
|
|
63
|
+
const { currentProjectId, currentProject, costTrackingEnabled } = useProjects();
|
|
49
64
|
const { authFetch } = useAuth();
|
|
50
65
|
const [fetchedStats, setFetchedStats] = useState<TelemetryStats | null>(null);
|
|
51
66
|
const [historicalEvents, setHistoricalEvents] = useState<TelemetryEvent[]>([]);
|
|
52
67
|
const [fetchedUsage, setFetchedUsage] = useState<UsageByAgent[]>([]);
|
|
68
|
+
const [dailyUsage, setDailyUsage] = useState<DailyUsage[]>([]);
|
|
53
69
|
const [loading, setLoading] = useState(true);
|
|
54
70
|
const [filter, setFilter] = useState({
|
|
55
71
|
level: "",
|
|
@@ -60,6 +76,20 @@ export function TelemetryPage() {
|
|
|
60
76
|
const [agents, setAgents] = useState<Array<{ id: string; name: string; projectId: string | null }>>([]);
|
|
61
77
|
const [expandedEvent, setExpandedEvent] = useState<string | null>(null);
|
|
62
78
|
|
|
79
|
+
// Sort state for usage table
|
|
80
|
+
type SortKey = "agent" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "errors" | "cost";
|
|
81
|
+
const [sortKey, setSortKey] = useState<SortKey>("cost");
|
|
82
|
+
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
|
83
|
+
|
|
84
|
+
const handleSort = (key: SortKey) => {
|
|
85
|
+
if (sortKey === key) {
|
|
86
|
+
setSortDir(d => d === "asc" ? "desc" : "asc");
|
|
87
|
+
} else {
|
|
88
|
+
setSortKey(key);
|
|
89
|
+
setSortDir("desc");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
63
93
|
// Track IDs that were in the fetched stats to avoid double-counting
|
|
64
94
|
const countedEventIdsRef = useRef<Set<string>>(new Set());
|
|
65
95
|
|
|
@@ -127,6 +157,18 @@ export function TelemetryPage() {
|
|
|
127
157
|
const usageRes = await authFetch(`/api/telemetry/usage?${usageParams}`);
|
|
128
158
|
const usageData = await usageRes.json();
|
|
129
159
|
setFetchedUsage(usageData.usage || []);
|
|
160
|
+
|
|
161
|
+
// Fetch daily usage for charts
|
|
162
|
+
const dailyParams = new URLSearchParams();
|
|
163
|
+
dailyParams.set("group_by", "day");
|
|
164
|
+
if (projectParam) dailyParams.set("project_id", projectParam);
|
|
165
|
+
const dailyRes = await authFetch(`/api/telemetry/usage?${dailyParams}`);
|
|
166
|
+
const dailyData = await dailyRes.json();
|
|
167
|
+
// Sort by date ascending for charts
|
|
168
|
+
const sorted = (dailyData.usage || []).sort((a: DailyUsage, b: DailyUsage) =>
|
|
169
|
+
a.date.localeCompare(b.date)
|
|
170
|
+
);
|
|
171
|
+
setDailyUsage(sorted);
|
|
130
172
|
} catch (e) {
|
|
131
173
|
console.error("Failed to fetch telemetry:", e);
|
|
132
174
|
}
|
|
@@ -168,6 +210,7 @@ export function TelemetryPage() {
|
|
|
168
210
|
total_errors: fetchedStats.total_errors + deltaErrors,
|
|
169
211
|
total_input_tokens: fetchedStats.total_input_tokens + deltaInputTokens,
|
|
170
212
|
total_output_tokens: fetchedStats.total_output_tokens + deltaOutputTokens,
|
|
213
|
+
total_cost: fetchedStats.total_cost || 0,
|
|
171
214
|
};
|
|
172
215
|
}, [fetchedStats, realtimeEvents]);
|
|
173
216
|
|
|
@@ -198,6 +241,7 @@ export function TelemetryPage() {
|
|
|
198
241
|
errors: eventStats.errors,
|
|
199
242
|
input_tokens: eventStats.input_tokens,
|
|
200
243
|
output_tokens: eventStats.output_tokens,
|
|
244
|
+
cost: 0,
|
|
201
245
|
});
|
|
202
246
|
}
|
|
203
247
|
}
|
|
@@ -206,6 +250,22 @@ export function TelemetryPage() {
|
|
|
206
250
|
return Array.from(usageMap.values());
|
|
207
251
|
}, [fetchedUsage, realtimeEvents]);
|
|
208
252
|
|
|
253
|
+
// Sorted usage for the table
|
|
254
|
+
const sortedUsage = useMemo(() => {
|
|
255
|
+
const sorted = [...usage];
|
|
256
|
+
sorted.sort((a, b) => {
|
|
257
|
+
if (sortKey === "agent") {
|
|
258
|
+
const aName = (agents.find(ag => ag.id === a.agent_id)?.name || a.agent_id).toLowerCase();
|
|
259
|
+
const bName = (agents.find(ag => ag.id === b.agent_id)?.name || b.agent_id).toLowerCase();
|
|
260
|
+
return sortDir === "asc" ? (aName < bName ? -1 : 1) : (aName > bName ? -1 : 1);
|
|
261
|
+
}
|
|
262
|
+
const aVal = a[sortKey] as number;
|
|
263
|
+
const bVal = b[sortKey] as number;
|
|
264
|
+
return sortDir === "asc" ? aVal - bVal : bVal - aVal;
|
|
265
|
+
});
|
|
266
|
+
return sorted;
|
|
267
|
+
}, [usage, sortKey, sortDir, agents]);
|
|
268
|
+
|
|
209
269
|
// Merge real-time events with historical, filtering and deduping
|
|
210
270
|
const allEvents = React.useMemo(() => {
|
|
211
271
|
// Apply filters to real-time events
|
|
@@ -289,7 +349,7 @@ export function TelemetryPage() {
|
|
|
289
349
|
};
|
|
290
350
|
|
|
291
351
|
const levelColors: Record<string, string> = {
|
|
292
|
-
debug: "text-[
|
|
352
|
+
debug: "text-[var(--color-text-faint)]",
|
|
293
353
|
info: "text-blue-400",
|
|
294
354
|
warn: "text-yellow-400",
|
|
295
355
|
error: "text-red-400",
|
|
@@ -354,61 +414,256 @@ export function TelemetryPage() {
|
|
|
354
414
|
: `Telemetry - ${currentProject?.name || ""}`}
|
|
355
415
|
</h1>
|
|
356
416
|
</div>
|
|
357
|
-
<p className="text-[
|
|
417
|
+
<p className="text-[var(--color-text-muted)]">
|
|
358
418
|
Monitor agent activity, token usage, and errors.
|
|
359
419
|
</p>
|
|
360
420
|
</div>
|
|
361
421
|
|
|
362
422
|
{/* Stats Cards */}
|
|
363
423
|
{stats && (
|
|
364
|
-
<div className=
|
|
424
|
+
<div className={`grid grid-cols-2 md:grid-cols-3 ${costTrackingEnabled ? "lg:grid-cols-7" : "lg:grid-cols-6"} gap-4 mb-6`}>
|
|
365
425
|
<StatCard label="Events" value={formatNumber(stats.total_events)} />
|
|
366
426
|
<StatCard label="LLM Calls" value={formatNumber(stats.total_llm_calls)} />
|
|
367
427
|
<StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />
|
|
368
428
|
<StatCard label="Errors" value={formatNumber(stats.total_errors)} color="red" />
|
|
369
429
|
<StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
|
|
370
430
|
<StatCard label="Output Tokens" value={formatNumber(stats.total_output_tokens)} />
|
|
431
|
+
{costTrackingEnabled && (
|
|
432
|
+
<StatCard label="Total Cost" value={`$${stats.total_cost.toFixed(4)}`} color="orange" />
|
|
433
|
+
)}
|
|
371
434
|
</div>
|
|
372
435
|
)}
|
|
373
436
|
|
|
437
|
+
{/* Charts */}
|
|
438
|
+
{(() => {
|
|
439
|
+
// Use daily data if we have multiple days, otherwise aggregate events by hour
|
|
440
|
+
const useDaily = dailyUsage.length > 1;
|
|
441
|
+
const chartData = useDaily ? dailyUsage : (() => {
|
|
442
|
+
// Aggregate all visible events by hour
|
|
443
|
+
const buckets = new Map<string, { date: string; llm_calls: number; tool_calls: number; errors: number; input_tokens: number; output_tokens: number }>();
|
|
444
|
+
for (const event of allEvents) {
|
|
445
|
+
const d = new Date(event.timestamp);
|
|
446
|
+
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:00`;
|
|
447
|
+
if (!buckets.has(key)) {
|
|
448
|
+
buckets.set(key, { date: key, llm_calls: 0, tool_calls: 0, errors: 0, input_tokens: 0, output_tokens: 0 });
|
|
449
|
+
}
|
|
450
|
+
const b = buckets.get(key)!;
|
|
451
|
+
const s = extractEventStats(event);
|
|
452
|
+
b.llm_calls += s.llm_calls;
|
|
453
|
+
b.tool_calls += s.tool_calls;
|
|
454
|
+
b.errors += s.errors;
|
|
455
|
+
b.input_tokens += s.input_tokens;
|
|
456
|
+
b.output_tokens += s.output_tokens;
|
|
457
|
+
}
|
|
458
|
+
return Array.from(buckets.values()).sort((a, b) => a.date.localeCompare(b.date));
|
|
459
|
+
})();
|
|
460
|
+
const chartLabel = useDaily ? "Daily" : "Hourly";
|
|
461
|
+
|
|
462
|
+
if (chartData.length === 0) return null;
|
|
463
|
+
|
|
464
|
+
return (
|
|
465
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
|
466
|
+
{/* Activity Chart */}
|
|
467
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
468
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Activity</h3>
|
|
469
|
+
<ResponsiveContainer width="100%" height={200}>
|
|
470
|
+
<AreaChart data={chartData}>
|
|
471
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
|
|
472
|
+
<XAxis
|
|
473
|
+
dataKey="date"
|
|
474
|
+
stroke="var(--color-border-light)"
|
|
475
|
+
tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
|
|
476
|
+
tickFormatter={(v) => {
|
|
477
|
+
if (!useDaily && v.includes(" ")) {
|
|
478
|
+
return v.split(" ")[1];
|
|
479
|
+
}
|
|
480
|
+
const d = new Date(v + "T00:00:00");
|
|
481
|
+
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
482
|
+
}}
|
|
483
|
+
/>
|
|
484
|
+
<YAxis stroke="var(--color-border-light)" tick={{ fill: "var(--color-text-muted)", fontSize: 11 }} allowDecimals={false} />
|
|
485
|
+
<Tooltip
|
|
486
|
+
contentStyle={{
|
|
487
|
+
backgroundColor: "var(--color-surface)",
|
|
488
|
+
border: "1px solid var(--color-border-light)",
|
|
489
|
+
borderRadius: "8px",
|
|
490
|
+
fontSize: 12,
|
|
491
|
+
}}
|
|
492
|
+
labelStyle={{ color: "var(--color-text-secondary)" }}
|
|
493
|
+
cursor={{ stroke: "rgba(255,255,255,0.1)" }}
|
|
494
|
+
labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
|
|
495
|
+
/>
|
|
496
|
+
<Legend
|
|
497
|
+
wrapperStyle={{ fontSize: 11 }}
|
|
498
|
+
iconType="circle"
|
|
499
|
+
iconSize={8}
|
|
500
|
+
/>
|
|
501
|
+
<Area
|
|
502
|
+
type="monotone"
|
|
503
|
+
dataKey="llm_calls"
|
|
504
|
+
name="LLM Calls"
|
|
505
|
+
stroke="var(--color-accent)"
|
|
506
|
+
fill="var(--color-accent)"
|
|
507
|
+
fillOpacity={0.15}
|
|
508
|
+
strokeWidth={1.5}
|
|
509
|
+
/>
|
|
510
|
+
<Area
|
|
511
|
+
type="monotone"
|
|
512
|
+
dataKey="tool_calls"
|
|
513
|
+
name="Tool Calls"
|
|
514
|
+
stroke="var(--color-accent-hover)"
|
|
515
|
+
fill="var(--color-accent-hover)"
|
|
516
|
+
fillOpacity={0.08}
|
|
517
|
+
strokeWidth={1.5}
|
|
518
|
+
/>
|
|
519
|
+
<Area
|
|
520
|
+
type="monotone"
|
|
521
|
+
dataKey="errors"
|
|
522
|
+
name="Errors"
|
|
523
|
+
stroke="#ef4444"
|
|
524
|
+
fill="#ef4444"
|
|
525
|
+
fillOpacity={0.1}
|
|
526
|
+
strokeWidth={1.5}
|
|
527
|
+
/>
|
|
528
|
+
</AreaChart>
|
|
529
|
+
</ResponsiveContainer>
|
|
530
|
+
</div>
|
|
531
|
+
|
|
532
|
+
{/* Token Usage Chart */}
|
|
533
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
534
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
|
|
535
|
+
<ResponsiveContainer width="100%" height={200}>
|
|
536
|
+
<BarChart data={chartData}>
|
|
537
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
|
|
538
|
+
<XAxis
|
|
539
|
+
dataKey="date"
|
|
540
|
+
stroke="var(--color-border-light)"
|
|
541
|
+
tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
|
|
542
|
+
tickFormatter={(v) => {
|
|
543
|
+
if (!useDaily && v.includes(" ")) {
|
|
544
|
+
return v.split(" ")[1];
|
|
545
|
+
}
|
|
546
|
+
const d = new Date(v + "T00:00:00");
|
|
547
|
+
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
548
|
+
}}
|
|
549
|
+
/>
|
|
550
|
+
<YAxis
|
|
551
|
+
stroke="var(--color-border-light)"
|
|
552
|
+
tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
|
|
553
|
+
tickFormatter={(v) => {
|
|
554
|
+
if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`;
|
|
555
|
+
if (v >= 1000) return `${(v / 1000).toFixed(0)}K`;
|
|
556
|
+
return v;
|
|
557
|
+
}}
|
|
558
|
+
/>
|
|
559
|
+
<Tooltip
|
|
560
|
+
contentStyle={{
|
|
561
|
+
backgroundColor: "var(--color-surface)",
|
|
562
|
+
border: "1px solid var(--color-border-light)",
|
|
563
|
+
borderRadius: "8px",
|
|
564
|
+
fontSize: 12,
|
|
565
|
+
}}
|
|
566
|
+
labelStyle={{ color: "var(--color-text-secondary)" }}
|
|
567
|
+
cursor={{ fill: "rgba(255,255,255,0.03)" }}
|
|
568
|
+
labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
|
|
569
|
+
formatter={(value: number) => [value.toLocaleString(), undefined]}
|
|
570
|
+
/>
|
|
571
|
+
<Legend
|
|
572
|
+
wrapperStyle={{ fontSize: 11 }}
|
|
573
|
+
iconType="circle"
|
|
574
|
+
iconSize={8}
|
|
575
|
+
/>
|
|
576
|
+
<Bar
|
|
577
|
+
dataKey="input_tokens"
|
|
578
|
+
name="Input Tokens"
|
|
579
|
+
fill="var(--color-accent)"
|
|
580
|
+
radius={[2, 2, 0, 0]}
|
|
581
|
+
/>
|
|
582
|
+
<Bar
|
|
583
|
+
dataKey="output_tokens"
|
|
584
|
+
name="Output Tokens"
|
|
585
|
+
fill="var(--color-accent-hover)"
|
|
586
|
+
radius={[2, 2, 0, 0]}
|
|
587
|
+
/>
|
|
588
|
+
</BarChart>
|
|
589
|
+
</ResponsiveContainer>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
);
|
|
593
|
+
})()}
|
|
594
|
+
|
|
374
595
|
{/* Usage by Agent */}
|
|
375
|
-
{usage.length > 0 && (
|
|
596
|
+
{usage.length > 0 && (() => {
|
|
597
|
+
const maxCost = Math.max(...sortedUsage.map(u => u.cost), 0.0001);
|
|
598
|
+
const SortHeader = ({ label, field, align = "right" }: { label: string; field: SortKey; align?: string }) => (
|
|
599
|
+
<th
|
|
600
|
+
className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
|
|
601
|
+
onClick={() => handleSort(field)}
|
|
602
|
+
>
|
|
603
|
+
<span className="inline-flex items-center gap-1">
|
|
604
|
+
{align === "right" && sortKey === field && (
|
|
605
|
+
<span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
|
|
606
|
+
)}
|
|
607
|
+
{label}
|
|
608
|
+
{align === "left" && sortKey === field && (
|
|
609
|
+
<span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
|
|
610
|
+
)}
|
|
611
|
+
</span>
|
|
612
|
+
</th>
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
return (
|
|
376
616
|
<div className="mb-6">
|
|
377
617
|
<h2 className="text-lg font-medium mb-3">Usage by Agent</h2>
|
|
378
|
-
<div className="bg-[
|
|
618
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg overflow-hidden">
|
|
379
619
|
<table className="w-full text-sm">
|
|
380
620
|
<thead>
|
|
381
|
-
<tr className="border-b border-[
|
|
382
|
-
<
|
|
383
|
-
<
|
|
384
|
-
<
|
|
385
|
-
<
|
|
386
|
-
<
|
|
387
|
-
<
|
|
621
|
+
<tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
|
|
622
|
+
<SortHeader label="Agent" field="agent" align="left" />
|
|
623
|
+
<SortHeader label="LLM Calls" field="llm_calls" />
|
|
624
|
+
<SortHeader label="Tool Calls" field="tool_calls" />
|
|
625
|
+
<SortHeader label="Input Tokens" field="input_tokens" />
|
|
626
|
+
<SortHeader label="Output Tokens" field="output_tokens" />
|
|
627
|
+
<SortHeader label="Errors" field="errors" />
|
|
628
|
+
{costTrackingEnabled && <SortHeader label="Est. Cost" field="cost" />}
|
|
388
629
|
</tr>
|
|
389
630
|
</thead>
|
|
390
631
|
<tbody>
|
|
391
|
-
{
|
|
392
|
-
<tr key={u.agent_id} className="border-b border-[
|
|
632
|
+
{sortedUsage.map((u) => (
|
|
633
|
+
<tr key={u.agent_id} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
|
|
393
634
|
<td className="p-3 font-medium">{getAgentName(u.agent_id)}</td>
|
|
394
|
-
<td className="p-3 text-right text-[
|
|
395
|
-
<td className="p-3 text-right text-[
|
|
396
|
-
<td className="p-3 text-right text-[
|
|
397
|
-
<td className="p-3 text-right text-[
|
|
635
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
|
|
636
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
|
|
637
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
|
|
638
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
|
|
398
639
|
<td className="p-3 text-right">
|
|
399
640
|
{u.errors > 0 ? (
|
|
400
641
|
<span className="text-red-400">{u.errors}</span>
|
|
401
642
|
) : (
|
|
402
|
-
<span className="text-[
|
|
643
|
+
<span className="text-[var(--color-text-faint)]">0</span>
|
|
403
644
|
)}
|
|
404
645
|
</td>
|
|
646
|
+
{costTrackingEnabled && (
|
|
647
|
+
<td className="p-3 text-right">
|
|
648
|
+
<div className="flex items-center justify-end gap-2">
|
|
649
|
+
<div className="w-16 h-1.5 bg-[var(--color-surface-raised)] rounded-full overflow-hidden">
|
|
650
|
+
<div
|
|
651
|
+
className="h-full bg-orange-500 rounded-full"
|
|
652
|
+
style={{ width: `${(u.cost / maxCost) * 100}%` }}
|
|
653
|
+
/>
|
|
654
|
+
</div>
|
|
655
|
+
<span className="text-[var(--color-text-secondary)] min-w-[60px] text-right">${u.cost.toFixed(4)}</span>
|
|
656
|
+
</div>
|
|
657
|
+
</td>
|
|
658
|
+
)}
|
|
405
659
|
</tr>
|
|
406
660
|
))}
|
|
407
661
|
</tbody>
|
|
408
662
|
</table>
|
|
409
663
|
</div>
|
|
410
664
|
</div>
|
|
411
|
-
|
|
665
|
+
);
|
|
666
|
+
})()}
|
|
412
667
|
|
|
413
668
|
{/* Filters */}
|
|
414
669
|
<div className="flex flex-wrap items-center gap-3 mb-4">
|
|
@@ -424,14 +679,14 @@ export function TelemetryPage() {
|
|
|
424
679
|
<div className="flex flex-wrap items-center gap-1.5 flex-1">
|
|
425
680
|
{allCategories.map((cat) => {
|
|
426
681
|
const isHidden = hiddenCategories.has(cat);
|
|
427
|
-
const colorClass = categoryColors[cat] || "bg-[
|
|
682
|
+
const colorClass = categoryColors[cat] || "bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] border-[var(--color-border-light)]";
|
|
428
683
|
return (
|
|
429
684
|
<button
|
|
430
685
|
key={cat}
|
|
431
686
|
onClick={() => toggleCategory(cat)}
|
|
432
687
|
className={`px-2 py-0.5 rounded text-xs border transition-all ${
|
|
433
688
|
isHidden
|
|
434
|
-
? "bg-[
|
|
689
|
+
? "bg-[var(--color-surface-raised)] text-[var(--color-text-faint)] border-[var(--color-border-light)] opacity-50"
|
|
435
690
|
: colorClass
|
|
436
691
|
}`}
|
|
437
692
|
>
|
|
@@ -451,7 +706,7 @@ export function TelemetryPage() {
|
|
|
451
706
|
</div>
|
|
452
707
|
<button
|
|
453
708
|
onClick={fetchData}
|
|
454
|
-
className="px-3 py-2 bg-[
|
|
709
|
+
className="px-3 py-2 bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] border border-[var(--color-border-light)] rounded text-sm transition"
|
|
455
710
|
>
|
|
456
711
|
Refresh
|
|
457
712
|
</button>
|
|
@@ -459,31 +714,31 @@ export function TelemetryPage() {
|
|
|
459
714
|
</div>
|
|
460
715
|
|
|
461
716
|
{/* Events List */}
|
|
462
|
-
<div className="bg-[
|
|
463
|
-
<div className="p-3 border-b border-[
|
|
717
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg">
|
|
718
|
+
<div className="p-3 border-b border-[var(--color-border)] flex items-center justify-between">
|
|
464
719
|
<h2 className="font-medium">Recent Events</h2>
|
|
465
720
|
{realtimeEvents.length > 0 && (
|
|
466
|
-
<span className="text-xs text-[
|
|
721
|
+
<span className="text-xs text-[var(--color-text-muted)]">
|
|
467
722
|
{realtimeEvents.length} new
|
|
468
723
|
</span>
|
|
469
724
|
)}
|
|
470
725
|
</div>
|
|
471
726
|
|
|
472
727
|
{loading && allEvents.length === 0 ? (
|
|
473
|
-
<div className="p-8 text-center text-[
|
|
728
|
+
<div className="p-8 text-center text-[var(--color-text-muted)]">Loading...</div>
|
|
474
729
|
) : allEvents.length === 0 ? (
|
|
475
|
-
<div className="p-8 text-center text-[
|
|
730
|
+
<div className="p-8 text-center text-[var(--color-text-muted)]">
|
|
476
731
|
No telemetry events yet. Events will appear here in real-time once agents start sending data.
|
|
477
732
|
</div>
|
|
478
733
|
) : (
|
|
479
|
-
<div className="divide-y divide-[
|
|
734
|
+
<div className="divide-y divide-[var(--color-border)]">
|
|
480
735
|
{allEvents.map((event) => {
|
|
481
736
|
const isNew = newEventIds.has(event.id);
|
|
482
737
|
|
|
483
738
|
return (
|
|
484
739
|
<div
|
|
485
740
|
key={event.id}
|
|
486
|
-
className={`p-3 hover:bg-[
|
|
741
|
+
className={`p-3 hover:bg-[var(--color-bg)] cursor-pointer transition-all duration-500 ${
|
|
487
742
|
isNew ? "bg-green-500/5" : ""
|
|
488
743
|
}`}
|
|
489
744
|
style={{
|
|
@@ -492,17 +747,17 @@ export function TelemetryPage() {
|
|
|
492
747
|
onClick={() => setExpandedEvent(expandedEvent === event.id ? null : event.id)}
|
|
493
748
|
>
|
|
494
749
|
<div className="flex items-start gap-3">
|
|
495
|
-
<span className={`px-2 py-0.5 rounded text-xs border transition-colors duration-300 ${categoryColors[event.category] || "bg-[
|
|
750
|
+
<span className={`px-2 py-0.5 rounded text-xs border transition-colors duration-300 ${categoryColors[event.category] || "bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] border-[var(--color-border-light)]"}`}>
|
|
496
751
|
{event.category}
|
|
497
752
|
</span>
|
|
498
753
|
<div className="flex-1 min-w-0">
|
|
499
754
|
<div className="flex items-center gap-2">
|
|
500
755
|
<span className="font-medium text-sm">{event.type}</span>
|
|
501
|
-
<span className={`text-xs ${levelColors[event.level] || "text-[
|
|
756
|
+
<span className={`text-xs ${levelColors[event.level] || "text-[var(--color-text-muted)]"}`}>
|
|
502
757
|
{event.level}
|
|
503
758
|
</span>
|
|
504
759
|
{event.duration_ms && (
|
|
505
|
-
<span className="text-xs text-[
|
|
760
|
+
<span className="text-xs text-[var(--color-text-faint)]">{event.duration_ms}ms</span>
|
|
506
761
|
)}
|
|
507
762
|
<span
|
|
508
763
|
className={`w-1.5 h-1.5 rounded-full bg-green-400 transition-opacity duration-1000 ${
|
|
@@ -510,14 +765,14 @@ export function TelemetryPage() {
|
|
|
510
765
|
}`}
|
|
511
766
|
/>
|
|
512
767
|
</div>
|
|
513
|
-
<div className="text-xs text-[
|
|
768
|
+
<div className="text-xs text-[var(--color-text-faint)] mt-1">
|
|
514
769
|
{getAgentName(event.agent_id)} · {new Date(event.timestamp).toLocaleString()}
|
|
515
770
|
</div>
|
|
516
771
|
{event.error && (
|
|
517
772
|
<div className="text-xs text-red-400 mt-1 font-mono">{event.error}</div>
|
|
518
773
|
)}
|
|
519
774
|
{expandedEvent === event.id && event.data && Object.keys(event.data).length > 0 && (
|
|
520
|
-
<pre className="text-xs text-[
|
|
775
|
+
<pre className="text-xs text-[var(--color-text-muted)] mt-2 p-2 bg-[var(--color-bg)] rounded overflow-x-auto">
|
|
521
776
|
{JSON.stringify(event.data, null, 2)}
|
|
522
777
|
</pre>
|
|
523
778
|
)}
|
|
@@ -536,9 +791,9 @@ export function TelemetryPage() {
|
|
|
536
791
|
|
|
537
792
|
function StatCard({ label, value, color }: { label: string; value: string; color?: string }) {
|
|
538
793
|
return (
|
|
539
|
-
<div className="bg-[
|
|
540
|
-
<div className="text-[
|
|
541
|
-
<div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : ""}`}>
|
|
794
|
+
<div className="bg-[var(--color-surface)] border border-[var(--color-border)] rounded-lg p-4">
|
|
795
|
+
<div className="text-[var(--color-text-muted)] text-xs mb-1">{label}</div>
|
|
796
|
+
<div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : color === "orange" ? "text-orange-400" : ""}`}>
|
|
542
797
|
{value}
|
|
543
798
|
</div>
|
|
544
799
|
</div>
|