apteva 0.4.41 → 0.4.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ActivityPage.sw9p594m.js +3 -0
- package/dist/ApiDocsPage.90e03bz7.js +4 -0
- package/dist/App.0ws87fpx.js +53 -0
- package/dist/App.3vnrera5.js +4 -0
- package/dist/App.94x6mh7f.js +20 -0
- package/dist/{App.7fb3e7mp.js → App.9sryp183.js} +1 -1
- package/dist/App.d9tny4t0.js +221 -0
- package/dist/App.jhb45d7r.js +8 -0
- package/dist/App.p7jjw1zf.js +4 -0
- package/dist/App.pfbdzrhh.js +4 -0
- package/dist/App.stgng5bx.js +13 -0
- package/dist/App.tm3k7h4b.js +4 -0
- package/dist/App.vkg121c6.js +4 -0
- package/dist/App.wghtdzsk.js +1 -0
- package/dist/App.xf7wsckg.js +4 -0
- package/dist/App.xva0tfzh.js +4 -0
- package/dist/App.ysxy7akk.js +61 -0
- package/dist/App.yzkh4gq2.js +4 -0
- package/dist/ConnectionsPage.q5f9fd37.js +3 -0
- package/dist/McpPage.f3ccrezb.js +3 -0
- package/dist/SettingsPage.3sqx6wm4.js +3 -0
- package/dist/SkillsPage.whxnez67.js +3 -0
- package/dist/TasksPage.zp4jfevw.js +3 -0
- package/dist/TelemetryPage.a9fmxq87.js +3 -0
- package/dist/TestsPage.18krj0d1.js +3 -0
- package/dist/ThreadsPage.nnphgy98.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +11 -10
- package/src/db.ts +61 -13
- package/src/integrations/agentdojo.ts +1 -0
- package/src/mcp-platform.ts +418 -63
- package/src/openapi.ts +96 -0
- package/src/providers.ts +55 -24
- package/src/routes/api/agent-utils.ts +25 -4
- package/src/routes/api/agents.ts +19 -1
- package/src/routes/api/meta-agent.ts +2 -0
- package/src/routes/api/system.ts +90 -1
- package/src/routes/api/telemetry.ts +38 -2
- package/src/routes/share.ts +85 -0
- package/src/server.ts +64 -1
- package/src/web/App.tsx +89 -11
- package/src/web/components/activity/ActivityPage.tsx +14 -14
- package/src/web/components/agents/AgentCard.tsx +19 -17
- package/src/web/components/agents/AgentPanel.tsx +541 -220
- package/src/web/components/agents/AgentsView.tsx +4 -4
- package/src/web/components/agents/CreateAgentModal.tsx +24 -82
- 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/LoadingSpinner.tsx +2 -2
- package/src/web/components/common/Modal.tsx +9 -9
- package/src/web/components/common/Select.tsx +9 -9
- 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 +32 -32
- package/src/web/components/layout/Header.tsx +50 -34
- package/src/web/components/layout/Sidebar.tsx +35 -15
- package/src/web/components/mcp/IntegrationsPanel.tsx +40 -40
- package/src/web/components/mcp/McpPage.tsx +208 -208
- package/src/web/components/meta-agent/MetaAgent.tsx +12 -10
- package/src/web/components/onboarding/OnboardingWizard.tsx +25 -25
- package/src/web/components/settings/SettingsPage.tsx +291 -175
- package/src/web/components/skills/SkillsPage.tsx +88 -88
- package/src/web/components/tasks/TasksPage.tsx +539 -78
- package/src/web/components/telemetry/TelemetryPage.tsx +405 -65
- package/src/web/components/tests/TestsPage.tsx +50 -50
- package/src/web/components/threads/ThreadsPage.tsx +23 -21
- package/src/web/context/ProjectContext.tsx +6 -1
- package/src/web/context/ThemeContext.tsx +90 -0
- package/src/web/context/index.ts +2 -0
- package/src/web/index.html +1 -6
- package/src/web/styles.css +52 -3
- package/src/web/themes.ts +162 -0
- package/src/web/types.ts +0 -4
- package/dist/ActivityPage.7907h64p.js +0 -3
- package/dist/ApiDocsPage.k3jjenpq.js +0 -4
- package/dist/App.01nq20st.js +0 -4
- package/dist/App.1maqvamf.js +0 -4
- package/dist/App.2yjrh32f.js +0 -4
- package/dist/App.3qw8nben.js +0 -20
- package/dist/App.7sy3wq8c.js +0 -4
- package/dist/App.apjrmctz.js +0 -57
- package/dist/App.av6t2yhe.js +0 -4
- package/dist/App.jqj5a094.js +0 -46
- package/dist/App.mc7xf85h.js +0 -4
- package/dist/App.myxqcj9x.js +0 -4
- package/dist/App.nm91r1mp.js +0 -13
- package/dist/App.p02f4ret.js +0 -1
- package/dist/App.qcknavjz.js +0 -221
- package/dist/App.vc7vfhg4.js +0 -4
- package/dist/App.z4s9zkw5.js +0 -4
- package/dist/ConnectionsPage.z1pw5xe2.js +0 -3
- package/dist/McpPage.8vc97z0b.js +0 -3
- package/dist/SettingsPage.p61bz8kd.js +0 -3
- package/dist/SkillsPage.r9x43g3g.js +0 -3
- package/dist/TasksPage.1e0zkye4.js +0 -3
- package/dist/TelemetryPage.p9vbe4gf.js +0 -3
- package/dist/TestsPage.d4xy504e.js +0 -3
- package/dist/ThreadsPage.m016am3x.js +0 -3
|
@@ -13,15 +13,36 @@ interface TelemetryStats {
|
|
|
13
13
|
total_errors: number;
|
|
14
14
|
total_input_tokens: number;
|
|
15
15
|
total_output_tokens: number;
|
|
16
|
+
total_cache_creation_tokens: number;
|
|
17
|
+
total_cache_read_tokens: number;
|
|
18
|
+
total_reasoning_tokens: number;
|
|
19
|
+
total_cost: number;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
interface UsageByAgent {
|
|
19
23
|
agent_id: string;
|
|
20
24
|
input_tokens: number;
|
|
21
25
|
output_tokens: number;
|
|
26
|
+
cache_creation_tokens: number;
|
|
27
|
+
cache_read_tokens: number;
|
|
28
|
+
reasoning_tokens: number;
|
|
22
29
|
llm_calls: number;
|
|
23
30
|
tool_calls: number;
|
|
24
31
|
errors: number;
|
|
32
|
+
cost: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface UsageByProject {
|
|
36
|
+
project_id: string | null;
|
|
37
|
+
input_tokens: number;
|
|
38
|
+
output_tokens: number;
|
|
39
|
+
cache_creation_tokens: number;
|
|
40
|
+
cache_read_tokens: number;
|
|
41
|
+
reasoning_tokens: number;
|
|
42
|
+
llm_calls: number;
|
|
43
|
+
tool_calls: number;
|
|
44
|
+
errors: number;
|
|
45
|
+
cost: number;
|
|
25
46
|
}
|
|
26
47
|
|
|
27
48
|
interface DailyUsage {
|
|
@@ -40,12 +61,18 @@ function extractEventStats(event: TelemetryEvent): {
|
|
|
40
61
|
errors: number;
|
|
41
62
|
input_tokens: number;
|
|
42
63
|
output_tokens: number;
|
|
64
|
+
cache_creation_tokens: number;
|
|
65
|
+
cache_read_tokens: number;
|
|
66
|
+
reasoning_tokens: number;
|
|
43
67
|
} {
|
|
44
68
|
const isLlm = event.category === "LLM";
|
|
45
69
|
const isTool = event.category === "TOOL";
|
|
46
70
|
const isError = event.level === "error";
|
|
47
71
|
const inputTokens = (event.data?.input_tokens as number) || 0;
|
|
48
72
|
const outputTokens = (event.data?.output_tokens as number) || 0;
|
|
73
|
+
const cacheCreationTokens = (event.data?.cache_creation_tokens as number) || 0;
|
|
74
|
+
const cacheReadTokens = (event.data?.cache_read_tokens as number) || 0;
|
|
75
|
+
const reasoningTokens = (event.data?.reasoning_tokens as number) || 0;
|
|
49
76
|
|
|
50
77
|
return {
|
|
51
78
|
llm_calls: isLlm ? 1 : 0,
|
|
@@ -53,17 +80,21 @@ function extractEventStats(event: TelemetryEvent): {
|
|
|
53
80
|
errors: isError ? 1 : 0,
|
|
54
81
|
input_tokens: inputTokens,
|
|
55
82
|
output_tokens: outputTokens,
|
|
83
|
+
cache_creation_tokens: cacheCreationTokens,
|
|
84
|
+
cache_read_tokens: cacheReadTokens,
|
|
85
|
+
reasoning_tokens: reasoningTokens,
|
|
56
86
|
};
|
|
57
87
|
}
|
|
58
88
|
|
|
59
89
|
export function TelemetryPage() {
|
|
60
90
|
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
61
|
-
const { currentProjectId, currentProject } = useProjects();
|
|
91
|
+
const { currentProjectId, currentProject, costTrackingEnabled, projectsEnabled, projects } = useProjects();
|
|
62
92
|
const { authFetch } = useAuth();
|
|
63
93
|
const [fetchedStats, setFetchedStats] = useState<TelemetryStats | null>(null);
|
|
64
94
|
const [historicalEvents, setHistoricalEvents] = useState<TelemetryEvent[]>([]);
|
|
65
95
|
const [fetchedUsage, setFetchedUsage] = useState<UsageByAgent[]>([]);
|
|
66
96
|
const [dailyUsage, setDailyUsage] = useState<DailyUsage[]>([]);
|
|
97
|
+
const [projectUsage, setProjectUsage] = useState<UsageByProject[]>([]);
|
|
67
98
|
const [loading, setLoading] = useState(true);
|
|
68
99
|
const [filter, setFilter] = useState({
|
|
69
100
|
level: "",
|
|
@@ -74,6 +105,37 @@ export function TelemetryPage() {
|
|
|
74
105
|
const [agents, setAgents] = useState<Array<{ id: string; name: string; projectId: string | null }>>([]);
|
|
75
106
|
const [expandedEvent, setExpandedEvent] = useState<string | null>(null);
|
|
76
107
|
|
|
108
|
+
// Time range filter
|
|
109
|
+
const [timeRange, setTimeRange] = useState<string>("all");
|
|
110
|
+
|
|
111
|
+
// Sort state for usage table
|
|
112
|
+
type SortKey = "agent" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "cache_creation_tokens" | "cache_read_tokens" | "reasoning_tokens" | "errors" | "cost";
|
|
113
|
+
const [sortKey, setSortKey] = useState<SortKey>("cost");
|
|
114
|
+
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
|
115
|
+
|
|
116
|
+
const handleSort = (key: SortKey) => {
|
|
117
|
+
if (sortKey === key) {
|
|
118
|
+
setSortDir(d => d === "asc" ? "desc" : "asc");
|
|
119
|
+
} else {
|
|
120
|
+
setSortKey(key);
|
|
121
|
+
setSortDir("desc");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// Sort state for project usage table
|
|
126
|
+
type ProjectSortKey = "project" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "cache_creation_tokens" | "cache_read_tokens" | "reasoning_tokens" | "errors" | "cost";
|
|
127
|
+
const [projectSortKey, setProjectSortKey] = useState<ProjectSortKey>("cost");
|
|
128
|
+
const [projectSortDir, setProjectSortDir] = useState<"asc" | "desc">("desc");
|
|
129
|
+
|
|
130
|
+
const handleProjectSort = (key: ProjectSortKey) => {
|
|
131
|
+
if (projectSortKey === key) {
|
|
132
|
+
setProjectSortDir(d => d === "asc" ? "desc" : "asc");
|
|
133
|
+
} else {
|
|
134
|
+
setProjectSortKey(key);
|
|
135
|
+
setProjectSortDir("desc");
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
77
139
|
// Track IDs that were in the fetched stats to avoid double-counting
|
|
78
140
|
const countedEventIdsRef = useRef<Set<string>>(new Set());
|
|
79
141
|
|
|
@@ -105,16 +167,35 @@ export function TelemetryPage() {
|
|
|
105
167
|
// Get agent IDs for the current project
|
|
106
168
|
const projectAgentIds = useMemo(() => new Set(filteredAgents.map(a => a.id)), [filteredAgents]);
|
|
107
169
|
|
|
170
|
+
// Compute `since` ISO string from selected time range
|
|
171
|
+
const getSince = useCallback((): string | undefined => {
|
|
172
|
+
if (timeRange === "all") return undefined;
|
|
173
|
+
const now = new Date();
|
|
174
|
+
const ms: Record<string, number> = {
|
|
175
|
+
"1h": 3600000,
|
|
176
|
+
"6h": 6 * 3600000,
|
|
177
|
+
"24h": 24 * 3600000,
|
|
178
|
+
"7d": 7 * 24 * 3600000,
|
|
179
|
+
"30d": 30 * 24 * 3600000,
|
|
180
|
+
};
|
|
181
|
+
if (ms[timeRange]) {
|
|
182
|
+
return new Date(now.getTime() - ms[timeRange]).toISOString();
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
}, [timeRange]);
|
|
186
|
+
|
|
108
187
|
// Fetch stats and historical data (less frequently now since we have real-time)
|
|
109
188
|
const fetchData = async () => {
|
|
110
189
|
setLoading(true);
|
|
111
190
|
try {
|
|
112
191
|
// Build project filter param
|
|
113
192
|
const projectParam = currentProjectId === "unassigned" ? "null" : currentProjectId || "";
|
|
193
|
+
const since = getSince();
|
|
114
194
|
|
|
115
195
|
// Fetch stats
|
|
116
196
|
const statsParams = new URLSearchParams();
|
|
117
197
|
if (projectParam) statsParams.set("project_id", projectParam);
|
|
198
|
+
if (since) statsParams.set("since", since);
|
|
118
199
|
const statsRes = await authFetch(`/api/telemetry/stats${statsParams.toString() ? `?${statsParams}` : ""}`);
|
|
119
200
|
const statsData = await statsRes.json();
|
|
120
201
|
setFetchedStats(statsData.stats);
|
|
@@ -124,6 +205,7 @@ export function TelemetryPage() {
|
|
|
124
205
|
if (filter.level) params.set("level", filter.level);
|
|
125
206
|
if (filter.agent_id) params.set("agent_id", filter.agent_id);
|
|
126
207
|
if (projectParam) params.set("project_id", projectParam);
|
|
208
|
+
if (since) params.set("since", since);
|
|
127
209
|
params.set("limit", "100"); // Fetch more since we filter client-side
|
|
128
210
|
|
|
129
211
|
const eventsRes = await authFetch(`/api/telemetry/events?${params}`);
|
|
@@ -138,6 +220,7 @@ export function TelemetryPage() {
|
|
|
138
220
|
const usageParams = new URLSearchParams();
|
|
139
221
|
usageParams.set("group_by", "agent");
|
|
140
222
|
if (projectParam) usageParams.set("project_id", projectParam);
|
|
223
|
+
if (since) usageParams.set("since", since);
|
|
141
224
|
const usageRes = await authFetch(`/api/telemetry/usage?${usageParams}`);
|
|
142
225
|
const usageData = await usageRes.json();
|
|
143
226
|
setFetchedUsage(usageData.usage || []);
|
|
@@ -146,6 +229,7 @@ export function TelemetryPage() {
|
|
|
146
229
|
const dailyParams = new URLSearchParams();
|
|
147
230
|
dailyParams.set("group_by", "day");
|
|
148
231
|
if (projectParam) dailyParams.set("project_id", projectParam);
|
|
232
|
+
if (since) dailyParams.set("since", since);
|
|
149
233
|
const dailyRes = await authFetch(`/api/telemetry/usage?${dailyParams}`);
|
|
150
234
|
const dailyData = await dailyRes.json();
|
|
151
235
|
// Sort by date ascending for charts
|
|
@@ -153,6 +237,18 @@ export function TelemetryPage() {
|
|
|
153
237
|
a.date.localeCompare(b.date)
|
|
154
238
|
);
|
|
155
239
|
setDailyUsage(sorted);
|
|
240
|
+
|
|
241
|
+
// Fetch usage by project (only when viewing all projects and feature is enabled)
|
|
242
|
+
if (projectsEnabled && currentProjectId === null) {
|
|
243
|
+
const projParams = new URLSearchParams();
|
|
244
|
+
projParams.set("group_by", "project");
|
|
245
|
+
if (since) projParams.set("since", since);
|
|
246
|
+
const projRes = await authFetch(`/api/telemetry/usage?${projParams}`);
|
|
247
|
+
const projData = await projRes.json();
|
|
248
|
+
setProjectUsage(projData.usage || []);
|
|
249
|
+
} else {
|
|
250
|
+
setProjectUsage([]);
|
|
251
|
+
}
|
|
156
252
|
} catch (e) {
|
|
157
253
|
console.error("Failed to fetch telemetry:", e);
|
|
158
254
|
}
|
|
@@ -161,7 +257,7 @@ export function TelemetryPage() {
|
|
|
161
257
|
|
|
162
258
|
useEffect(() => {
|
|
163
259
|
fetchData();
|
|
164
|
-
}, [filter, currentProjectId, authFetch, statusChangeCounter]);
|
|
260
|
+
}, [filter, timeRange, currentProjectId, authFetch, statusChangeCounter]);
|
|
165
261
|
|
|
166
262
|
// Compute real-time stats from new events (not already counted in fetched stats)
|
|
167
263
|
const stats = useMemo(() => {
|
|
@@ -174,6 +270,9 @@ export function TelemetryPage() {
|
|
|
174
270
|
let deltaErrors = 0;
|
|
175
271
|
let deltaInputTokens = 0;
|
|
176
272
|
let deltaOutputTokens = 0;
|
|
273
|
+
let deltaCacheCreationTokens = 0;
|
|
274
|
+
let deltaCacheReadTokens = 0;
|
|
275
|
+
let deltaReasoningTokens = 0;
|
|
177
276
|
|
|
178
277
|
for (const event of realtimeEvents) {
|
|
179
278
|
if (!countedEventIdsRef.current.has(event.id)) {
|
|
@@ -184,6 +283,9 @@ export function TelemetryPage() {
|
|
|
184
283
|
deltaErrors += eventStats.errors;
|
|
185
284
|
deltaInputTokens += eventStats.input_tokens;
|
|
186
285
|
deltaOutputTokens += eventStats.output_tokens;
|
|
286
|
+
deltaCacheCreationTokens += eventStats.cache_creation_tokens;
|
|
287
|
+
deltaCacheReadTokens += eventStats.cache_read_tokens;
|
|
288
|
+
deltaReasoningTokens += eventStats.reasoning_tokens;
|
|
187
289
|
}
|
|
188
290
|
}
|
|
189
291
|
|
|
@@ -194,6 +296,10 @@ export function TelemetryPage() {
|
|
|
194
296
|
total_errors: fetchedStats.total_errors + deltaErrors,
|
|
195
297
|
total_input_tokens: fetchedStats.total_input_tokens + deltaInputTokens,
|
|
196
298
|
total_output_tokens: fetchedStats.total_output_tokens + deltaOutputTokens,
|
|
299
|
+
total_cache_creation_tokens: (fetchedStats.total_cache_creation_tokens || 0) + deltaCacheCreationTokens,
|
|
300
|
+
total_cache_read_tokens: (fetchedStats.total_cache_read_tokens || 0) + deltaCacheReadTokens,
|
|
301
|
+
total_reasoning_tokens: (fetchedStats.total_reasoning_tokens || 0) + deltaReasoningTokens,
|
|
302
|
+
total_cost: fetchedStats.total_cost || 0,
|
|
197
303
|
};
|
|
198
304
|
}, [fetchedStats, realtimeEvents]);
|
|
199
305
|
|
|
@@ -216,6 +322,9 @@ export function TelemetryPage() {
|
|
|
216
322
|
existing.errors += eventStats.errors;
|
|
217
323
|
existing.input_tokens += eventStats.input_tokens;
|
|
218
324
|
existing.output_tokens += eventStats.output_tokens;
|
|
325
|
+
existing.cache_creation_tokens += eventStats.cache_creation_tokens;
|
|
326
|
+
existing.cache_read_tokens += eventStats.cache_read_tokens;
|
|
327
|
+
existing.reasoning_tokens += eventStats.reasoning_tokens;
|
|
219
328
|
} else {
|
|
220
329
|
usageMap.set(event.agent_id, {
|
|
221
330
|
agent_id: event.agent_id,
|
|
@@ -224,6 +333,10 @@ export function TelemetryPage() {
|
|
|
224
333
|
errors: eventStats.errors,
|
|
225
334
|
input_tokens: eventStats.input_tokens,
|
|
226
335
|
output_tokens: eventStats.output_tokens,
|
|
336
|
+
cache_creation_tokens: eventStats.cache_creation_tokens,
|
|
337
|
+
cache_read_tokens: eventStats.cache_read_tokens,
|
|
338
|
+
reasoning_tokens: eventStats.reasoning_tokens,
|
|
339
|
+
cost: 0,
|
|
227
340
|
});
|
|
228
341
|
}
|
|
229
342
|
}
|
|
@@ -232,6 +345,49 @@ export function TelemetryPage() {
|
|
|
232
345
|
return Array.from(usageMap.values());
|
|
233
346
|
}, [fetchedUsage, realtimeEvents]);
|
|
234
347
|
|
|
348
|
+
// Sorted usage for the table
|
|
349
|
+
const sortedUsage = useMemo(() => {
|
|
350
|
+
const sorted = [...usage];
|
|
351
|
+
sorted.sort((a, b) => {
|
|
352
|
+
if (sortKey === "agent") {
|
|
353
|
+
const aName = (agents.find(ag => ag.id === a.agent_id)?.name || a.agent_id).toLowerCase();
|
|
354
|
+
const bName = (agents.find(ag => ag.id === b.agent_id)?.name || b.agent_id).toLowerCase();
|
|
355
|
+
return sortDir === "asc" ? (aName < bName ? -1 : 1) : (aName > bName ? -1 : 1);
|
|
356
|
+
}
|
|
357
|
+
const aVal = a[sortKey] as number;
|
|
358
|
+
const bVal = b[sortKey] as number;
|
|
359
|
+
return sortDir === "asc" ? aVal - bVal : bVal - aVal;
|
|
360
|
+
});
|
|
361
|
+
return sorted;
|
|
362
|
+
}, [usage, sortKey, sortDir, agents]);
|
|
363
|
+
|
|
364
|
+
// Sorted project usage for the table
|
|
365
|
+
const sortedProjectUsage = useMemo(() => {
|
|
366
|
+
const sorted = [...projectUsage];
|
|
367
|
+
sorted.sort((a, b) => {
|
|
368
|
+
if (projectSortKey === "project") {
|
|
369
|
+
const aName = (a.project_id ? projects.find(p => p.id === a.project_id)?.name || a.project_id : "Unassigned").toLowerCase();
|
|
370
|
+
const bName = (b.project_id ? projects.find(p => p.id === b.project_id)?.name || b.project_id : "Unassigned").toLowerCase();
|
|
371
|
+
return projectSortDir === "asc" ? (aName < bName ? -1 : 1) : (aName > bName ? -1 : 1);
|
|
372
|
+
}
|
|
373
|
+
const aVal = a[projectSortKey] as number;
|
|
374
|
+
const bVal = b[projectSortKey] as number;
|
|
375
|
+
return projectSortDir === "asc" ? aVal - bVal : bVal - aVal;
|
|
376
|
+
});
|
|
377
|
+
return sorted;
|
|
378
|
+
}, [projectUsage, projectSortKey, projectSortDir, projects]);
|
|
379
|
+
|
|
380
|
+
const getProjectName = (projectId: string | null) => {
|
|
381
|
+
if (!projectId) return "Unassigned";
|
|
382
|
+
const project = projects.find(p => p.id === projectId);
|
|
383
|
+
return project?.name || projectId;
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const getProjectColor = (projectId: string | null) => {
|
|
387
|
+
if (!projectId) return undefined;
|
|
388
|
+
return projects.find(p => p.id === projectId)?.color;
|
|
389
|
+
};
|
|
390
|
+
|
|
235
391
|
// Merge real-time events with historical, filtering and deduping
|
|
236
392
|
const allEvents = React.useMemo(() => {
|
|
237
393
|
// Apply filters to real-time events
|
|
@@ -315,7 +471,7 @@ export function TelemetryPage() {
|
|
|
315
471
|
};
|
|
316
472
|
|
|
317
473
|
const levelColors: Record<string, string> = {
|
|
318
|
-
debug: "text-[
|
|
474
|
+
debug: "text-[var(--color-text-faint)]",
|
|
319
475
|
info: "text-blue-400",
|
|
320
476
|
warn: "text-yellow-400",
|
|
321
477
|
error: "text-red-400",
|
|
@@ -380,20 +536,56 @@ export function TelemetryPage() {
|
|
|
380
536
|
: `Telemetry - ${currentProject?.name || ""}`}
|
|
381
537
|
</h1>
|
|
382
538
|
</div>
|
|
383
|
-
<p className="text-[
|
|
539
|
+
<p className="text-[var(--color-text-muted)]">
|
|
384
540
|
Monitor agent activity, token usage, and errors.
|
|
385
541
|
</p>
|
|
386
542
|
</div>
|
|
387
543
|
|
|
544
|
+
{/* Time Range Selector */}
|
|
545
|
+
<div className="flex gap-2 mb-6">
|
|
546
|
+
{[
|
|
547
|
+
{ value: "1h", label: "Last hour" },
|
|
548
|
+
{ value: "6h", label: "Last 6h" },
|
|
549
|
+
{ value: "24h", label: "Last 24h" },
|
|
550
|
+
{ value: "7d", label: "Last 7 days" },
|
|
551
|
+
{ value: "30d", label: "Last 30 days" },
|
|
552
|
+
{ value: "all", label: "All time" },
|
|
553
|
+
].map(opt => (
|
|
554
|
+
<button
|
|
555
|
+
key={opt.value}
|
|
556
|
+
onClick={() => setTimeRange(opt.value)}
|
|
557
|
+
className={`px-3 py-1.5 rounded text-sm transition ${
|
|
558
|
+
timeRange === opt.value
|
|
559
|
+
? "bg-[var(--color-accent)] text-black"
|
|
560
|
+
: "bg-[var(--color-surface-raised)] hover:bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)]"
|
|
561
|
+
}`}
|
|
562
|
+
>
|
|
563
|
+
{opt.label}
|
|
564
|
+
</button>
|
|
565
|
+
))}
|
|
566
|
+
</div>
|
|
567
|
+
|
|
388
568
|
{/* Stats Cards */}
|
|
389
569
|
{stats && (
|
|
390
|
-
<div className="
|
|
570
|
+
<div className="flex flex-wrap gap-4 mb-6">
|
|
391
571
|
<StatCard label="Events" value={formatNumber(stats.total_events)} />
|
|
392
572
|
<StatCard label="LLM Calls" value={formatNumber(stats.total_llm_calls)} />
|
|
393
573
|
<StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />
|
|
394
574
|
<StatCard label="Errors" value={formatNumber(stats.total_errors)} color="red" />
|
|
395
575
|
<StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
|
|
396
576
|
<StatCard label="Output Tokens" value={formatNumber(stats.total_output_tokens)} />
|
|
577
|
+
{(stats.total_cache_creation_tokens > 0 || stats.total_cache_read_tokens > 0) && (
|
|
578
|
+
<>
|
|
579
|
+
<StatCard label="Cache Write" value={formatNumber(stats.total_cache_creation_tokens)} />
|
|
580
|
+
<StatCard label="Cache Read" value={formatNumber(stats.total_cache_read_tokens)} />
|
|
581
|
+
</>
|
|
582
|
+
)}
|
|
583
|
+
{stats.total_reasoning_tokens > 0 && (
|
|
584
|
+
<StatCard label="Reasoning" value={formatNumber(stats.total_reasoning_tokens)} />
|
|
585
|
+
)}
|
|
586
|
+
{costTrackingEnabled && (
|
|
587
|
+
<StatCard label="Total Cost" value={`$${stats.total_cost.toFixed(4)}`} color="orange" />
|
|
588
|
+
)}
|
|
397
589
|
</div>
|
|
398
590
|
)}
|
|
399
591
|
|
|
@@ -427,15 +619,15 @@ export function TelemetryPage() {
|
|
|
427
619
|
return (
|
|
428
620
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
|
429
621
|
{/* Activity Chart */}
|
|
430
|
-
<div className="bg-[
|
|
431
|
-
<h3 className="text-sm font-medium text-[
|
|
622
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
623
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Activity</h3>
|
|
432
624
|
<ResponsiveContainer width="100%" height={200}>
|
|
433
625
|
<AreaChart data={chartData}>
|
|
434
|
-
<CartesianGrid strokeDasharray="3 3" stroke="
|
|
626
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
|
|
435
627
|
<XAxis
|
|
436
628
|
dataKey="date"
|
|
437
|
-
stroke="
|
|
438
|
-
tick={{ fill: "
|
|
629
|
+
stroke="var(--color-border-light)"
|
|
630
|
+
tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
|
|
439
631
|
tickFormatter={(v) => {
|
|
440
632
|
if (!useDaily && v.includes(" ")) {
|
|
441
633
|
return v.split(" ")[1];
|
|
@@ -444,15 +636,15 @@ export function TelemetryPage() {
|
|
|
444
636
|
return `${d.getMonth() + 1}/${d.getDate()}`;
|
|
445
637
|
}}
|
|
446
638
|
/>
|
|
447
|
-
<YAxis stroke="
|
|
639
|
+
<YAxis stroke="var(--color-border-light)" tick={{ fill: "var(--color-text-muted)", fontSize: 11 }} allowDecimals={false} />
|
|
448
640
|
<Tooltip
|
|
449
641
|
contentStyle={{
|
|
450
|
-
backgroundColor: "
|
|
451
|
-
border: "1px solid
|
|
642
|
+
backgroundColor: "var(--color-surface)",
|
|
643
|
+
border: "1px solid var(--color-border-light)",
|
|
452
644
|
borderRadius: "8px",
|
|
453
645
|
fontSize: 12,
|
|
454
646
|
}}
|
|
455
|
-
labelStyle={{ color: "
|
|
647
|
+
labelStyle={{ color: "var(--color-text-secondary)" }}
|
|
456
648
|
cursor={{ stroke: "rgba(255,255,255,0.1)" }}
|
|
457
649
|
labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
|
|
458
650
|
/>
|
|
@@ -465,19 +657,21 @@ export function TelemetryPage() {
|
|
|
465
657
|
type="monotone"
|
|
466
658
|
dataKey="llm_calls"
|
|
467
659
|
name="LLM Calls"
|
|
468
|
-
stroke="
|
|
469
|
-
fill="
|
|
660
|
+
stroke="var(--color-accent)"
|
|
661
|
+
fill="var(--color-accent)"
|
|
470
662
|
fillOpacity={0.15}
|
|
471
663
|
strokeWidth={1.5}
|
|
664
|
+
|
|
472
665
|
/>
|
|
473
666
|
<Area
|
|
474
667
|
type="monotone"
|
|
475
668
|
dataKey="tool_calls"
|
|
476
669
|
name="Tool Calls"
|
|
477
|
-
stroke="
|
|
478
|
-
fill="
|
|
670
|
+
stroke="var(--color-accent-hover)"
|
|
671
|
+
fill="var(--color-accent-hover)"
|
|
479
672
|
fillOpacity={0.08}
|
|
480
673
|
strokeWidth={1.5}
|
|
674
|
+
|
|
481
675
|
/>
|
|
482
676
|
<Area
|
|
483
677
|
type="monotone"
|
|
@@ -487,21 +681,22 @@ export function TelemetryPage() {
|
|
|
487
681
|
fill="#ef4444"
|
|
488
682
|
fillOpacity={0.1}
|
|
489
683
|
strokeWidth={1.5}
|
|
684
|
+
|
|
490
685
|
/>
|
|
491
686
|
</AreaChart>
|
|
492
687
|
</ResponsiveContainer>
|
|
493
688
|
</div>
|
|
494
689
|
|
|
495
690
|
{/* Token Usage Chart */}
|
|
496
|
-
<div className="bg-[
|
|
497
|
-
<h3 className="text-sm font-medium text-[
|
|
691
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
692
|
+
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
|
|
498
693
|
<ResponsiveContainer width="100%" height={200}>
|
|
499
694
|
<BarChart data={chartData}>
|
|
500
|
-
<CartesianGrid strokeDasharray="3 3" stroke="
|
|
695
|
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--color-border)" />
|
|
501
696
|
<XAxis
|
|
502
697
|
dataKey="date"
|
|
503
|
-
stroke="
|
|
504
|
-
tick={{ fill: "
|
|
698
|
+
stroke="var(--color-border-light)"
|
|
699
|
+
tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
|
|
505
700
|
tickFormatter={(v) => {
|
|
506
701
|
if (!useDaily && v.includes(" ")) {
|
|
507
702
|
return v.split(" ")[1];
|
|
@@ -511,8 +706,8 @@ export function TelemetryPage() {
|
|
|
511
706
|
}}
|
|
512
707
|
/>
|
|
513
708
|
<YAxis
|
|
514
|
-
stroke="
|
|
515
|
-
tick={{ fill: "
|
|
709
|
+
stroke="var(--color-border-light)"
|
|
710
|
+
tick={{ fill: "var(--color-text-muted)", fontSize: 11 }}
|
|
516
711
|
tickFormatter={(v) => {
|
|
517
712
|
if (v >= 1000000) return `${(v / 1000000).toFixed(1)}M`;
|
|
518
713
|
if (v >= 1000) return `${(v / 1000).toFixed(0)}K`;
|
|
@@ -521,12 +716,12 @@ export function TelemetryPage() {
|
|
|
521
716
|
/>
|
|
522
717
|
<Tooltip
|
|
523
718
|
contentStyle={{
|
|
524
|
-
backgroundColor: "
|
|
525
|
-
border: "1px solid
|
|
719
|
+
backgroundColor: "var(--color-surface)",
|
|
720
|
+
border: "1px solid var(--color-border-light)",
|
|
526
721
|
borderRadius: "8px",
|
|
527
722
|
fontSize: 12,
|
|
528
723
|
}}
|
|
529
|
-
labelStyle={{ color: "
|
|
724
|
+
labelStyle={{ color: "var(--color-text-secondary)" }}
|
|
530
725
|
cursor={{ fill: "rgba(255,255,255,0.03)" }}
|
|
531
726
|
labelFormatter={(v) => useDaily ? new Date(v + "T00:00:00").toLocaleDateString() : v}
|
|
532
727
|
formatter={(value: number) => [value.toLocaleString(), undefined]}
|
|
@@ -539,14 +734,16 @@ export function TelemetryPage() {
|
|
|
539
734
|
<Bar
|
|
540
735
|
dataKey="input_tokens"
|
|
541
736
|
name="Input Tokens"
|
|
542
|
-
fill="
|
|
737
|
+
fill="var(--color-accent)"
|
|
543
738
|
radius={[2, 2, 0, 0]}
|
|
739
|
+
|
|
544
740
|
/>
|
|
545
741
|
<Bar
|
|
546
742
|
dataKey="output_tokens"
|
|
547
743
|
name="Output Tokens"
|
|
548
|
-
fill="
|
|
744
|
+
fill="var(--color-accent-hover)"
|
|
549
745
|
radius={[2, 2, 0, 0]}
|
|
746
|
+
|
|
550
747
|
/>
|
|
551
748
|
</BarChart>
|
|
552
749
|
</ResponsiveContainer>
|
|
@@ -556,43 +753,186 @@ export function TelemetryPage() {
|
|
|
556
753
|
})()}
|
|
557
754
|
|
|
558
755
|
{/* Usage by Agent */}
|
|
559
|
-
{usage.length > 0 && (
|
|
756
|
+
{usage.length > 0 && (() => {
|
|
757
|
+
const maxCost = Math.max(...sortedUsage.map(u => u.cost), 0.0001);
|
|
758
|
+
const hasCacheTokens = sortedUsage.some(u => (u.cache_creation_tokens || 0) > 0 || (u.cache_read_tokens || 0) > 0);
|
|
759
|
+
const hasReasoningTokens = sortedUsage.some(u => (u.reasoning_tokens || 0) > 0);
|
|
760
|
+
const SortHeader = ({ label, field, align = "right" }: { label: string; field: SortKey; align?: string }) => (
|
|
761
|
+
<th
|
|
762
|
+
className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
|
|
763
|
+
onClick={() => handleSort(field)}
|
|
764
|
+
>
|
|
765
|
+
<span className="inline-flex items-center gap-1">
|
|
766
|
+
{align === "right" && sortKey === field && (
|
|
767
|
+
<span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
|
|
768
|
+
)}
|
|
769
|
+
{label}
|
|
770
|
+
{align === "left" && sortKey === field && (
|
|
771
|
+
<span className="text-orange-400">{sortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
|
|
772
|
+
)}
|
|
773
|
+
</span>
|
|
774
|
+
</th>
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
return (
|
|
560
778
|
<div className="mb-6">
|
|
561
779
|
<h2 className="text-lg font-medium mb-3">Usage by Agent</h2>
|
|
562
|
-
<div className="bg-[
|
|
780
|
+
<div className="bg-[var(--color-surface)] card overflow-hidden">
|
|
563
781
|
<table className="w-full text-sm">
|
|
564
782
|
<thead>
|
|
565
|
-
<tr className="border-b border-[
|
|
566
|
-
<
|
|
567
|
-
<
|
|
568
|
-
<
|
|
569
|
-
<
|
|
570
|
-
<
|
|
571
|
-
<
|
|
783
|
+
<tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
|
|
784
|
+
<SortHeader label="Agent" field="agent" align="left" />
|
|
785
|
+
<SortHeader label="LLM Calls" field="llm_calls" />
|
|
786
|
+
<SortHeader label="Tool Calls" field="tool_calls" />
|
|
787
|
+
<SortHeader label="Input Tokens" field="input_tokens" />
|
|
788
|
+
<SortHeader label="Output Tokens" field="output_tokens" />
|
|
789
|
+
{hasCacheTokens && <SortHeader label="Cache Write" field="cache_creation_tokens" />}
|
|
790
|
+
{hasCacheTokens && <SortHeader label="Cache Read" field="cache_read_tokens" />}
|
|
791
|
+
{hasReasoningTokens && <SortHeader label="Reasoning" field="reasoning_tokens" />}
|
|
792
|
+
<SortHeader label="Errors" field="errors" />
|
|
793
|
+
{costTrackingEnabled && <SortHeader label="Est. Cost" field="cost" />}
|
|
572
794
|
</tr>
|
|
573
795
|
</thead>
|
|
574
796
|
<tbody>
|
|
575
|
-
{
|
|
576
|
-
<tr key={u.agent_id} className="border-b border-[
|
|
797
|
+
{sortedUsage.map((u) => (
|
|
798
|
+
<tr key={u.agent_id} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
|
|
577
799
|
<td className="p-3 font-medium">{getAgentName(u.agent_id)}</td>
|
|
578
|
-
<td className="p-3 text-right text-[
|
|
579
|
-
<td className="p-3 text-right text-[
|
|
580
|
-
<td className="p-3 text-right text-[
|
|
581
|
-
<td className="p-3 text-right text-[
|
|
800
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
|
|
801
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
|
|
802
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
|
|
803
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
|
|
804
|
+
{hasCacheTokens && (
|
|
805
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_creation_tokens || 0)}</td>
|
|
806
|
+
)}
|
|
807
|
+
{hasCacheTokens && (
|
|
808
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_read_tokens || 0)}</td>
|
|
809
|
+
)}
|
|
810
|
+
{hasReasoningTokens && (
|
|
811
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.reasoning_tokens || 0)}</td>
|
|
812
|
+
)}
|
|
582
813
|
<td className="p-3 text-right">
|
|
583
814
|
{u.errors > 0 ? (
|
|
584
815
|
<span className="text-red-400">{u.errors}</span>
|
|
585
816
|
) : (
|
|
586
|
-
<span className="text-[
|
|
817
|
+
<span className="text-[var(--color-text-faint)]">0</span>
|
|
587
818
|
)}
|
|
588
819
|
</td>
|
|
820
|
+
{costTrackingEnabled && (
|
|
821
|
+
<td className="p-3 text-right">
|
|
822
|
+
<div className="flex items-center justify-end gap-2">
|
|
823
|
+
<div className="w-16 h-1.5 bg-[var(--color-surface-raised)] rounded-full overflow-hidden">
|
|
824
|
+
<div
|
|
825
|
+
className="h-full bg-orange-500 rounded-full"
|
|
826
|
+
style={{ width: `${(u.cost / maxCost) * 100}%` }}
|
|
827
|
+
/>
|
|
828
|
+
</div>
|
|
829
|
+
<span className="text-[var(--color-text-secondary)] min-w-[60px] text-right">${u.cost.toFixed(4)}</span>
|
|
830
|
+
</div>
|
|
831
|
+
</td>
|
|
832
|
+
)}
|
|
589
833
|
</tr>
|
|
590
834
|
))}
|
|
591
835
|
</tbody>
|
|
592
836
|
</table>
|
|
593
837
|
</div>
|
|
594
838
|
</div>
|
|
595
|
-
|
|
839
|
+
);
|
|
840
|
+
})()}
|
|
841
|
+
|
|
842
|
+
{/* Usage by Project */}
|
|
843
|
+
{projectsEnabled && currentProjectId === null && sortedProjectUsage.length > 0 && (() => {
|
|
844
|
+
const maxCost = Math.max(...sortedProjectUsage.map(u => u.cost), 0.0001);
|
|
845
|
+
const hasProjCacheTokens = sortedProjectUsage.some(u => (u.cache_creation_tokens || 0) > 0 || (u.cache_read_tokens || 0) > 0);
|
|
846
|
+
const hasProjReasoningTokens = sortedProjectUsage.some(u => (u.reasoning_tokens || 0) > 0);
|
|
847
|
+
const PSortHeader = ({ label, field, align = "right" }: { label: string; field: ProjectSortKey; align?: string }) => (
|
|
848
|
+
<th
|
|
849
|
+
className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
|
|
850
|
+
onClick={() => handleProjectSort(field)}
|
|
851
|
+
>
|
|
852
|
+
<span className="inline-flex items-center gap-1">
|
|
853
|
+
{align === "right" && projectSortKey === field && (
|
|
854
|
+
<span className="text-orange-400">{projectSortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
|
|
855
|
+
)}
|
|
856
|
+
{label}
|
|
857
|
+
{align === "left" && projectSortKey === field && (
|
|
858
|
+
<span className="text-orange-400">{projectSortDir === "asc" ? "\u25b2" : "\u25bc"}</span>
|
|
859
|
+
)}
|
|
860
|
+
</span>
|
|
861
|
+
</th>
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
return (
|
|
865
|
+
<div className="mb-6">
|
|
866
|
+
<h2 className="text-lg font-medium mb-3">Usage by Project</h2>
|
|
867
|
+
<div className="bg-[var(--color-surface)] card overflow-hidden">
|
|
868
|
+
<table className="w-full text-sm">
|
|
869
|
+
<thead>
|
|
870
|
+
<tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
|
|
871
|
+
<PSortHeader label="Project" field="project" align="left" />
|
|
872
|
+
<PSortHeader label="LLM Calls" field="llm_calls" />
|
|
873
|
+
<PSortHeader label="Tool Calls" field="tool_calls" />
|
|
874
|
+
<PSortHeader label="Input Tokens" field="input_tokens" />
|
|
875
|
+
<PSortHeader label="Output Tokens" field="output_tokens" />
|
|
876
|
+
{hasProjCacheTokens && <PSortHeader label="Cache Write" field="cache_creation_tokens" />}
|
|
877
|
+
{hasProjCacheTokens && <PSortHeader label="Cache Read" field="cache_read_tokens" />}
|
|
878
|
+
{hasProjReasoningTokens && <PSortHeader label="Reasoning" field="reasoning_tokens" />}
|
|
879
|
+
<PSortHeader label="Errors" field="errors" />
|
|
880
|
+
{costTrackingEnabled && <PSortHeader label="Est. Cost" field="cost" />}
|
|
881
|
+
</tr>
|
|
882
|
+
</thead>
|
|
883
|
+
<tbody>
|
|
884
|
+
{sortedProjectUsage.map((u) => {
|
|
885
|
+
const color = getProjectColor(u.project_id);
|
|
886
|
+
return (
|
|
887
|
+
<tr key={u.project_id || "_unassigned"} className="border-b border-[var(--color-border)] last:border-0 hover:bg-[var(--color-bg)]">
|
|
888
|
+
<td className="p-3 font-medium">
|
|
889
|
+
<span className="inline-flex items-center gap-2">
|
|
890
|
+
{color && <span className="w-2.5 h-2.5 rounded-full flex-shrink-0" style={{ backgroundColor: color }} />}
|
|
891
|
+
{getProjectName(u.project_id)}
|
|
892
|
+
</span>
|
|
893
|
+
</td>
|
|
894
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.llm_calls)}</td>
|
|
895
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
|
|
896
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
|
|
897
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.output_tokens)}</td>
|
|
898
|
+
{hasProjCacheTokens && (
|
|
899
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_creation_tokens || 0)}</td>
|
|
900
|
+
)}
|
|
901
|
+
{hasProjCacheTokens && (
|
|
902
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.cache_read_tokens || 0)}</td>
|
|
903
|
+
)}
|
|
904
|
+
{hasProjReasoningTokens && (
|
|
905
|
+
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.reasoning_tokens || 0)}</td>
|
|
906
|
+
)}
|
|
907
|
+
<td className="p-3 text-right">
|
|
908
|
+
{u.errors > 0 ? (
|
|
909
|
+
<span className="text-red-400">{u.errors}</span>
|
|
910
|
+
) : (
|
|
911
|
+
<span className="text-[var(--color-text-faint)]">0</span>
|
|
912
|
+
)}
|
|
913
|
+
</td>
|
|
914
|
+
{costTrackingEnabled && (
|
|
915
|
+
<td className="p-3 text-right">
|
|
916
|
+
<div className="flex items-center justify-end gap-2">
|
|
917
|
+
<div className="w-16 h-1.5 bg-[var(--color-surface-raised)] rounded-full overflow-hidden">
|
|
918
|
+
<div
|
|
919
|
+
className="h-full bg-orange-500 rounded-full"
|
|
920
|
+
style={{ width: `${(u.cost / maxCost) * 100}%` }}
|
|
921
|
+
/>
|
|
922
|
+
</div>
|
|
923
|
+
<span className="text-[var(--color-text-secondary)] min-w-[60px] text-right">${u.cost.toFixed(4)}</span>
|
|
924
|
+
</div>
|
|
925
|
+
</td>
|
|
926
|
+
)}
|
|
927
|
+
</tr>
|
|
928
|
+
);
|
|
929
|
+
})}
|
|
930
|
+
</tbody>
|
|
931
|
+
</table>
|
|
932
|
+
</div>
|
|
933
|
+
</div>
|
|
934
|
+
);
|
|
935
|
+
})()}
|
|
596
936
|
|
|
597
937
|
{/* Filters */}
|
|
598
938
|
<div className="flex flex-wrap items-center gap-3 mb-4">
|
|
@@ -608,14 +948,14 @@ export function TelemetryPage() {
|
|
|
608
948
|
<div className="flex flex-wrap items-center gap-1.5 flex-1">
|
|
609
949
|
{allCategories.map((cat) => {
|
|
610
950
|
const isHidden = hiddenCategories.has(cat);
|
|
611
|
-
const colorClass = categoryColors[cat] || "bg-[
|
|
951
|
+
const colorClass = categoryColors[cat] || "bg-[var(--color-surface-raised)] text-[var(--color-text-secondary)] border-[var(--color-border-light)]";
|
|
612
952
|
return (
|
|
613
953
|
<button
|
|
614
954
|
key={cat}
|
|
615
955
|
onClick={() => toggleCategory(cat)}
|
|
616
956
|
className={`px-2 py-0.5 rounded text-xs border transition-all ${
|
|
617
957
|
isHidden
|
|
618
|
-
? "bg-[
|
|
958
|
+
? "bg-[var(--color-surface-raised)] text-[var(--color-text-faint)] border-[var(--color-border-light)] opacity-50"
|
|
619
959
|
: colorClass
|
|
620
960
|
}`}
|
|
621
961
|
>
|
|
@@ -635,7 +975,7 @@ export function TelemetryPage() {
|
|
|
635
975
|
</div>
|
|
636
976
|
<button
|
|
637
977
|
onClick={fetchData}
|
|
638
|
-
className="px-3 py-2 bg-[
|
|
978
|
+
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"
|
|
639
979
|
>
|
|
640
980
|
Refresh
|
|
641
981
|
</button>
|
|
@@ -643,31 +983,31 @@ export function TelemetryPage() {
|
|
|
643
983
|
</div>
|
|
644
984
|
|
|
645
985
|
{/* Events List */}
|
|
646
|
-
<div className="bg-[
|
|
647
|
-
<div className="p-3 border-b border-[
|
|
986
|
+
<div className="bg-[var(--color-surface)] card">
|
|
987
|
+
<div className="p-3 border-b border-[var(--color-border)] flex items-center justify-between">
|
|
648
988
|
<h2 className="font-medium">Recent Events</h2>
|
|
649
989
|
{realtimeEvents.length > 0 && (
|
|
650
|
-
<span className="text-xs text-[
|
|
990
|
+
<span className="text-xs text-[var(--color-text-muted)]">
|
|
651
991
|
{realtimeEvents.length} new
|
|
652
992
|
</span>
|
|
653
993
|
)}
|
|
654
994
|
</div>
|
|
655
995
|
|
|
656
996
|
{loading && allEvents.length === 0 ? (
|
|
657
|
-
<div className="p-8 text-center text-[
|
|
997
|
+
<div className="p-8 text-center text-[var(--color-text-muted)]">Loading...</div>
|
|
658
998
|
) : allEvents.length === 0 ? (
|
|
659
|
-
<div className="p-8 text-center text-[
|
|
999
|
+
<div className="p-8 text-center text-[var(--color-text-muted)]">
|
|
660
1000
|
No telemetry events yet. Events will appear here in real-time once agents start sending data.
|
|
661
1001
|
</div>
|
|
662
1002
|
) : (
|
|
663
|
-
<div className="divide-y divide-[
|
|
1003
|
+
<div className="divide-y divide-[var(--color-border)]">
|
|
664
1004
|
{allEvents.map((event) => {
|
|
665
1005
|
const isNew = newEventIds.has(event.id);
|
|
666
1006
|
|
|
667
1007
|
return (
|
|
668
1008
|
<div
|
|
669
1009
|
key={event.id}
|
|
670
|
-
className={`p-3 hover:bg-[
|
|
1010
|
+
className={`p-3 hover:bg-[var(--color-bg)] cursor-pointer transition-all duration-500 ${
|
|
671
1011
|
isNew ? "bg-green-500/5" : ""
|
|
672
1012
|
}`}
|
|
673
1013
|
style={{
|
|
@@ -676,17 +1016,17 @@ export function TelemetryPage() {
|
|
|
676
1016
|
onClick={() => setExpandedEvent(expandedEvent === event.id ? null : event.id)}
|
|
677
1017
|
>
|
|
678
1018
|
<div className="flex items-start gap-3">
|
|
679
|
-
<span className={`px-2 py-0.5 rounded text-xs border transition-colors duration-300 ${categoryColors[event.category] || "bg-[
|
|
1019
|
+
<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)]"}`}>
|
|
680
1020
|
{event.category}
|
|
681
1021
|
</span>
|
|
682
1022
|
<div className="flex-1 min-w-0">
|
|
683
1023
|
<div className="flex items-center gap-2">
|
|
684
1024
|
<span className="font-medium text-sm">{event.type}</span>
|
|
685
|
-
<span className={`text-xs ${levelColors[event.level] || "text-[
|
|
1025
|
+
<span className={`text-xs ${levelColors[event.level] || "text-[var(--color-text-muted)]"}`}>
|
|
686
1026
|
{event.level}
|
|
687
1027
|
</span>
|
|
688
1028
|
{event.duration_ms && (
|
|
689
|
-
<span className="text-xs text-[
|
|
1029
|
+
<span className="text-xs text-[var(--color-text-faint)]">{event.duration_ms}ms</span>
|
|
690
1030
|
)}
|
|
691
1031
|
<span
|
|
692
1032
|
className={`w-1.5 h-1.5 rounded-full bg-green-400 transition-opacity duration-1000 ${
|
|
@@ -694,14 +1034,14 @@ export function TelemetryPage() {
|
|
|
694
1034
|
}`}
|
|
695
1035
|
/>
|
|
696
1036
|
</div>
|
|
697
|
-
<div className="text-xs text-[
|
|
1037
|
+
<div className="text-xs text-[var(--color-text-faint)] mt-1">
|
|
698
1038
|
{getAgentName(event.agent_id)} · {new Date(event.timestamp).toLocaleString()}
|
|
699
1039
|
</div>
|
|
700
1040
|
{event.error && (
|
|
701
1041
|
<div className="text-xs text-red-400 mt-1 font-mono">{event.error}</div>
|
|
702
1042
|
)}
|
|
703
1043
|
{expandedEvent === event.id && event.data && Object.keys(event.data).length > 0 && (
|
|
704
|
-
<pre className="text-xs text-[
|
|
1044
|
+
<pre className="text-xs text-[var(--color-text-muted)] mt-2 p-2 bg-[var(--color-bg)] rounded overflow-x-auto">
|
|
705
1045
|
{JSON.stringify(event.data, null, 2)}
|
|
706
1046
|
</pre>
|
|
707
1047
|
)}
|
|
@@ -720,9 +1060,9 @@ export function TelemetryPage() {
|
|
|
720
1060
|
|
|
721
1061
|
function StatCard({ label, value, color }: { label: string; value: string; color?: string }) {
|
|
722
1062
|
return (
|
|
723
|
-
<div className="bg-[
|
|
724
|
-
<div className="text-[
|
|
725
|
-
<div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : ""}`}>
|
|
1063
|
+
<div className="bg-[var(--color-surface)] card p-4 flex-1 min-w-[120px]">
|
|
1064
|
+
<div className="text-[var(--color-text-muted)] text-xs mb-1">{label}</div>
|
|
1065
|
+
<div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : color === "orange" ? "text-orange-400" : ""}`}>
|
|
726
1066
|
{value}
|
|
727
1067
|
</div>
|
|
728
1068
|
</div>
|