@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/dist/client/index.css +1 -1
  3. package/dist/client/index.js +101 -101
  4. package/dist/client/styles.css +362 -98
  5. package/dist/types/client/api.d.ts +7 -1
  6. package/dist/types/client/app/routes.d.ts +1 -1
  7. package/dist/types/client/data/useHashRoute.d.ts +2 -0
  8. package/dist/types/client/routes/TracesRoute.d.ts +11 -0
  9. package/dist/types/client/routes/index.d.ts +1 -0
  10. package/dist/types/client/traces/AggregatesPanel.d.ts +9 -0
  11. package/dist/types/client/traces/Minimap.d.ts +14 -0
  12. package/dist/types/client/traces/SpanDrawer.d.ts +14 -0
  13. package/dist/types/client/traces/SummaryStrip.d.ts +9 -0
  14. package/dist/types/client/traces/TimelineCanvas.d.ts +25 -0
  15. package/dist/types/client/traces/TraceView.d.ts +11 -0
  16. package/dist/types/client/traces/TranscriptList.d.ts +24 -0
  17. package/dist/types/client/traces/time-scale.d.ts +40 -0
  18. package/dist/types/client/traces/trace-colors.d.ts +31 -0
  19. package/dist/types/db.d.ts +24 -1
  20. package/dist/types/index.d.ts +1 -1
  21. package/dist/types/parser.d.ts +8 -0
  22. package/dist/types/server.d.ts +3 -2
  23. package/dist/types/shared-types.d.ts +130 -0
  24. package/dist/types/trace.d.ts +33 -0
  25. package/dist/types/types.d.ts +35 -1
  26. package/package.json +4 -4
  27. package/src/client/App.tsx +11 -1
  28. package/src/client/api.ts +23 -0
  29. package/src/client/app/routes.ts +7 -0
  30. package/src/client/data/useHashRoute.ts +28 -10
  31. package/src/client/routes/ProvidersRoute.tsx +1 -0
  32. package/src/client/routes/TracesRoute.tsx +182 -0
  33. package/src/client/routes/index.ts +1 -0
  34. package/src/client/styles.css +433 -111
  35. package/src/client/traces/AggregatesPanel.tsx +88 -0
  36. package/src/client/traces/Minimap.tsx +177 -0
  37. package/src/client/traces/SpanDrawer.tsx +309 -0
  38. package/src/client/traces/SummaryStrip.tsx +37 -0
  39. package/src/client/traces/TimelineCanvas.tsx +768 -0
  40. package/src/client/traces/TraceView.tsx +344 -0
  41. package/src/client/traces/TranscriptList.tsx +166 -0
  42. package/src/client/traces/time-scale.ts +250 -0
  43. package/src/client/traces/trace-colors.ts +77 -0
  44. package/src/db.ts +77 -0
  45. package/src/index.ts +1 -6
  46. package/src/parser.ts +55 -2
  47. package/src/server.ts +69 -17
  48. package/src/shared-types.ts +138 -0
  49. package/src/trace.ts +1002 -0
  50. 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
+ }
@@ -8,3 +8,4 @@ export * from "./ProjectsRoute";
8
8
  export * from "./ProvidersRoute";
9
9
  export * from "./RequestsRoute";
10
10
  export * from "./ToolsRoute";
11
+ export * from "./TracesRoute";