@linxiraos/pi-stats 1.1.6 → 1.1.8
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 +2 -2
- package/dist/client/index.css +1 -1
- package/dist/client/index.js +101 -101
- package/dist/client/styles.css +362 -98
- package/dist/types/client/api.d.ts +7 -1
- package/dist/types/client/app/routes.d.ts +1 -1
- package/dist/types/client/data/useHashRoute.d.ts +2 -0
- package/dist/types/client/routes/TracesRoute.d.ts +11 -0
- package/dist/types/client/routes/index.d.ts +1 -0
- package/dist/types/client/traces/AggregatesPanel.d.ts +9 -0
- package/dist/types/client/traces/Minimap.d.ts +14 -0
- package/dist/types/client/traces/SpanDrawer.d.ts +14 -0
- package/dist/types/client/traces/SummaryStrip.d.ts +9 -0
- package/dist/types/client/traces/TimelineCanvas.d.ts +25 -0
- package/dist/types/client/traces/TraceView.d.ts +11 -0
- package/dist/types/client/traces/TranscriptList.d.ts +24 -0
- package/dist/types/client/traces/time-scale.d.ts +40 -0
- package/dist/types/client/traces/trace-colors.d.ts +31 -0
- package/dist/types/db.d.ts +24 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/parser.d.ts +8 -0
- package/dist/types/server.d.ts +3 -2
- package/dist/types/shared-types.d.ts +130 -0
- package/dist/types/trace.d.ts +33 -0
- package/dist/types/types.d.ts +35 -1
- package/package.json +4 -4
- package/src/client/App.tsx +11 -1
- package/src/client/api.ts +23 -0
- package/src/client/app/routes.ts +7 -0
- package/src/client/data/useHashRoute.ts +28 -10
- package/src/client/routes/ProvidersRoute.tsx +1 -0
- package/src/client/routes/TracesRoute.tsx +182 -0
- package/src/client/routes/index.ts +1 -0
- package/src/client/styles.css +433 -111
- package/src/client/traces/AggregatesPanel.tsx +88 -0
- package/src/client/traces/Minimap.tsx +177 -0
- package/src/client/traces/SpanDrawer.tsx +309 -0
- package/src/client/traces/SummaryStrip.tsx +37 -0
- package/src/client/traces/TimelineCanvas.tsx +768 -0
- package/src/client/traces/TraceView.tsx +344 -0
- package/src/client/traces/TranscriptList.tsx +166 -0
- package/src/client/traces/time-scale.ts +250 -0
- package/src/client/traces/trace-colors.ts +77 -0
- package/src/db.ts +77 -0
- package/src/index.ts +1 -6
- package/src/parser.ts +55 -2
- package/src/server.ts +69 -17
- package/src/shared-types.ts +138 -0
- package/src/trace.ts +1002 -0
- package/src/types.ts +45 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Traces section: root-session list (subagents folded in) that opens the
|
|
3
|
+
* flamegraph trace viewer for a selected session.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useMemo, useState } from "react";
|
|
7
|
+
import { getSessions } from "../api";
|
|
8
|
+
import { formatCompact, formatCost, formatDurationMs, formatRelativeTime } from "../data/formatters";
|
|
9
|
+
import { useResource } from "../data/useResource";
|
|
10
|
+
import { TraceView } from "../traces/TraceView";
|
|
11
|
+
import type { SessionSummary } from "../types";
|
|
12
|
+
import { AsyncBoundary, DataTable, Panel } from "../ui";
|
|
13
|
+
|
|
14
|
+
export interface TracesRouteProps {
|
|
15
|
+
active: boolean;
|
|
16
|
+
session: string | null;
|
|
17
|
+
onOpenSession: (file: string | null) => void;
|
|
18
|
+
refreshTrigger: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function ModelChips({ models }: { models: string[] }) {
|
|
22
|
+
const shown = models.slice(0, 3);
|
|
23
|
+
return (
|
|
24
|
+
<div style={{ display: "flex", gap: 4, flexWrap: "wrap" }}>
|
|
25
|
+
{shown.map(model => (
|
|
26
|
+
<span
|
|
27
|
+
key={model}
|
|
28
|
+
className="stats-text-muted truncate"
|
|
29
|
+
style={{
|
|
30
|
+
fontSize: 10,
|
|
31
|
+
border: "1px solid var(--border)",
|
|
32
|
+
borderRadius: 999,
|
|
33
|
+
padding: "1px 6px",
|
|
34
|
+
maxWidth: 140,
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
{model}
|
|
38
|
+
</span>
|
|
39
|
+
))}
|
|
40
|
+
{models.length > 3 && (
|
|
41
|
+
<span className="stats-text-muted" style={{ fontSize: 10 }}>
|
|
42
|
+
+{models.length - 3}
|
|
43
|
+
</span>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function TracesRoute({ active, session, onOpenSession, refreshTrigger }: TracesRouteProps) {
|
|
50
|
+
const [filter, setFilter] = useState("");
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
data: sessions,
|
|
54
|
+
error,
|
|
55
|
+
loading,
|
|
56
|
+
} = useResource(["sessions", refreshTrigger], signal => getSessions(200, undefined, signal), {
|
|
57
|
+
pollMs: 30000,
|
|
58
|
+
enabled: active && session === null,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const filtered = useMemo(() => {
|
|
62
|
+
if (!sessions) return [];
|
|
63
|
+
const needle = filter.trim().toLowerCase();
|
|
64
|
+
if (!needle) return sessions;
|
|
65
|
+
return sessions.filter(
|
|
66
|
+
row =>
|
|
67
|
+
(row.title ?? "").toLowerCase().includes(needle) ||
|
|
68
|
+
row.folder.toLowerCase().includes(needle) ||
|
|
69
|
+
row.models.some(model => model.toLowerCase().includes(needle)),
|
|
70
|
+
);
|
|
71
|
+
}, [sessions, filter]);
|
|
72
|
+
|
|
73
|
+
const columns = useMemo(
|
|
74
|
+
() => [
|
|
75
|
+
{
|
|
76
|
+
key: "title",
|
|
77
|
+
header: "Title",
|
|
78
|
+
render: (item: SessionSummary) => (
|
|
79
|
+
<div className="stats-font-medium stats-text-primary truncate" style={{ maxWidth: 280 }}>
|
|
80
|
+
{item.title ?? item.file.split("/").pop()}
|
|
81
|
+
</div>
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "folder",
|
|
86
|
+
header: "Project",
|
|
87
|
+
render: (item: SessionSummary) => (
|
|
88
|
+
<span className="stats-text-muted truncate" style={{ maxWidth: 160, display: "inline-block" }}>
|
|
89
|
+
{item.folder.split("/").slice(-2).join("/")}
|
|
90
|
+
</span>
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "started",
|
|
95
|
+
header: "Started",
|
|
96
|
+
render: (item: SessionSummary) => formatRelativeTime(item.startedAt),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "duration",
|
|
100
|
+
header: "Duration",
|
|
101
|
+
numeric: true,
|
|
102
|
+
render: (item: SessionSummary) => formatDurationMs(item.endedAt - item.startedAt),
|
|
103
|
+
},
|
|
104
|
+
{ key: "requests", header: "Requests", numeric: true, render: (item: SessionSummary) => item.requests },
|
|
105
|
+
{ key: "toolCalls", header: "Tools", numeric: true, render: (item: SessionSummary) => item.toolCalls },
|
|
106
|
+
{ key: "subagents", header: "Agents", numeric: true, render: (item: SessionSummary) => item.subagents },
|
|
107
|
+
{
|
|
108
|
+
key: "tokens",
|
|
109
|
+
header: "Tokens",
|
|
110
|
+
numeric: true,
|
|
111
|
+
render: (item: SessionSummary) => formatCompact(item.totalTokens),
|
|
112
|
+
},
|
|
113
|
+
{ key: "cost", header: "Cost", numeric: true, render: (item: SessionSummary) => formatCost(item.costTotal) },
|
|
114
|
+
{ key: "models", header: "Models", render: (item: SessionSummary) => <ModelChips models={item.models} /> },
|
|
115
|
+
],
|
|
116
|
+
[],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const renderMobileCard = (item: SessionSummary, onClick?: () => void) => (
|
|
120
|
+
<div className="stats-mobile-card" onClick={onClick}>
|
|
121
|
+
<div className="stats-mobile-card-header">
|
|
122
|
+
<div className="stats-font-semibold stats-text-primary truncate">
|
|
123
|
+
{item.title ?? item.file.split("/").pop()}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
<div className="stats-mobile-card-grid">
|
|
127
|
+
<div>
|
|
128
|
+
<div className="stats-mobile-card-label">Started</div>
|
|
129
|
+
<div className="stats-mobile-card-value">{formatRelativeTime(item.startedAt)}</div>
|
|
130
|
+
</div>
|
|
131
|
+
<div>
|
|
132
|
+
<div className="stats-mobile-card-label">Duration</div>
|
|
133
|
+
<div className="stats-mobile-card-value">{formatDurationMs(item.endedAt - item.startedAt)}</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div>
|
|
136
|
+
<div className="stats-mobile-card-label">Requests</div>
|
|
137
|
+
<div className="stats-mobile-card-value">{item.requests}</div>
|
|
138
|
+
</div>
|
|
139
|
+
<div>
|
|
140
|
+
<div className="stats-mobile-card-label">Cost</div>
|
|
141
|
+
<div className="stats-mobile-card-value">{formatCost(item.costTotal)}</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
if (session !== null) {
|
|
148
|
+
return <TraceView file={session} active={active} onBack={() => onOpenSession(null)} />;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className="stats-route-container">
|
|
153
|
+
<Panel
|
|
154
|
+
title="Sessions"
|
|
155
|
+
subtitle="Recent sessions with subagent activity folded in — click one to open its trace"
|
|
156
|
+
actions={
|
|
157
|
+
<input
|
|
158
|
+
type="search"
|
|
159
|
+
value={filter}
|
|
160
|
+
onChange={event => setFilter(event.target.value)}
|
|
161
|
+
placeholder="Filter by title, project, model…"
|
|
162
|
+
aria-label="Filter sessions"
|
|
163
|
+
spellCheck={false}
|
|
164
|
+
className="stats-trace-input"
|
|
165
|
+
style={{ width: 220 }}
|
|
166
|
+
/>
|
|
167
|
+
}
|
|
168
|
+
>
|
|
169
|
+
<AsyncBoundary loading={loading} error={error} data={sessions}>
|
|
170
|
+
<DataTable
|
|
171
|
+
columns={columns}
|
|
172
|
+
data={filtered}
|
|
173
|
+
keyExtractor={item => item.file}
|
|
174
|
+
onRowClick={item => onOpenSession(item.file)}
|
|
175
|
+
renderMobileCard={renderMobileCard}
|
|
176
|
+
emptyText="No sessions found — run a Sync to index recent activity"
|
|
177
|
+
/>
|
|
178
|
+
</AsyncBoundary>
|
|
179
|
+
</Panel>
|
|
180
|
+
</div>
|
|
181
|
+
);
|
|
182
|
+
}
|