apteva 0.4.44 → 0.4.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ActivityPage.c48n83h2.js → ActivityPage.sw9p594m.js} +1 -1
- package/dist/{ApiDocsPage.yzcxx5ax.js → ApiDocsPage.90e03bz7.js} +1 -1
- package/dist/App.0ws87fpx.js +53 -0
- package/dist/App.3vnrera5.js +4 -0
- package/dist/App.94x6mh7f.js +20 -0
- package/dist/{App.qzbx5wtj.js → App.9sryp183.js} +1 -1
- package/dist/App.d9tny4t0.js +221 -0
- package/dist/{App.r5serxkt.js → App.jhb45d7r.js} +1 -1
- package/dist/App.p7jjw1zf.js +4 -0
- package/dist/App.pfbdzrhh.js +4 -0
- package/dist/App.stgng5bx.js +13 -0
- package/dist/{App.152mbs1r.js → App.tm3k7h4b.js} +1 -1
- package/dist/App.vkg121c6.js +4 -0
- package/dist/App.wghtdzsk.js +1 -0
- package/dist/App.xf7wsckg.js +4 -0
- package/dist/App.xva0tfzh.js +4 -0
- package/dist/App.ysxy7akk.js +61 -0
- package/dist/App.yzkh4gq2.js +4 -0
- package/dist/ConnectionsPage.q5f9fd37.js +3 -0
- package/dist/McpPage.f3ccrezb.js +3 -0
- package/dist/SettingsPage.3sqx6wm4.js +3 -0
- package/dist/SkillsPage.whxnez67.js +3 -0
- package/dist/TasksPage.zp4jfevw.js +3 -0
- package/dist/TelemetryPage.a9fmxq87.js +3 -0
- package/dist/TestsPage.18krj0d1.js +3 -0
- package/dist/ThreadsPage.nnphgy98.js +3 -0
- package/dist/apteva-kit.css +1 -1
- package/dist/index.html +1 -1
- package/dist/styles.css +1 -1
- package/package.json +5 -4
- package/src/db.ts +42 -4
- package/src/providers.ts +14 -9
- package/src/routes/api/agent-utils.ts +25 -3
- package/src/routes/api/telemetry.ts +20 -2
- package/src/server.ts +52 -1
- package/src/web/App.tsx +1 -1
- package/src/web/components/agents/AgentCard.tsx +9 -7
- package/src/web/components/agents/AgentPanel.tsx +205 -44
- package/src/web/components/agents/CreateAgentModal.tsx +5 -5
- package/src/web/components/auth/LoginPage.tsx +2 -2
- package/src/web/components/common/LoadingSpinner.tsx +1 -1
- package/src/web/components/common/Modal.tsx +6 -6
- package/src/web/components/common/Select.tsx +2 -2
- package/src/web/components/connections/ConnectionsPage.tsx +1 -1
- package/src/web/components/connections/IntegrationsTab.tsx +3 -3
- package/src/web/components/connections/OverviewTab.tsx +3 -3
- package/src/web/components/connections/TriggersTab.tsx +8 -8
- package/src/web/components/dashboard/Dashboard.tsx +2 -2
- package/src/web/components/layout/Header.tsx +3 -3
- package/src/web/components/layout/Sidebar.tsx +3 -2
- package/src/web/components/mcp/McpPage.tsx +13 -13
- package/src/web/components/onboarding/OnboardingWizard.tsx +2 -2
- package/src/web/components/settings/SettingsPage.tsx +55 -22
- package/src/web/components/skills/SkillsPage.tsx +7 -7
- package/src/web/components/tasks/TasksPage.tsx +212 -36
- package/src/web/components/telemetry/TelemetryPage.tsx +278 -9
- package/src/web/components/tests/TestsPage.tsx +2 -2
- package/src/web/components/threads/ThreadsPage.tsx +2 -2
- package/src/web/context/ThemeContext.tsx +31 -10
- package/src/web/index.html +1 -6
- package/src/web/styles.css +47 -0
- package/src/web/themes.ts +68 -5
- package/dist/App.09yb8t0b.js +0 -1
- package/dist/App.3a67nx9w.js +0 -4
- package/dist/App.9epx6785.js +0 -4
- package/dist/App.d8955awp.js +0 -4
- package/dist/App.drwb57jq.js +0 -4
- package/dist/App.gssbmajb.js +0 -4
- package/dist/App.qw70pc29.js +0 -53
- package/dist/App.tpmp9020.js +0 -20
- package/dist/App.v2wb4d7d.js +0 -61
- package/dist/App.vxmaaj0m.js +0 -13
- package/dist/App.w4p2tda9.js +0 -4
- package/dist/App.wv2ng55q.js +0 -221
- package/dist/App.yncnrn0f.js +0 -4
- package/dist/ConnectionsPage.k6cspyqq.js +0 -3
- package/dist/McpPage.cdxm48xj.js +0 -3
- package/dist/SettingsPage.evpv7c2y.js +0 -3
- package/dist/SkillsPage.pvzp6c1a.js +0 -3
- package/dist/TasksPage.6jnvbpsy.js +0 -3
- package/dist/TelemetryPage.t7vk24zc.js +0 -3
- package/dist/TestsPage.5x6658aa.js +0 -3
- package/dist/ThreadsPage.3fvhtevh.js +0 -3
|
@@ -13,6 +13,9 @@ 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;
|
|
16
19
|
total_cost: number;
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -20,6 +23,22 @@ interface UsageByAgent {
|
|
|
20
23
|
agent_id: string;
|
|
21
24
|
input_tokens: number;
|
|
22
25
|
output_tokens: number;
|
|
26
|
+
cache_creation_tokens: number;
|
|
27
|
+
cache_read_tokens: number;
|
|
28
|
+
reasoning_tokens: number;
|
|
29
|
+
llm_calls: number;
|
|
30
|
+
tool_calls: number;
|
|
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;
|
|
23
42
|
llm_calls: number;
|
|
24
43
|
tool_calls: number;
|
|
25
44
|
errors: number;
|
|
@@ -42,12 +61,18 @@ function extractEventStats(event: TelemetryEvent): {
|
|
|
42
61
|
errors: number;
|
|
43
62
|
input_tokens: number;
|
|
44
63
|
output_tokens: number;
|
|
64
|
+
cache_creation_tokens: number;
|
|
65
|
+
cache_read_tokens: number;
|
|
66
|
+
reasoning_tokens: number;
|
|
45
67
|
} {
|
|
46
68
|
const isLlm = event.category === "LLM";
|
|
47
69
|
const isTool = event.category === "TOOL";
|
|
48
70
|
const isError = event.level === "error";
|
|
49
71
|
const inputTokens = (event.data?.input_tokens as number) || 0;
|
|
50
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;
|
|
51
76
|
|
|
52
77
|
return {
|
|
53
78
|
llm_calls: isLlm ? 1 : 0,
|
|
@@ -55,17 +80,21 @@ function extractEventStats(event: TelemetryEvent): {
|
|
|
55
80
|
errors: isError ? 1 : 0,
|
|
56
81
|
input_tokens: inputTokens,
|
|
57
82
|
output_tokens: outputTokens,
|
|
83
|
+
cache_creation_tokens: cacheCreationTokens,
|
|
84
|
+
cache_read_tokens: cacheReadTokens,
|
|
85
|
+
reasoning_tokens: reasoningTokens,
|
|
58
86
|
};
|
|
59
87
|
}
|
|
60
88
|
|
|
61
89
|
export function TelemetryPage() {
|
|
62
90
|
const { events: realtimeEvents, statusChangeCounter } = useTelemetryContext();
|
|
63
|
-
const { currentProjectId, currentProject, costTrackingEnabled } = useProjects();
|
|
91
|
+
const { currentProjectId, currentProject, costTrackingEnabled, projectsEnabled, projects } = useProjects();
|
|
64
92
|
const { authFetch } = useAuth();
|
|
65
93
|
const [fetchedStats, setFetchedStats] = useState<TelemetryStats | null>(null);
|
|
66
94
|
const [historicalEvents, setHistoricalEvents] = useState<TelemetryEvent[]>([]);
|
|
67
95
|
const [fetchedUsage, setFetchedUsage] = useState<UsageByAgent[]>([]);
|
|
68
96
|
const [dailyUsage, setDailyUsage] = useState<DailyUsage[]>([]);
|
|
97
|
+
const [projectUsage, setProjectUsage] = useState<UsageByProject[]>([]);
|
|
69
98
|
const [loading, setLoading] = useState(true);
|
|
70
99
|
const [filter, setFilter] = useState({
|
|
71
100
|
level: "",
|
|
@@ -76,8 +105,11 @@ export function TelemetryPage() {
|
|
|
76
105
|
const [agents, setAgents] = useState<Array<{ id: string; name: string; projectId: string | null }>>([]);
|
|
77
106
|
const [expandedEvent, setExpandedEvent] = useState<string | null>(null);
|
|
78
107
|
|
|
108
|
+
// Time range filter
|
|
109
|
+
const [timeRange, setTimeRange] = useState<string>("all");
|
|
110
|
+
|
|
79
111
|
// Sort state for usage table
|
|
80
|
-
type SortKey = "agent" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "errors" | "cost";
|
|
112
|
+
type SortKey = "agent" | "llm_calls" | "tool_calls" | "input_tokens" | "output_tokens" | "cache_creation_tokens" | "cache_read_tokens" | "reasoning_tokens" | "errors" | "cost";
|
|
81
113
|
const [sortKey, setSortKey] = useState<SortKey>("cost");
|
|
82
114
|
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
|
83
115
|
|
|
@@ -90,6 +122,20 @@ export function TelemetryPage() {
|
|
|
90
122
|
}
|
|
91
123
|
};
|
|
92
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
|
+
|
|
93
139
|
// Track IDs that were in the fetched stats to avoid double-counting
|
|
94
140
|
const countedEventIdsRef = useRef<Set<string>>(new Set());
|
|
95
141
|
|
|
@@ -121,16 +167,35 @@ export function TelemetryPage() {
|
|
|
121
167
|
// Get agent IDs for the current project
|
|
122
168
|
const projectAgentIds = useMemo(() => new Set(filteredAgents.map(a => a.id)), [filteredAgents]);
|
|
123
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
|
+
|
|
124
187
|
// Fetch stats and historical data (less frequently now since we have real-time)
|
|
125
188
|
const fetchData = async () => {
|
|
126
189
|
setLoading(true);
|
|
127
190
|
try {
|
|
128
191
|
// Build project filter param
|
|
129
192
|
const projectParam = currentProjectId === "unassigned" ? "null" : currentProjectId || "";
|
|
193
|
+
const since = getSince();
|
|
130
194
|
|
|
131
195
|
// Fetch stats
|
|
132
196
|
const statsParams = new URLSearchParams();
|
|
133
197
|
if (projectParam) statsParams.set("project_id", projectParam);
|
|
198
|
+
if (since) statsParams.set("since", since);
|
|
134
199
|
const statsRes = await authFetch(`/api/telemetry/stats${statsParams.toString() ? `?${statsParams}` : ""}`);
|
|
135
200
|
const statsData = await statsRes.json();
|
|
136
201
|
setFetchedStats(statsData.stats);
|
|
@@ -140,6 +205,7 @@ export function TelemetryPage() {
|
|
|
140
205
|
if (filter.level) params.set("level", filter.level);
|
|
141
206
|
if (filter.agent_id) params.set("agent_id", filter.agent_id);
|
|
142
207
|
if (projectParam) params.set("project_id", projectParam);
|
|
208
|
+
if (since) params.set("since", since);
|
|
143
209
|
params.set("limit", "100"); // Fetch more since we filter client-side
|
|
144
210
|
|
|
145
211
|
const eventsRes = await authFetch(`/api/telemetry/events?${params}`);
|
|
@@ -154,6 +220,7 @@ export function TelemetryPage() {
|
|
|
154
220
|
const usageParams = new URLSearchParams();
|
|
155
221
|
usageParams.set("group_by", "agent");
|
|
156
222
|
if (projectParam) usageParams.set("project_id", projectParam);
|
|
223
|
+
if (since) usageParams.set("since", since);
|
|
157
224
|
const usageRes = await authFetch(`/api/telemetry/usage?${usageParams}`);
|
|
158
225
|
const usageData = await usageRes.json();
|
|
159
226
|
setFetchedUsage(usageData.usage || []);
|
|
@@ -162,6 +229,7 @@ export function TelemetryPage() {
|
|
|
162
229
|
const dailyParams = new URLSearchParams();
|
|
163
230
|
dailyParams.set("group_by", "day");
|
|
164
231
|
if (projectParam) dailyParams.set("project_id", projectParam);
|
|
232
|
+
if (since) dailyParams.set("since", since);
|
|
165
233
|
const dailyRes = await authFetch(`/api/telemetry/usage?${dailyParams}`);
|
|
166
234
|
const dailyData = await dailyRes.json();
|
|
167
235
|
// Sort by date ascending for charts
|
|
@@ -169,6 +237,18 @@ export function TelemetryPage() {
|
|
|
169
237
|
a.date.localeCompare(b.date)
|
|
170
238
|
);
|
|
171
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
|
+
}
|
|
172
252
|
} catch (e) {
|
|
173
253
|
console.error("Failed to fetch telemetry:", e);
|
|
174
254
|
}
|
|
@@ -177,7 +257,7 @@ export function TelemetryPage() {
|
|
|
177
257
|
|
|
178
258
|
useEffect(() => {
|
|
179
259
|
fetchData();
|
|
180
|
-
}, [filter, currentProjectId, authFetch, statusChangeCounter]);
|
|
260
|
+
}, [filter, timeRange, currentProjectId, authFetch, statusChangeCounter]);
|
|
181
261
|
|
|
182
262
|
// Compute real-time stats from new events (not already counted in fetched stats)
|
|
183
263
|
const stats = useMemo(() => {
|
|
@@ -190,6 +270,9 @@ export function TelemetryPage() {
|
|
|
190
270
|
let deltaErrors = 0;
|
|
191
271
|
let deltaInputTokens = 0;
|
|
192
272
|
let deltaOutputTokens = 0;
|
|
273
|
+
let deltaCacheCreationTokens = 0;
|
|
274
|
+
let deltaCacheReadTokens = 0;
|
|
275
|
+
let deltaReasoningTokens = 0;
|
|
193
276
|
|
|
194
277
|
for (const event of realtimeEvents) {
|
|
195
278
|
if (!countedEventIdsRef.current.has(event.id)) {
|
|
@@ -200,6 +283,9 @@ export function TelemetryPage() {
|
|
|
200
283
|
deltaErrors += eventStats.errors;
|
|
201
284
|
deltaInputTokens += eventStats.input_tokens;
|
|
202
285
|
deltaOutputTokens += eventStats.output_tokens;
|
|
286
|
+
deltaCacheCreationTokens += eventStats.cache_creation_tokens;
|
|
287
|
+
deltaCacheReadTokens += eventStats.cache_read_tokens;
|
|
288
|
+
deltaReasoningTokens += eventStats.reasoning_tokens;
|
|
203
289
|
}
|
|
204
290
|
}
|
|
205
291
|
|
|
@@ -210,6 +296,9 @@ export function TelemetryPage() {
|
|
|
210
296
|
total_errors: fetchedStats.total_errors + deltaErrors,
|
|
211
297
|
total_input_tokens: fetchedStats.total_input_tokens + deltaInputTokens,
|
|
212
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,
|
|
213
302
|
total_cost: fetchedStats.total_cost || 0,
|
|
214
303
|
};
|
|
215
304
|
}, [fetchedStats, realtimeEvents]);
|
|
@@ -233,6 +322,9 @@ export function TelemetryPage() {
|
|
|
233
322
|
existing.errors += eventStats.errors;
|
|
234
323
|
existing.input_tokens += eventStats.input_tokens;
|
|
235
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;
|
|
236
328
|
} else {
|
|
237
329
|
usageMap.set(event.agent_id, {
|
|
238
330
|
agent_id: event.agent_id,
|
|
@@ -241,6 +333,9 @@ export function TelemetryPage() {
|
|
|
241
333
|
errors: eventStats.errors,
|
|
242
334
|
input_tokens: eventStats.input_tokens,
|
|
243
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,
|
|
244
339
|
cost: 0,
|
|
245
340
|
});
|
|
246
341
|
}
|
|
@@ -266,6 +361,33 @@ export function TelemetryPage() {
|
|
|
266
361
|
return sorted;
|
|
267
362
|
}, [usage, sortKey, sortDir, agents]);
|
|
268
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
|
+
|
|
269
391
|
// Merge real-time events with historical, filtering and deduping
|
|
270
392
|
const allEvents = React.useMemo(() => {
|
|
271
393
|
// Apply filters to real-time events
|
|
@@ -419,15 +541,48 @@ export function TelemetryPage() {
|
|
|
419
541
|
</p>
|
|
420
542
|
</div>
|
|
421
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
|
+
|
|
422
568
|
{/* Stats Cards */}
|
|
423
569
|
{stats && (
|
|
424
|
-
<div className=
|
|
570
|
+
<div className="flex flex-wrap gap-4 mb-6">
|
|
425
571
|
<StatCard label="Events" value={formatNumber(stats.total_events)} />
|
|
426
572
|
<StatCard label="LLM Calls" value={formatNumber(stats.total_llm_calls)} />
|
|
427
573
|
<StatCard label="Tool Calls" value={formatNumber(stats.total_tool_calls)} />
|
|
428
574
|
<StatCard label="Errors" value={formatNumber(stats.total_errors)} color="red" />
|
|
429
575
|
<StatCard label="Input Tokens" value={formatNumber(stats.total_input_tokens)} />
|
|
430
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
|
+
)}
|
|
431
586
|
{costTrackingEnabled && (
|
|
432
587
|
<StatCard label="Total Cost" value={`$${stats.total_cost.toFixed(4)}`} color="orange" />
|
|
433
588
|
)}
|
|
@@ -464,7 +619,7 @@ export function TelemetryPage() {
|
|
|
464
619
|
return (
|
|
465
620
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
|
466
621
|
{/* Activity Chart */}
|
|
467
|
-
<div className="bg-[var(--color-surface)]
|
|
622
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
468
623
|
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Activity</h3>
|
|
469
624
|
<ResponsiveContainer width="100%" height={200}>
|
|
470
625
|
<AreaChart data={chartData}>
|
|
@@ -506,6 +661,7 @@ export function TelemetryPage() {
|
|
|
506
661
|
fill="var(--color-accent)"
|
|
507
662
|
fillOpacity={0.15}
|
|
508
663
|
strokeWidth={1.5}
|
|
664
|
+
|
|
509
665
|
/>
|
|
510
666
|
<Area
|
|
511
667
|
type="monotone"
|
|
@@ -515,6 +671,7 @@ export function TelemetryPage() {
|
|
|
515
671
|
fill="var(--color-accent-hover)"
|
|
516
672
|
fillOpacity={0.08}
|
|
517
673
|
strokeWidth={1.5}
|
|
674
|
+
|
|
518
675
|
/>
|
|
519
676
|
<Area
|
|
520
677
|
type="monotone"
|
|
@@ -524,13 +681,14 @@ export function TelemetryPage() {
|
|
|
524
681
|
fill="#ef4444"
|
|
525
682
|
fillOpacity={0.1}
|
|
526
683
|
strokeWidth={1.5}
|
|
684
|
+
|
|
527
685
|
/>
|
|
528
686
|
</AreaChart>
|
|
529
687
|
</ResponsiveContainer>
|
|
530
688
|
</div>
|
|
531
689
|
|
|
532
690
|
{/* Token Usage Chart */}
|
|
533
|
-
<div className="bg-[var(--color-surface)]
|
|
691
|
+
<div className="bg-[var(--color-surface)] card p-4">
|
|
534
692
|
<h3 className="text-sm font-medium text-[var(--color-text-secondary)] mb-4">{chartLabel} Token Usage</h3>
|
|
535
693
|
<ResponsiveContainer width="100%" height={200}>
|
|
536
694
|
<BarChart data={chartData}>
|
|
@@ -578,12 +736,14 @@ export function TelemetryPage() {
|
|
|
578
736
|
name="Input Tokens"
|
|
579
737
|
fill="var(--color-accent)"
|
|
580
738
|
radius={[2, 2, 0, 0]}
|
|
739
|
+
|
|
581
740
|
/>
|
|
582
741
|
<Bar
|
|
583
742
|
dataKey="output_tokens"
|
|
584
743
|
name="Output Tokens"
|
|
585
744
|
fill="var(--color-accent-hover)"
|
|
586
745
|
radius={[2, 2, 0, 0]}
|
|
746
|
+
|
|
587
747
|
/>
|
|
588
748
|
</BarChart>
|
|
589
749
|
</ResponsiveContainer>
|
|
@@ -595,6 +755,8 @@ export function TelemetryPage() {
|
|
|
595
755
|
{/* Usage by Agent */}
|
|
596
756
|
{usage.length > 0 && (() => {
|
|
597
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);
|
|
598
760
|
const SortHeader = ({ label, field, align = "right" }: { label: string; field: SortKey; align?: string }) => (
|
|
599
761
|
<th
|
|
600
762
|
className={`${align === "left" ? "text-left" : "text-right"} p-3 cursor-pointer hover:text-[var(--color-text-secondary)] select-none transition-colors`}
|
|
@@ -615,7 +777,7 @@ export function TelemetryPage() {
|
|
|
615
777
|
return (
|
|
616
778
|
<div className="mb-6">
|
|
617
779
|
<h2 className="text-lg font-medium mb-3">Usage by Agent</h2>
|
|
618
|
-
<div className="bg-[var(--color-surface)]
|
|
780
|
+
<div className="bg-[var(--color-surface)] card overflow-hidden">
|
|
619
781
|
<table className="w-full text-sm">
|
|
620
782
|
<thead>
|
|
621
783
|
<tr className="border-b border-[var(--color-border)] text-[var(--color-text-muted)]">
|
|
@@ -624,6 +786,9 @@ export function TelemetryPage() {
|
|
|
624
786
|
<SortHeader label="Tool Calls" field="tool_calls" />
|
|
625
787
|
<SortHeader label="Input Tokens" field="input_tokens" />
|
|
626
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" />}
|
|
627
792
|
<SortHeader label="Errors" field="errors" />
|
|
628
793
|
{costTrackingEnabled && <SortHeader label="Est. Cost" field="cost" />}
|
|
629
794
|
</tr>
|
|
@@ -636,6 +801,15 @@ export function TelemetryPage() {
|
|
|
636
801
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.tool_calls)}</td>
|
|
637
802
|
<td className="p-3 text-right text-[var(--color-text-secondary)]">{formatNumber(u.input_tokens)}</td>
|
|
638
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
|
+
)}
|
|
639
813
|
<td className="p-3 text-right">
|
|
640
814
|
{u.errors > 0 ? (
|
|
641
815
|
<span className="text-red-400">{u.errors}</span>
|
|
@@ -665,6 +839,101 @@ export function TelemetryPage() {
|
|
|
665
839
|
);
|
|
666
840
|
})()}
|
|
667
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
|
+
})()}
|
|
936
|
+
|
|
668
937
|
{/* Filters */}
|
|
669
938
|
<div className="flex flex-wrap items-center gap-3 mb-4">
|
|
670
939
|
<div className="w-44">
|
|
@@ -714,7 +983,7 @@ export function TelemetryPage() {
|
|
|
714
983
|
</div>
|
|
715
984
|
|
|
716
985
|
{/* Events List */}
|
|
717
|
-
<div className="bg-[var(--color-surface)]
|
|
986
|
+
<div className="bg-[var(--color-surface)] card">
|
|
718
987
|
<div className="p-3 border-b border-[var(--color-border)] flex items-center justify-between">
|
|
719
988
|
<h2 className="font-medium">Recent Events</h2>
|
|
720
989
|
{realtimeEvents.length > 0 && (
|
|
@@ -791,7 +1060,7 @@ export function TelemetryPage() {
|
|
|
791
1060
|
|
|
792
1061
|
function StatCard({ label, value, color }: { label: string; value: string; color?: string }) {
|
|
793
1062
|
return (
|
|
794
|
-
<div className="bg-[var(--color-surface)]
|
|
1063
|
+
<div className="bg-[var(--color-surface)] card p-4 flex-1 min-w-[120px]">
|
|
795
1064
|
<div className="text-[var(--color-text-muted)] text-xs mb-1">{label}</div>
|
|
796
1065
|
<div className={`text-2xl font-semibold ${color === "red" ? "text-red-400" : color === "orange" ? "text-orange-400" : ""}`}>
|
|
797
1066
|
{value}
|
|
@@ -342,7 +342,7 @@ export function TestsPage() {
|
|
|
342
342
|
) : (
|
|
343
343
|
<div className="space-y-3">
|
|
344
344
|
{tests.map(tc => (
|
|
345
|
-
<div key={tc.id} className="bg-[var(--color-surface)]
|
|
345
|
+
<div key={tc.id} className="bg-[var(--color-surface)] card p-4">
|
|
346
346
|
<div className="flex items-start justify-between">
|
|
347
347
|
<div className="flex-1 min-w-0">
|
|
348
348
|
<div className="flex items-center gap-2 mb-1">
|
|
@@ -515,7 +515,7 @@ export function TestsPage() {
|
|
|
515
515
|
{/* Create/Edit Form Modal */}
|
|
516
516
|
{showForm && (
|
|
517
517
|
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center" onClick={() => setShowForm(false)}>
|
|
518
|
-
<div className="bg-[var(--color-surface)]
|
|
518
|
+
<div className="bg-[var(--color-surface)] card w-full max-w-lg mx-4 p-6" onClick={e => e.stopPropagation()}>
|
|
519
519
|
<h2 className="text-lg font-bold mb-4">{editingTest ? "Edit Test" : "New Test"}</h2>
|
|
520
520
|
|
|
521
521
|
<div className="space-y-4">
|
|
@@ -163,7 +163,7 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
|
|
|
163
163
|
<button
|
|
164
164
|
onClick={() => setShowAgentPicker(!showAgentPicker)}
|
|
165
165
|
disabled={runningAgents.length === 0}
|
|
166
|
-
className="w-full flex items-center justify-center gap-2 px-3 py-2
|
|
166
|
+
className="w-full flex items-center justify-center gap-2 px-3 py-2 btn bg-[var(--color-accent-10)] text-[var(--color-accent)] text-sm font-medium hover:bg-[var(--color-accent-20)] transition disabled:opacity-30 disabled:cursor-not-allowed"
|
|
167
167
|
>
|
|
168
168
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
169
169
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
|
@@ -175,7 +175,7 @@ export function ThreadsPage({ agents, onNavigate }: ThreadsPageProps) {
|
|
|
175
175
|
{showAgentPicker && (
|
|
176
176
|
<>
|
|
177
177
|
<div className="fixed inset-0 z-40" onClick={() => setShowAgentPicker(false)} />
|
|
178
|
-
<div className="absolute top-full left-0 right-0 mt-1 bg-[var(--color-surface)]
|
|
178
|
+
<div className="absolute top-full left-0 right-0 mt-1 bg-[var(--color-surface)] card shadow-xl z-50 max-h-60 overflow-auto">
|
|
179
179
|
{runningAgents.map(agent => (
|
|
180
180
|
<button
|
|
181
181
|
key={agent.id}
|