@oh-my-pi/omp-stats 16.3.0 → 16.3.2
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/CHANGELOG.md +6 -0
- package/dist/client/index.js +83 -83
- package/dist/client/styles.css +4 -1
- package/dist/types/aggregator.d.ts +6 -1
- package/dist/types/client/api.d.ts +2 -1
- package/dist/types/client/app/routes.d.ts +1 -1
- package/dist/types/client/data/view-models.d.ts +9 -1
- package/dist/types/client/routes/ToolsRoute.d.ts +7 -0
- package/dist/types/client/routes/index.d.ts +1 -0
- package/dist/types/db.d.ts +31 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/parser.d.ts +3 -1
- package/dist/types/shared-types.d.ts +48 -0
- package/dist/types/types.d.ts +41 -0
- package/package.json +4 -4
- package/src/aggregator.ts +22 -1
- package/src/client/App.tsx +3 -0
- package/src/client/api.ts +8 -0
- package/src/client/app/routes.ts +7 -1
- package/src/client/data/useHashRoute.ts +1 -0
- package/src/client/data/view-models.ts +18 -0
- package/src/client/routes/ToolsRoute.tsx +463 -0
- package/src/client/routes/index.ts +1 -0
- package/src/db.ts +252 -0
- package/src/index.ts +5 -0
- package/src/parser.ts +84 -2
- package/src/server.ts +6 -0
- package/src/shared-types.ts +52 -0
- package/src/types.ts +43 -0
package/dist/client/styles.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! tailwindcss v4.3.
|
|
1
|
+
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
2
2
|
@layer properties;
|
|
3
3
|
@layer theme, base, components, utilities;
|
|
4
4
|
@layer theme {
|
|
@@ -244,6 +244,9 @@
|
|
|
244
244
|
.w-full {
|
|
245
245
|
width: 100%;
|
|
246
246
|
}
|
|
247
|
+
.max-w-\[280px\] {
|
|
248
|
+
max-width: 280px;
|
|
249
|
+
}
|
|
247
250
|
.max-w-\[440px\] {
|
|
248
251
|
max-width: 440px;
|
|
249
252
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BehaviorDashboardStats, DashboardStats, MessageStats, RequestDetails } from "./types";
|
|
1
|
+
import type { BehaviorDashboardStats, DashboardStats, MessageStats, RequestDetails, ToolDashboardStats } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Progress event emitted after each session file is fully processed.
|
|
4
4
|
* `current` is the number of files completed (skipped + parsed),
|
|
@@ -79,4 +79,9 @@ export declare function getRequestDetails(id: number): Promise<RequestDetails |
|
|
|
79
79
|
*/
|
|
80
80
|
export declare function getTotalMessageCount(): Promise<number>;
|
|
81
81
|
export declare function getBehaviorDashboardStats(range?: string | null): Promise<BehaviorDashboardStats>;
|
|
82
|
+
/**
|
|
83
|
+
* Get the tools dashboard payload: per-tool totals, per-(tool, model)
|
|
84
|
+
* breakdown, and the call time series (bucketed like the model series).
|
|
85
|
+
*/
|
|
86
|
+
export declare function getToolDashboardStats(range?: string | null): Promise<ToolDashboardStats>;
|
|
82
87
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BehaviorDashboardStats, CostDashboardStats, FolderStats, GainDashboardStats, MessageStats, ModelDashboardStats, OverviewStats, RequestDetails, TimeRange } from "./types";
|
|
1
|
+
import type { BehaviorDashboardStats, CostDashboardStats, FolderStats, GainDashboardStats, MessageStats, ModelDashboardStats, OverviewStats, RequestDetails, TimeRange, ToolDashboardStats } from "./types";
|
|
2
2
|
export declare class ApiError extends Error {
|
|
3
3
|
status: number;
|
|
4
4
|
endpoint: string;
|
|
@@ -18,3 +18,4 @@ export declare function sync(signal?: AbortSignal): Promise<{
|
|
|
18
18
|
export declare function getBehaviorDashboardStats(range?: TimeRange, signal?: AbortSignal): Promise<BehaviorDashboardStats>;
|
|
19
19
|
export declare function getFolderStats(range?: TimeRange, signal?: AbortSignal): Promise<FolderStats[]>;
|
|
20
20
|
export declare function getGainDashboardStats(range?: TimeRange, project?: string | null, signal?: AbortSignal): Promise<GainDashboardStats>;
|
|
21
|
+
export declare function getToolDashboardStats(range?: TimeRange, signal?: AbortSignal): Promise<ToolDashboardStats>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from "react";
|
|
2
|
-
export type DashboardSection = "overview" | "requests" | "errors" | "models" | "costs" | "behavior" | "projects" | "gain";
|
|
2
|
+
export type DashboardSection = "overview" | "requests" | "errors" | "models" | "tools" | "costs" | "behavior" | "projects" | "gain";
|
|
3
3
|
export interface DashboardRoute {
|
|
4
4
|
id: DashboardSection;
|
|
5
5
|
label: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentType, AgentTypeStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, ModelPerformancePoint, TimeRange } from "../types";
|
|
1
|
+
import type { AgentType, AgentTypeStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, ModelPerformancePoint, TimeRange, ToolUsageStats } from "../types";
|
|
2
2
|
export interface AgentTokenSegment {
|
|
3
3
|
agentType: AgentType;
|
|
4
4
|
/** input + output + cache read + cache write — the displayed denominator. */
|
|
@@ -57,3 +57,11 @@ export declare function buildCostSummary(costSeries: CostTimeSeriesPoint[]): Cos
|
|
|
57
57
|
export declare function buildModelPerformanceLookup(points: ModelPerformancePoint[], range: TimeRange): Map<string, ModelPerformanceSeries>;
|
|
58
58
|
export declare function buildBehaviorSummary(overall: BehaviorOverallStats, series: BehaviorTimeSeriesPoint[]): BehaviorSummaryView;
|
|
59
59
|
export declare function buildFolderRows(folders: FolderStats[]): FolderRowView[];
|
|
60
|
+
/** Table row for the Tools route: usage stats plus derived rates/shares. */
|
|
61
|
+
export interface ToolRowView extends ToolUsageStats {
|
|
62
|
+
/** errors / calls (0 for zero calls). */
|
|
63
|
+
errorRate: number;
|
|
64
|
+
/** Calls relative to the busiest tool, 0-100, for the share bar. */
|
|
65
|
+
callsPercentage: number;
|
|
66
|
+
}
|
|
67
|
+
export declare function buildToolRows(tools: ToolUsageStats[]): ToolRowView[];
|
package/dist/types/db.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
-
import type { AgentTypeStats, AggregatedStats, BehaviorModelStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, UserMessageLink, UserMessageStats } from "./types";
|
|
2
|
+
import type { AgentTypeStats, AggregatedStats, BehaviorModelStats, BehaviorOverallStats, BehaviorTimeSeriesPoint, CostTimeSeriesPoint, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, ToolCallStats, ToolModelStats, ToolResultLink, ToolTimeSeriesPoint, ToolUsageStats, UserMessageLink, UserMessageStats } from "./types";
|
|
3
3
|
/**
|
|
4
4
|
* Initialize the database and create tables.
|
|
5
5
|
*/
|
|
@@ -112,3 +112,33 @@ export declare function getBehaviorOverall(cutoff?: number | null): BehaviorOver
|
|
|
112
112
|
* user messages that never received an assistant reply.
|
|
113
113
|
*/
|
|
114
114
|
export declare function getBehaviorByModel(cutoff?: number | null): BehaviorModelStats[];
|
|
115
|
+
/**
|
|
116
|
+
* Insert tool-call rows. Idempotent via UNIQUE(session_file, tool_call_id);
|
|
117
|
+
* the `WHERE NOT EXISTS` guard mirrors {@link insertMessageStats}: forked
|
|
118
|
+
* sessions deep-copy assistant entries (same `entry_id`, `timestamp`, and
|
|
119
|
+
* tool-call ids under a new file), so first-write-wins across the lineage
|
|
120
|
+
* keeps aggregates from double counting. Keyed on the assistant entry
|
|
121
|
+
* identity, not the call id alone — provider call ids are not a global
|
|
122
|
+
* namespace across unrelated sessions.
|
|
123
|
+
*/
|
|
124
|
+
export declare function insertToolCalls(calls: ToolCallStats[]): number;
|
|
125
|
+
/**
|
|
126
|
+
* Attach result size / error flag to persisted tool-call rows. Results can
|
|
127
|
+
* land in a later incremental sync pass than the call that produced them, so
|
|
128
|
+
* this is an UPDATE keyed by (session_file, tool_call_id). The `IS NULL`
|
|
129
|
+
* guard makes re-syncs idempotent; rows skipped by the fork guard simply
|
|
130
|
+
* never match.
|
|
131
|
+
*/
|
|
132
|
+
export declare function updateToolResults(links: ToolResultLink[]): number;
|
|
133
|
+
/**
|
|
134
|
+
* Get tool usage aggregated by tool name.
|
|
135
|
+
*/
|
|
136
|
+
export declare function getToolStats(cutoff?: number): ToolUsageStats[];
|
|
137
|
+
/**
|
|
138
|
+
* Get tool usage aggregated by (tool, model, provider).
|
|
139
|
+
*/
|
|
140
|
+
export declare function getToolStatsByModel(cutoff?: number): ToolModelStats[];
|
|
141
|
+
/**
|
|
142
|
+
* Get tool-call time series (one point per bucket per tool).
|
|
143
|
+
*/
|
|
144
|
+
export declare function getToolTimeSeries(days?: number, cutoff?: number | null, bucketMs?: number): ToolTimeSeriesPoint[];
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
export { getDashboardStats, getTotalMessageCount, type SyncOptions, type SyncProgress, smokeTestSyncWorker, syncAllSessions, } from "./aggregator";
|
|
2
|
+
export { getDashboardStats, getToolDashboardStats, getTotalMessageCount, type SyncOptions, type SyncProgress, smokeTestSyncWorker, syncAllSessions, } from "./aggregator";
|
|
3
3
|
export { closeDb } from "./db";
|
|
4
4
|
export { getGainDashboardStats } from "./gain-aggregator";
|
|
5
5
|
export { startServer } from "./server";
|
|
6
6
|
export type { GainDashboardStats, GainSource, GainSourceTotals, GainTimeSeriesPoint, } from "./shared-types";
|
|
7
|
-
export type { AggregatedStats, DashboardStats, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, } from "./types";
|
|
7
|
+
export type { AggregatedStats, DashboardStats, FolderStats, MessageStats, ModelPerformancePoint, ModelStats, ModelTimeSeriesPoint, TimeSeriesPoint, ToolDashboardStats, ToolModelStats, ToolTimeSeriesPoint, ToolUsageStats, } from "./types";
|
package/dist/types/parser.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentType, MessageStats, SessionEntry, UserMessageLink, UserMessageStats } from "./types";
|
|
1
|
+
import type { AgentType, MessageStats, SessionEntry, ToolCallStats, ToolResultLink, UserMessageLink, UserMessageStats } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* Classify which agent produced a transcript from its path within the sessions
|
|
4
4
|
* directory. Layout: `<sessionsDir>/<project>/<file>.jsonl` is the `main`
|
|
@@ -30,6 +30,8 @@ export interface ParseSessionResult {
|
|
|
30
30
|
stats: MessageStats[];
|
|
31
31
|
userStats: UserMessageStats[];
|
|
32
32
|
userLinks: UserMessageLink[];
|
|
33
|
+
toolCalls: ToolCallStats[];
|
|
34
|
+
toolResults: ToolResultLink[];
|
|
33
35
|
newOffset: number;
|
|
34
36
|
}
|
|
35
37
|
export declare function parseSessionFile(sessionPath: string, fromOffset?: number): Promise<ParseSessionResult>;
|
|
@@ -251,3 +251,51 @@ export interface GainDashboardStats {
|
|
|
251
251
|
/** All distinct projects seen in the data, for the selector. */
|
|
252
252
|
projects: string[];
|
|
253
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Aggregated usage for a single tool over the active range.
|
|
256
|
+
*
|
|
257
|
+
* Token/cost fields are the *real* provider usage of the assistant turns that
|
|
258
|
+
* invoked the tool, split evenly across that turn's tool calls so the numbers
|
|
259
|
+
* stay additive (a turn with 3 calls contributes a third of its usage to each
|
|
260
|
+
* tool). Payload fields (`argsChars`/`resultChars`) are raw character counts
|
|
261
|
+
* of the serialized arguments and the text fed back into context — a size
|
|
262
|
+
* proxy, not provider-counted tokens.
|
|
263
|
+
*/
|
|
264
|
+
export interface ToolUsageStats {
|
|
265
|
+
/** Tool name as recorded on the tool call. */
|
|
266
|
+
tool: string;
|
|
267
|
+
/** Number of tool calls. */
|
|
268
|
+
calls: number;
|
|
269
|
+
/** Calls whose result came back with `isError`. */
|
|
270
|
+
errors: number;
|
|
271
|
+
/** Serialized tool-call argument characters. */
|
|
272
|
+
argsChars: number;
|
|
273
|
+
/** Text characters of tool results fed back into context. */
|
|
274
|
+
resultChars: number;
|
|
275
|
+
/** Total provider tokens of invoking turns, attributed per call share. */
|
|
276
|
+
totalTokensShare: number;
|
|
277
|
+
/** Output tokens of invoking turns, attributed per call share. */
|
|
278
|
+
outputTokensShare: number;
|
|
279
|
+
/** Cost (USD) of invoking turns, attributed per call share. */
|
|
280
|
+
costShare: number;
|
|
281
|
+
/** Unix ms of the most recent call in range. */
|
|
282
|
+
lastUsed: number;
|
|
283
|
+
}
|
|
284
|
+
/** Per-(tool, model) breakdown with the same attribution as {@link ToolUsageStats}. */
|
|
285
|
+
export interface ToolModelStats extends ToolUsageStats {
|
|
286
|
+
model: string;
|
|
287
|
+
provider: string;
|
|
288
|
+
}
|
|
289
|
+
/** Tool-call time-series point (one bucket per tool). */
|
|
290
|
+
export interface ToolTimeSeriesPoint {
|
|
291
|
+
timestamp: number;
|
|
292
|
+
tool: string;
|
|
293
|
+
calls: number;
|
|
294
|
+
errors: number;
|
|
295
|
+
}
|
|
296
|
+
/** Complete tools dashboard payload. */
|
|
297
|
+
export interface ToolDashboardStats {
|
|
298
|
+
byTool: ToolUsageStats[];
|
|
299
|
+
byToolModel: ToolModelStats[];
|
|
300
|
+
series: ToolTimeSeriesPoint[];
|
|
301
|
+
}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -121,3 +121,44 @@ export interface UserMessageLink {
|
|
|
121
121
|
model: string;
|
|
122
122
|
provider: string;
|
|
123
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* One tool call extracted from an assistant message's `toolCall` content
|
|
126
|
+
* blocks. `callsInTurn` records how many calls that assistant turn contained
|
|
127
|
+
* so aggregation can split the turn's real provider usage evenly per call.
|
|
128
|
+
*/
|
|
129
|
+
export interface ToolCallStats {
|
|
130
|
+
/** Session file path */
|
|
131
|
+
sessionFile: string;
|
|
132
|
+
/** Assistant-message entry ID that emitted the call */
|
|
133
|
+
entryId: string;
|
|
134
|
+
/** Provider-assigned tool call ID (unique within a session) */
|
|
135
|
+
toolCallId: string;
|
|
136
|
+
/** Folder/project path (extracted from session filename) */
|
|
137
|
+
folder: string;
|
|
138
|
+
/** Tool name */
|
|
139
|
+
toolName: string;
|
|
140
|
+
/** Model that emitted the call */
|
|
141
|
+
model: string;
|
|
142
|
+
/** Provider name */
|
|
143
|
+
provider: string;
|
|
144
|
+
/** Assistant-message timestamp (Unix ms) */
|
|
145
|
+
timestamp: number;
|
|
146
|
+
/** Which agent produced the call */
|
|
147
|
+
agentType: AgentType;
|
|
148
|
+
/** Total tool calls in the same assistant turn (>= 1) */
|
|
149
|
+
callsInTurn: number;
|
|
150
|
+
/** Serialized argument characters */
|
|
151
|
+
argsChars: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Result linkage emitted when the parser sees a `toolResult` message entry.
|
|
155
|
+
* Applied as an UPDATE on the persisted tool-call row — results can land in a
|
|
156
|
+
* later incremental sync pass than the call that produced them.
|
|
157
|
+
*/
|
|
158
|
+
export interface ToolResultLink {
|
|
159
|
+
sessionFile: string;
|
|
160
|
+
toolCallId: string;
|
|
161
|
+
/** Text characters fed back into context */
|
|
162
|
+
resultChars: number;
|
|
163
|
+
isError: boolean;
|
|
164
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/omp-stats",
|
|
4
|
-
"version": "16.3.
|
|
4
|
+
"version": "16.3.2",
|
|
5
5
|
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"fmt": "biome format --write ."
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "16.3.
|
|
43
|
-
"@oh-my-pi/pi-catalog": "16.3.
|
|
44
|
-
"@oh-my-pi/pi-utils": "16.3.
|
|
42
|
+
"@oh-my-pi/pi-ai": "16.3.2",
|
|
43
|
+
"@oh-my-pi/pi-catalog": "16.3.2",
|
|
44
|
+
"@oh-my-pi/pi-utils": "16.3.2",
|
|
45
45
|
"@tailwindcss/node": "^4.3.0",
|
|
46
46
|
"chart.js": "^4.5.1",
|
|
47
47
|
"date-fns": "^4.4.0",
|
package/src/aggregator.ts
CHANGED
|
@@ -17,10 +17,15 @@ import {
|
|
|
17
17
|
getStatsByFolder,
|
|
18
18
|
getStatsByModel,
|
|
19
19
|
getTimeSeries,
|
|
20
|
+
getToolStats,
|
|
21
|
+
getToolStatsByModel,
|
|
22
|
+
getToolTimeSeries,
|
|
20
23
|
initDb,
|
|
21
24
|
insertMessageStats,
|
|
25
|
+
insertToolCalls,
|
|
22
26
|
insertUserMessageStats,
|
|
23
27
|
setFileOffset,
|
|
28
|
+
updateToolResults,
|
|
24
29
|
updateUserMessageLinks,
|
|
25
30
|
} from "./db";
|
|
26
31
|
import { getSessionEntry, listAllSessionFiles, type ParseSessionResult, parseSessionFile } from "./parser";
|
|
@@ -29,7 +34,7 @@ import type { SyncWorkerRequest, SyncWorkerResponse } from "./sync-worker";
|
|
|
29
34
|
// hidden argv mode, so the compiled binary and npm bundle only need one
|
|
30
35
|
// JavaScript entry. Standalone source `omp-stats` keeps using this package's
|
|
31
36
|
// own sync-worker source file.
|
|
32
|
-
import type { BehaviorDashboardStats, DashboardStats, MessageStats, RequestDetails } from "./types";
|
|
37
|
+
import type { BehaviorDashboardStats, DashboardStats, MessageStats, RequestDetails, ToolDashboardStats } from "./types";
|
|
33
38
|
|
|
34
39
|
/**
|
|
35
40
|
* Apply a freshly parsed result to the database. Runs entirely on the
|
|
@@ -39,6 +44,8 @@ function applyParseResult(sessionFile: string, lastModified: number, result: Par
|
|
|
39
44
|
if (result.stats.length > 0) insertMessageStats(result.stats);
|
|
40
45
|
if (result.userStats.length > 0) insertUserMessageStats(result.userStats);
|
|
41
46
|
if (result.userLinks.length > 0) updateUserMessageLinks(result.userLinks);
|
|
47
|
+
if (result.toolCalls.length > 0) insertToolCalls(result.toolCalls);
|
|
48
|
+
if (result.toolResults.length > 0) updateToolResults(result.toolResults);
|
|
42
49
|
setFileOffset(sessionFile, result.newOffset, lastModified);
|
|
43
50
|
return result.stats.length + result.userStats.length;
|
|
44
51
|
}
|
|
@@ -478,3 +485,17 @@ export async function getBehaviorDashboardStats(range?: string | null): Promise<
|
|
|
478
485
|
behaviorSeries: getBehaviorTimeSeries(cutoff),
|
|
479
486
|
};
|
|
480
487
|
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Get the tools dashboard payload: per-tool totals, per-(tool, model)
|
|
491
|
+
* breakdown, and the call time series (bucketed like the model series).
|
|
492
|
+
*/
|
|
493
|
+
export async function getToolDashboardStats(range?: string | null): Promise<ToolDashboardStats> {
|
|
494
|
+
await initDb();
|
|
495
|
+
const { modelSeriesDays, modelSeriesBucketMs, cutoff } = getTimeRangeConfig(range);
|
|
496
|
+
return {
|
|
497
|
+
byTool: getToolStats(cutoff ?? undefined),
|
|
498
|
+
byToolModel: getToolStatsByModel(cutoff ?? undefined),
|
|
499
|
+
series: getToolTimeSeries(modelSeriesDays, cutoff, modelSeriesBucketMs),
|
|
500
|
+
};
|
|
501
|
+
}
|
package/src/client/App.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
OverviewRoute,
|
|
12
12
|
ProjectsRoute,
|
|
13
13
|
RequestsRoute,
|
|
14
|
+
ToolsRoute,
|
|
14
15
|
} from "./routes";
|
|
15
16
|
import { RequestDrawer } from "./ui/RequestDrawer";
|
|
16
17
|
|
|
@@ -72,6 +73,8 @@ export default function App() {
|
|
|
72
73
|
);
|
|
73
74
|
case "models":
|
|
74
75
|
return <ModelsRoute active={isActive} range={range} refreshTrigger={refreshTrigger} />;
|
|
76
|
+
case "tools":
|
|
77
|
+
return <ToolsRoute active={isActive} range={range} refreshTrigger={refreshTrigger} />;
|
|
75
78
|
case "costs":
|
|
76
79
|
return <CostsRoute active={isActive} range={range} refreshTrigger={refreshTrigger} />;
|
|
77
80
|
case "behavior":
|
package/src/client/api.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
OverviewStats,
|
|
9
9
|
RequestDetails,
|
|
10
10
|
TimeRange,
|
|
11
|
+
ToolDashboardStats,
|
|
11
12
|
} from "./types";
|
|
12
13
|
|
|
13
14
|
const API_BASE = "/api";
|
|
@@ -92,3 +93,10 @@ export async function getGainDashboardStats(
|
|
|
92
93
|
if (project) params.set("project", project);
|
|
93
94
|
return fetchJson<GainDashboardStats>(`${API_BASE}/stats/gain?${params}`, { signal });
|
|
94
95
|
}
|
|
96
|
+
|
|
97
|
+
export async function getToolDashboardStats(
|
|
98
|
+
range: TimeRange = "24h",
|
|
99
|
+
signal?: AbortSignal,
|
|
100
|
+
): Promise<ToolDashboardStats> {
|
|
101
|
+
return fetchJson<ToolDashboardStats>(`${API_BASE}/stats/tools?range=${encodeURIComponent(range)}`, { signal });
|
|
102
|
+
}
|
package/src/client/app/routes.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Activity, AlertCircle, Coins, Cpu, Folder, LayoutDashboard, Smile, TrendingUp } from "lucide-react";
|
|
1
|
+
import { Activity, AlertCircle, Coins, Cpu, Folder, LayoutDashboard, Smile, TrendingUp, Wrench } from "lucide-react";
|
|
2
2
|
import type React from "react";
|
|
3
3
|
|
|
4
4
|
export type DashboardSection =
|
|
@@ -6,6 +6,7 @@ export type DashboardSection =
|
|
|
6
6
|
| "requests"
|
|
7
7
|
| "errors"
|
|
8
8
|
| "models"
|
|
9
|
+
| "tools"
|
|
9
10
|
| "costs"
|
|
10
11
|
| "behavior"
|
|
11
12
|
| "projects"
|
|
@@ -39,6 +40,11 @@ export const routes: DashboardRoute[] = [
|
|
|
39
40
|
label: "Models",
|
|
40
41
|
icon: Cpu,
|
|
41
42
|
},
|
|
43
|
+
{
|
|
44
|
+
id: "tools",
|
|
45
|
+
label: "Tools",
|
|
46
|
+
icon: Wrench,
|
|
47
|
+
},
|
|
42
48
|
{
|
|
43
49
|
id: "costs",
|
|
44
50
|
label: "Costs",
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
FolderStats,
|
|
9
9
|
ModelPerformancePoint,
|
|
10
10
|
TimeRange,
|
|
11
|
+
ToolUsageStats,
|
|
11
12
|
} from "../types";
|
|
12
13
|
|
|
13
14
|
/** Fixed display order for the agent-token-share breakdown. */
|
|
@@ -231,3 +232,20 @@ export function buildFolderRows(folders: FolderStats[]): FolderRowView[] {
|
|
|
231
232
|
requestsPercentage: maxRequests > 0 ? (f.totalRequests / maxRequests) * 100 : 0,
|
|
232
233
|
}));
|
|
233
234
|
}
|
|
235
|
+
|
|
236
|
+
/** Table row for the Tools route: usage stats plus derived rates/shares. */
|
|
237
|
+
export interface ToolRowView extends ToolUsageStats {
|
|
238
|
+
/** errors / calls (0 for zero calls). */
|
|
239
|
+
errorRate: number;
|
|
240
|
+
/** Calls relative to the busiest tool, 0-100, for the share bar. */
|
|
241
|
+
callsPercentage: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function buildToolRows(tools: ToolUsageStats[]): ToolRowView[] {
|
|
245
|
+
const maxCalls = tools.reduce((max, t) => Math.max(max, t.calls), 0);
|
|
246
|
+
return tools.map(t => ({
|
|
247
|
+
...t,
|
|
248
|
+
errorRate: t.calls > 0 ? t.errors / t.calls : 0,
|
|
249
|
+
callsPercentage: maxCalls > 0 ? (t.calls / maxCalls) * 100 : 0,
|
|
250
|
+
}));
|
|
251
|
+
}
|