@tonyclaw/agent-inspector 2.1.15 → 2.1.17
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/.output/cli.js +57 -13
- package/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-B9sLBHw5.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
- package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-D9I9W9n3.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-DGXK_Rii.js} +1 -1
- package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-df_JzwsS.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
- package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
- package/.output/public/assets/index-Blqvndcn.js +1 -0
- package/.output/public/assets/index-Cs79WRQj.css +1 -0
- package/.output/public/assets/{index-DjKt8XKe.js → index-Dqc2r1ea.js} +1 -1
- package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-D3QWQlSB.js} +1 -1
- package/.output/public/assets/{main-DpD1N0S8.js → main-ZhxhOCmF.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +255 -201
- package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
- package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +2732 -1849
- package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-HQfrxjO1.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-QSlSD11e.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +3 -3
- package/.output/server/_ssr/{index-47XVPghS.mjs → index-D_EzfXaN.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
- package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-__o2wrfO.mjs} +505 -136
- package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
- package/.output/server/index.mjs +63 -63
- package/README.md +57 -2
- package/package.json +1 -1
- package/src/cli/startupOutput.ts +16 -0
- package/src/cli.ts +61 -15
- package/src/components/ProxyViewer.tsx +535 -163
- package/src/components/ProxyViewerContainer.tsx +77 -67
- package/src/components/alerts/AlertsDialog.tsx +2 -1
- package/src/components/clients/ClientLogo.tsx +1 -1
- package/src/components/groups/GroupsDialog.tsx +19 -1
- package/src/components/providers/ProviderCard.tsx +133 -32
- package/src/components/providers/ProviderForm.tsx +61 -29
- package/src/components/providers/ProviderLogo.tsx +6 -1
- package/src/components/providers/ProvidersPanel.tsx +7 -7
- package/src/components/providers/SettingsDialog.tsx +13 -6
- package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
- package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
- package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
- package/src/components/proxy-viewer/ConversationHeader.tsx +153 -120
- package/src/components/proxy-viewer/LogEntry.tsx +130 -43
- package/src/components/proxy-viewer/LogEntryHeader.tsx +357 -354
- package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
- package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
- package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
- package/src/components/proxy-viewer/TurnGroup.tsx +33 -69
- package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
- package/src/components/proxy-viewer/bodyHydration.ts +56 -0
- package/src/components/proxy-viewer/viewerState.ts +0 -4
- package/src/components/ui/icon-trigger.ts +2 -0
- package/src/components/ui/transient-toast.tsx +104 -0
- package/src/lib/providerTestContract.ts +56 -10
- package/src/lib/providerTestPrompt.ts +23 -1
- package/src/lib/sessionInfoContract.ts +4 -0
- package/src/lib/upstreamUrl.ts +105 -1
- package/src/mcp/server.ts +71 -27
- package/src/mcp/toolHandlers.ts +24 -5
- package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
- package/src/proxy/sessionInfo.ts +31 -2
- package/src/proxy/sessionSupervisor.ts +20 -1
- package/src/proxy/store.ts +1 -1
- package/src/routes/api/providers.$providerId.test.log.ts +93 -14
- package/styles/globals.css +180 -180
- package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
- package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
- package/.output/public/assets/index-CI1-G8ua.js +0 -1
- package/.output/public/assets/index-DdhFqPsI.css +0 -1
|
@@ -1,20 +1,52 @@
|
|
|
1
|
-
import { type JSX, useCallback,
|
|
2
|
-
import {
|
|
1
|
+
import { type JSX, useCallback, useMemo, useState } from "react";
|
|
2
|
+
import { ChevronDown, ChevronRight, Cpu, FolderOpen, Monitor } from "lucide-react";
|
|
3
3
|
import type { CapturedLog } from "../../contracts";
|
|
4
|
+
import { cn, formatTokens } from "../../lib/utils";
|
|
5
|
+
import { ClientLogo, clientAppLabel, detectClientApp, type ClientApp } from "../clients/ClientLogo";
|
|
4
6
|
import { ConversationGroup, type ConversationGroupProps } from "./ConversationGroup";
|
|
5
7
|
import type { ConversationGroupData } from "./ConversationHeader";
|
|
6
|
-
import { LOG_FOCUS_REQUEST_EVENT, readLogFocusRequest } from "./logFocus";
|
|
7
8
|
|
|
8
9
|
const VIRTUALIZE_GROUP_THRESHOLD = 30;
|
|
9
|
-
const ESTIMATED_GROUP_HEIGHT = 112;
|
|
10
|
-
const VIRTUAL_OVERSCAN = 6;
|
|
11
10
|
|
|
12
11
|
type ConversationGroupListProps = Omit<
|
|
13
12
|
ConversationGroupProps,
|
|
14
|
-
"group" | "
|
|
13
|
+
"group" | "expanded" | "onExpandedChange"
|
|
15
14
|
> & {
|
|
16
15
|
groups: ConversationGroupData[];
|
|
17
|
-
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ProcessGroupData = {
|
|
19
|
+
id: string;
|
|
20
|
+
clientPid: number | null;
|
|
21
|
+
clientProjectFolder: string | null;
|
|
22
|
+
sessions: ConversationGroupData[];
|
|
23
|
+
logs: CapturedLog[];
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type IdeGroupData = {
|
|
27
|
+
id: string;
|
|
28
|
+
client: ClientApp;
|
|
29
|
+
label: string;
|
|
30
|
+
userAgent: string | null;
|
|
31
|
+
processes: ProcessGroupData[];
|
|
32
|
+
logs: CapturedLog[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type MutableProcessGroup = {
|
|
36
|
+
id: string;
|
|
37
|
+
clientPid: number | null;
|
|
38
|
+
clientProjectFolder: string | null;
|
|
39
|
+
sessions: ConversationGroupData[];
|
|
40
|
+
logs: CapturedLog[];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type MutableIdeGroup = {
|
|
44
|
+
id: string;
|
|
45
|
+
client: ClientApp;
|
|
46
|
+
label: string;
|
|
47
|
+
userAgent: string | null;
|
|
48
|
+
processes: Map<string, MutableProcessGroup>;
|
|
49
|
+
logs: CapturedLog[];
|
|
18
50
|
};
|
|
19
51
|
|
|
20
52
|
export function shouldVirtualizeConversationGroups(groupCount: number): boolean {
|
|
@@ -34,111 +66,442 @@ export function findConversationGroupIndexForLog(
|
|
|
34
66
|
return null;
|
|
35
67
|
}
|
|
36
68
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
function firstUserAgent(logs: readonly CapturedLog[]): string | null {
|
|
70
|
+
for (const log of logs) {
|
|
71
|
+
const userAgent = log.userAgent;
|
|
72
|
+
if (userAgent !== null && userAgent !== undefined && userAgent !== "") return userAgent;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function firstClientPid(logs: readonly CapturedLog[]): number | null {
|
|
78
|
+
for (const log of logs) {
|
|
79
|
+
const clientPid = log.clientPid;
|
|
80
|
+
if (clientPid !== null && clientPid !== undefined) return clientPid;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function firstClientProjectFolder(logs: readonly CapturedLog[]): string | null {
|
|
86
|
+
for (const log of logs) {
|
|
87
|
+
const projectFolder = log.clientProjectFolder;
|
|
88
|
+
if (projectFolder !== null && projectFolder !== undefined && projectFolder !== "") {
|
|
89
|
+
return projectFolder;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function groupClient(group: ConversationGroupData): ClientApp {
|
|
96
|
+
return detectClientApp({
|
|
97
|
+
userAgent: firstUserAgent(group.logs),
|
|
98
|
+
conversationId: group.conversationId,
|
|
99
|
+
clientProjectFolder: firstClientProjectFolder(group.logs),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function fallbackClientLabel(userAgent: string | null): string {
|
|
104
|
+
if (userAgent === null || userAgent === "") return "Unknown IDE";
|
|
105
|
+
const firstToken = userAgent.split(/[ /;]/)[0] ?? "";
|
|
106
|
+
if (firstToken === "") return "Unknown IDE";
|
|
107
|
+
return firstToken;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function clientLabel(client: ClientApp, userAgent: string | null): string {
|
|
111
|
+
const knownLabel = clientAppLabel(client);
|
|
112
|
+
if (knownLabel !== null) return knownLabel;
|
|
113
|
+
return fallbackClientLabel(userAgent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function bucketKey(value: string | number | null): string {
|
|
117
|
+
if (value === null || value === "") return "unknown";
|
|
118
|
+
return String(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ideKey(client: ClientApp, userAgent: string | null, projectFolder: string | null): string {
|
|
122
|
+
if (client !== "unknown") return client;
|
|
123
|
+
return `unknown:${bucketKey(userAgent)}:${bucketKey(projectFolder)}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function processKey(clientPid: number | null, projectFolder: string | null): string {
|
|
127
|
+
return `${bucketKey(clientPid)}:${bucketKey(projectFolder)}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function sortLogsByTimestamp(logs: readonly CapturedLog[]): CapturedLog[] {
|
|
131
|
+
return [...logs].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function firstTimestamp(logs: readonly CapturedLog[]): string {
|
|
135
|
+
return sortLogsByTimestamp(logs)[0]?.timestamp ?? "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function sortByFirstLogTime<T extends { logs: readonly CapturedLog[] }>(items: readonly T[]): T[] {
|
|
139
|
+
return [...items].sort((a, b) => firstTimestamp(a.logs).localeCompare(firstTimestamp(b.logs)));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function buildConversationHierarchy(
|
|
143
|
+
groups: readonly ConversationGroupData[],
|
|
144
|
+
): IdeGroupData[] {
|
|
145
|
+
const ideGroups = new Map<string, MutableIdeGroup>();
|
|
146
|
+
|
|
147
|
+
for (const group of groups) {
|
|
148
|
+
const userAgent = firstUserAgent(group.logs);
|
|
149
|
+
const projectFolder = firstClientProjectFolder(group.logs);
|
|
150
|
+
const client = groupClient(group);
|
|
151
|
+
const currentIdeKey = ideKey(client, userAgent, projectFolder);
|
|
152
|
+
let ideGroup = ideGroups.get(currentIdeKey);
|
|
153
|
+
|
|
154
|
+
if (ideGroup === undefined) {
|
|
155
|
+
ideGroup = {
|
|
156
|
+
id: currentIdeKey,
|
|
157
|
+
client,
|
|
158
|
+
label: clientLabel(client, userAgent),
|
|
159
|
+
userAgent,
|
|
160
|
+
processes: new Map(),
|
|
161
|
+
logs: [],
|
|
162
|
+
};
|
|
163
|
+
ideGroups.set(currentIdeKey, ideGroup);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
ideGroup.logs.push(...group.logs);
|
|
167
|
+
|
|
168
|
+
const clientPid = firstClientPid(group.logs);
|
|
169
|
+
const currentProcessKey = `${currentIdeKey}:${processKey(clientPid, projectFolder)}`;
|
|
170
|
+
let processGroup = ideGroup.processes.get(currentProcessKey);
|
|
171
|
+
|
|
172
|
+
if (processGroup === undefined) {
|
|
173
|
+
processGroup = {
|
|
174
|
+
id: currentProcessKey,
|
|
175
|
+
clientPid,
|
|
176
|
+
clientProjectFolder: projectFolder,
|
|
177
|
+
sessions: [],
|
|
178
|
+
logs: [],
|
|
179
|
+
};
|
|
180
|
+
ideGroup.processes.set(currentProcessKey, processGroup);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
processGroup.sessions.push(group);
|
|
184
|
+
processGroup.logs.push(...group.logs);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const hierarchy: IdeGroupData[] = [];
|
|
188
|
+
|
|
189
|
+
for (const ideGroup of ideGroups.values()) {
|
|
190
|
+
const processes: ProcessGroupData[] = sortByFirstLogTime(
|
|
191
|
+
[...ideGroup.processes.values()].map((processGroup) => ({
|
|
192
|
+
id: processGroup.id,
|
|
193
|
+
clientPid: processGroup.clientPid,
|
|
194
|
+
clientProjectFolder: processGroup.clientProjectFolder,
|
|
195
|
+
sessions: sortByFirstLogTime(processGroup.sessions),
|
|
196
|
+
logs: sortLogsByTimestamp(processGroup.logs),
|
|
197
|
+
})),
|
|
49
198
|
);
|
|
199
|
+
|
|
200
|
+
hierarchy.push({
|
|
201
|
+
id: ideGroup.id,
|
|
202
|
+
client: ideGroup.client,
|
|
203
|
+
label: ideGroup.label,
|
|
204
|
+
userAgent: ideGroup.userAgent,
|
|
205
|
+
processes,
|
|
206
|
+
logs: sortLogsByTimestamp(ideGroup.logs),
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return sortByFirstLogTime(hierarchy);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function formatTokenCount(count: number): string {
|
|
214
|
+
if (count <= 0) return "0";
|
|
215
|
+
return formatTokens(count);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function tokenTotals(logs: readonly CapturedLog[]): {
|
|
219
|
+
inputTokens: number;
|
|
220
|
+
outputTokens: number;
|
|
221
|
+
} {
|
|
222
|
+
let inputTokens = 0;
|
|
223
|
+
let outputTokens = 0;
|
|
224
|
+
|
|
225
|
+
for (const log of logs) {
|
|
226
|
+
if (log.inputTokens !== null && log.inputTokens !== undefined) inputTokens += log.inputTokens;
|
|
227
|
+
if (log.outputTokens !== null && log.outputTokens !== undefined) {
|
|
228
|
+
outputTokens += log.outputTokens;
|
|
229
|
+
}
|
|
50
230
|
}
|
|
51
231
|
|
|
232
|
+
return { inputTokens, outputTokens };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function countSessions(processes: readonly ProcessGroupData[]): number {
|
|
236
|
+
let sessions = 0;
|
|
237
|
+
for (const processGroup of processes) {
|
|
238
|
+
sessions += processGroup.sessions.length;
|
|
239
|
+
}
|
|
240
|
+
return sessions;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function shortProjectPath(projectFolder: string | null): string | null {
|
|
244
|
+
if (projectFolder === null || projectFolder === "") return null;
|
|
245
|
+
const parts = projectFolder
|
|
246
|
+
.replaceAll("\\", "/")
|
|
247
|
+
.split("/")
|
|
248
|
+
.filter((part) => part !== "");
|
|
249
|
+
const leaf = parts[parts.length - 1];
|
|
250
|
+
if (leaf === undefined) return projectFolder;
|
|
251
|
+
const parent = parts[parts.length - 2];
|
|
252
|
+
if (parent === undefined) return leaf;
|
|
253
|
+
return `${parent}/${leaf}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function SummaryChip({
|
|
257
|
+
label,
|
|
258
|
+
value,
|
|
259
|
+
className,
|
|
260
|
+
}: {
|
|
261
|
+
label: string;
|
|
262
|
+
value: string;
|
|
263
|
+
className?: string;
|
|
264
|
+
}): JSX.Element {
|
|
52
265
|
return (
|
|
53
|
-
<
|
|
266
|
+
<span
|
|
267
|
+
className={cn(
|
|
268
|
+
"border border-border bg-muted/40 inline-flex h-7 items-center gap-1 rounded-md px-2 text-xs text-muted-foreground",
|
|
269
|
+
className,
|
|
270
|
+
)}
|
|
271
|
+
>
|
|
272
|
+
<span className="font-mono text-[10px] uppercase tracking-[0.12em] text-white/35">
|
|
273
|
+
{label}
|
|
274
|
+
</span>
|
|
275
|
+
<span className="font-mono tabular-nums text-slate-100">{value}</span>
|
|
276
|
+
</span>
|
|
54
277
|
);
|
|
55
278
|
}
|
|
56
279
|
|
|
57
|
-
function
|
|
280
|
+
export function ConversationGroupList({
|
|
58
281
|
groups,
|
|
59
|
-
standalone,
|
|
60
282
|
...groupProps
|
|
61
283
|
}: ConversationGroupListProps): JSX.Element {
|
|
62
|
-
const
|
|
63
|
-
|
|
284
|
+
const hierarchy = useMemo(() => buildConversationHierarchy(groups), [groups]);
|
|
285
|
+
const [collapsedIdeIds, setCollapsedIdeIds] = useState<ReadonlySet<string>>(() => new Set());
|
|
286
|
+
const [collapsedProcessIds, setCollapsedProcessIds] = useState<ReadonlySet<string>>(
|
|
287
|
+
() => new Set(),
|
|
64
288
|
);
|
|
65
289
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const setGroupExpanded = useCallback((groupId: string, expanded: boolean) => {
|
|
74
|
-
setExpandedByGroupId((previous) => {
|
|
75
|
-
const next = new Map(previous);
|
|
76
|
-
if (expanded) {
|
|
77
|
-
next.set(groupId, true);
|
|
290
|
+
const toggleIde = useCallback((id: string) => {
|
|
291
|
+
setCollapsedIdeIds((previous) => {
|
|
292
|
+
const next = new Set(previous);
|
|
293
|
+
if (next.has(id)) {
|
|
294
|
+
next.delete(id);
|
|
78
295
|
} else {
|
|
79
|
-
next.
|
|
296
|
+
next.add(id);
|
|
80
297
|
}
|
|
81
298
|
return next;
|
|
82
299
|
});
|
|
83
300
|
}, []);
|
|
84
301
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
302
|
+
const toggleProcess = useCallback((id: string) => {
|
|
303
|
+
setCollapsedProcessIds((previous) => {
|
|
304
|
+
const next = new Set(previous);
|
|
305
|
+
if (next.has(id)) {
|
|
306
|
+
next.delete(id);
|
|
307
|
+
} else {
|
|
308
|
+
next.add(id);
|
|
92
309
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}, [
|
|
96
|
-
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
const handleLogFocusRequest = (event: Event): void => {
|
|
99
|
-
const request = readLogFocusRequest(event);
|
|
100
|
-
if (request === null) return;
|
|
101
|
-
const index = indexByLogId.get(request.logId);
|
|
102
|
-
if (index === undefined) return;
|
|
103
|
-
const group = groups[index];
|
|
104
|
-
if (group === undefined) return;
|
|
105
|
-
setGroupExpanded(group.id, true);
|
|
106
|
-
virtualizer.scrollToIndex(index, { align: "center" });
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
window.addEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
110
|
-
return () => {
|
|
111
|
-
window.removeEventListener(LOG_FOCUS_REQUEST_EVENT, handleLogFocusRequest);
|
|
112
|
-
};
|
|
113
|
-
}, [groups, indexByLogId, setGroupExpanded, virtualizer]);
|
|
310
|
+
return next;
|
|
311
|
+
});
|
|
312
|
+
}, []);
|
|
114
313
|
|
|
115
314
|
return (
|
|
116
|
-
<div
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
315
|
+
<div className="space-y-4">
|
|
316
|
+
{hierarchy.map((ideGroup) => (
|
|
317
|
+
<IdeGroupSection
|
|
318
|
+
key={ideGroup.id}
|
|
319
|
+
group={ideGroup}
|
|
320
|
+
collapsed={collapsedIdeIds.has(ideGroup.id)}
|
|
321
|
+
collapsedProcessIds={collapsedProcessIds}
|
|
322
|
+
onToggleIde={() => toggleIde(ideGroup.id)}
|
|
323
|
+
onToggleProcess={toggleProcess}
|
|
324
|
+
conversationProps={groupProps}
|
|
325
|
+
/>
|
|
326
|
+
))}
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function IdeGroupSection({
|
|
332
|
+
group,
|
|
333
|
+
collapsed,
|
|
334
|
+
collapsedProcessIds,
|
|
335
|
+
onToggleIde,
|
|
336
|
+
onToggleProcess,
|
|
337
|
+
conversationProps,
|
|
338
|
+
}: {
|
|
339
|
+
group: IdeGroupData;
|
|
340
|
+
collapsed: boolean;
|
|
341
|
+
collapsedProcessIds: ReadonlySet<string>;
|
|
342
|
+
onToggleIde: () => void;
|
|
343
|
+
onToggleProcess: (id: string) => void;
|
|
344
|
+
conversationProps: Omit<ConversationGroupProps, "group" | "expanded" | "onExpandedChange">;
|
|
345
|
+
}): JSX.Element {
|
|
346
|
+
const totals = tokenTotals(group.logs);
|
|
347
|
+
const sessionCount = countSessions(group.processes);
|
|
348
|
+
const logCount = group.logs.length;
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<section
|
|
352
|
+
className={cn("border border-border bg-card shadow-sm relative isolate rounded-[8px]")}
|
|
353
|
+
data-ide-group={group.id}
|
|
120
354
|
>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
className="
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
355
|
+
<button
|
|
356
|
+
type="button"
|
|
357
|
+
className="flex w-full min-w-0 items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-white/[0.03] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300/45"
|
|
358
|
+
onClick={onToggleIde}
|
|
359
|
+
aria-expanded={!collapsed}
|
|
360
|
+
>
|
|
361
|
+
{group.client === "unknown" ? (
|
|
362
|
+
<span className="border border-border bg-muted/40 inline-flex size-10 shrink-0 items-center justify-center rounded-[8px] text-slate-200">
|
|
363
|
+
<Monitor className="size-5" />
|
|
364
|
+
</span>
|
|
365
|
+
) : (
|
|
366
|
+
<ClientLogo client={group.client} className="size-10 rounded-[8px] text-cyan-100" />
|
|
367
|
+
)}
|
|
368
|
+
|
|
369
|
+
<span className="min-w-0 flex-1">
|
|
370
|
+
<span className="flex min-w-0 items-center gap-2">
|
|
371
|
+
<span
|
|
372
|
+
className="truncate text-sm font-semibold text-slate-50"
|
|
373
|
+
title={group.userAgent ?? group.label}
|
|
374
|
+
>
|
|
375
|
+
{group.label}
|
|
376
|
+
</span>
|
|
377
|
+
<span className="rounded border border-cyan-200/15 bg-cyan-300/[0.06] px-1.5 py-0.5 font-mono text-[10px] uppercase tracking-[0.16em] text-cyan-100/75">
|
|
378
|
+
IDE
|
|
379
|
+
</span>
|
|
380
|
+
</span>
|
|
381
|
+
<span className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
|
382
|
+
<span>
|
|
383
|
+
{group.processes.length} process{group.processes.length !== 1 ? "es" : ""}
|
|
384
|
+
</span>
|
|
385
|
+
<span>
|
|
386
|
+
{sessionCount} session{sessionCount !== 1 ? "s" : ""}
|
|
387
|
+
</span>
|
|
388
|
+
<span>
|
|
389
|
+
{logCount} log{logCount !== 1 ? "s" : ""}
|
|
390
|
+
</span>
|
|
391
|
+
</span>
|
|
392
|
+
</span>
|
|
393
|
+
|
|
394
|
+
<span className="hidden shrink-0 items-center gap-2 md:flex">
|
|
395
|
+
<SummaryChip label="IN" value={formatTokenCount(totals.inputTokens)} />
|
|
396
|
+
<SummaryChip label="OUT" value={formatTokenCount(totals.outputTokens)} />
|
|
397
|
+
</span>
|
|
398
|
+
|
|
399
|
+
<span className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-8 shrink-0 items-center justify-center rounded-md text-slate-200">
|
|
400
|
+
{collapsed ? <ChevronRight className="size-4" /> : <ChevronDown className="size-4" />}
|
|
401
|
+
</span>
|
|
402
|
+
</button>
|
|
403
|
+
|
|
404
|
+
{!collapsed && (
|
|
405
|
+
<div className="space-y-3 border-t border-white/10 px-3 pb-3 pt-3 sm:px-4">
|
|
406
|
+
{group.processes.map((processGroup) => (
|
|
407
|
+
<ProcessGroupSection
|
|
408
|
+
key={processGroup.id}
|
|
409
|
+
group={processGroup}
|
|
410
|
+
collapsed={collapsedProcessIds.has(processGroup.id)}
|
|
411
|
+
onToggle={() => onToggleProcess(processGroup.id)}
|
|
412
|
+
conversationProps={conversationProps}
|
|
138
413
|
/>
|
|
414
|
+
))}
|
|
415
|
+
</div>
|
|
416
|
+
)}
|
|
417
|
+
</section>
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function ProcessGroupSection({
|
|
422
|
+
group,
|
|
423
|
+
collapsed,
|
|
424
|
+
onToggle,
|
|
425
|
+
conversationProps,
|
|
426
|
+
}: {
|
|
427
|
+
group: ProcessGroupData;
|
|
428
|
+
collapsed: boolean;
|
|
429
|
+
onToggle: () => void;
|
|
430
|
+
conversationProps: Omit<ConversationGroupProps, "group" | "expanded" | "onExpandedChange">;
|
|
431
|
+
}): JSX.Element {
|
|
432
|
+
const totals = tokenTotals(group.logs);
|
|
433
|
+
const projectLabel = shortProjectPath(group.clientProjectFolder);
|
|
434
|
+
const sessionCount = group.sessions.length;
|
|
435
|
+
const logCount = group.logs.length;
|
|
436
|
+
const processLabel =
|
|
437
|
+
group.clientPid !== null && group.clientPid !== undefined
|
|
438
|
+
? `PID ${group.clientPid}`
|
|
439
|
+
: "process";
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<section className="relative pl-5">
|
|
443
|
+
<span className="absolute bottom-0 left-2 top-0 w-px bg-white/12" aria-hidden="true" />
|
|
444
|
+
<div className="border border-border bg-card shadow-sm overflow-hidden rounded-[8px]">
|
|
445
|
+
<button
|
|
446
|
+
type="button"
|
|
447
|
+
className="flex w-full min-w-0 items-center gap-3 px-3 py-2.5 text-left transition-colors hover:bg-white/[0.035] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-cyan-300/40"
|
|
448
|
+
onClick={onToggle}
|
|
449
|
+
aria-expanded={!collapsed}
|
|
450
|
+
>
|
|
451
|
+
<span className="border border-border bg-muted/40 inline-flex size-8 shrink-0 items-center justify-center rounded-md text-amber-100">
|
|
452
|
+
<Cpu className="size-4" />
|
|
453
|
+
</span>
|
|
454
|
+
|
|
455
|
+
<span className="min-w-0 flex-1">
|
|
456
|
+
<span className="flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1">
|
|
457
|
+
<span className="font-mono text-xs font-semibold tabular-nums text-slate-100">
|
|
458
|
+
{processLabel}
|
|
459
|
+
</span>
|
|
460
|
+
{projectLabel !== null && (
|
|
461
|
+
<span
|
|
462
|
+
className="inline-flex min-w-0 items-center gap-1 text-xs text-muted-foreground"
|
|
463
|
+
title={group.clientProjectFolder ?? projectLabel}
|
|
464
|
+
>
|
|
465
|
+
<FolderOpen className="size-3 shrink-0" />
|
|
466
|
+
<span className="truncate font-mono">{projectLabel}</span>
|
|
467
|
+
</span>
|
|
468
|
+
)}
|
|
469
|
+
</span>
|
|
470
|
+
<span className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
|
471
|
+
<span>
|
|
472
|
+
{sessionCount} session{sessionCount !== 1 ? "s" : ""}
|
|
473
|
+
</span>
|
|
474
|
+
<span>
|
|
475
|
+
{logCount} log{logCount !== 1 ? "s" : ""}
|
|
476
|
+
</span>
|
|
477
|
+
</span>
|
|
478
|
+
</span>
|
|
479
|
+
|
|
480
|
+
<span className="hidden shrink-0 items-center gap-2 md:flex">
|
|
481
|
+
<SummaryChip label="IN" value={formatTokenCount(totals.inputTokens)} />
|
|
482
|
+
<SummaryChip label="OUT" value={formatTokenCount(totals.outputTokens)} />
|
|
483
|
+
</span>
|
|
484
|
+
|
|
485
|
+
<span className="border border-input bg-background hover:bg-accent hover:text-accent-foreground inline-flex size-7 shrink-0 items-center justify-center rounded-md text-slate-200">
|
|
486
|
+
{collapsed ? <ChevronRight className="size-4" /> : <ChevronDown className="size-4" />}
|
|
487
|
+
</span>
|
|
488
|
+
</button>
|
|
489
|
+
|
|
490
|
+
{!collapsed && (
|
|
491
|
+
<div className="space-y-2 border-t border-white/10 p-2 sm:p-3">
|
|
492
|
+
{group.sessions.map((session) => (
|
|
493
|
+
<ConversationGroup
|
|
494
|
+
key={session.id}
|
|
495
|
+
group={session}
|
|
496
|
+
{...conversationProps}
|
|
497
|
+
showClientIdentity={false}
|
|
498
|
+
showLogClientMetadata={false}
|
|
499
|
+
showProcessMetadata={false}
|
|
500
|
+
/>
|
|
501
|
+
))}
|
|
139
502
|
</div>
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
</
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
</section>
|
|
143
506
|
);
|
|
144
507
|
}
|