@tonyclaw/agent-inspector 3.0.36 → 3.0.37
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/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BF8Yk4Nq.js → CompareDrawer-BvF04FAX.js} +1 -1
- package/.output/public/assets/{InspectorPet-D01knLHJ.js → InspectorPet-CSCn4jWU.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-9mjL1YlZ.js +126 -0
- package/.output/public/assets/{ReplayDialog-NRgCp09o.js → ReplayDialog-DWJkKTDB.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-BPiUptQ-.js → RequestAnatomy-CDFI55pJ.js} +1 -1
- package/.output/public/assets/{ResponseView-C6oCKVyP.js → ResponseView-tlIj9vRr.js} +1 -1
- package/.output/public/assets/{StreamingChunkSequence-B90BNPvD.js → StreamingChunkSequence-C7Ntivbf.js} +1 -1
- package/.output/public/assets/{_sessionId-c3quqe4s.js → _sessionId-BbrBvkvn.js} +1 -1
- package/.output/public/assets/{_sessionId-B7pqqX6S.js → _sessionId-DUL-l56X.js} +1 -1
- package/.output/public/assets/{index-D4Atppe6.js → index-By0CyDmk.js} +1 -1
- package/.output/public/assets/{index-D444FJGM.js → index-CEdulUJ3.js} +3 -3
- package/.output/public/assets/index-DCj1X8Qt.css +1 -0
- package/.output/public/assets/{index-CmGZ17BL.js → index-DF6bouqm.js} +1 -1
- package/.output/public/assets/{index-CjODQxYY.js → index-e-fWO2Du.js} +1 -1
- package/.output/public/assets/{json-viewer-CuYTDXFj.js → json-viewer-0fOrL_Cz.js} +1 -1
- package/.output/public/assets/{jszip.min-C-avw0w1.js → jszip.min-2Bsoez0Q.js} +1 -1
- package/.output/server/_libs/lucide-react.mjs +65 -65
- package/.output/server/_libs/radix-ui__react-select.mjs +7 -7
- package/.output/server/{_sessionId-BsDZ3m3b.mjs → _sessionId-D-GcecKN.mjs} +2 -2
- package/.output/server/{_sessionId-BTazjVlZ.mjs → _sessionId-DzY1bB_L.mjs} +1 -1
- package/.output/server/_ssr/{CompareDrawer-BMo93_DO.mjs → CompareDrawer-BXm8BitS.mjs} +3 -3
- package/.output/server/_ssr/{InspectorPet-DApzyYro.mjs → InspectorPet-CZUOr9sO.mjs} +1 -1
- package/.output/server/_ssr/{ProxyViewerContainer-CiXXtVXH.mjs → ProxyViewerContainer-BAQY5x_x.mjs} +120 -281
- package/.output/server/_ssr/{ReplayDialog-B8bP1qUr.mjs → ReplayDialog-BOODYnLb.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-DkVynjyh.mjs → RequestAnatomy-Bd2OBiJP.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-BAQa0WUP.mjs → ResponseView-ZSjpdHLG.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-B4_K4_VI.mjs → StreamingChunkSequence-BT-yQGLL.mjs} +3 -3
- package/.output/server/_ssr/{index-DhjNsApq.mjs → index-CSJlUfwz.mjs} +1 -1
- package/.output/server/_ssr/{index-BiwG5Sws.mjs → index-CXouA5fR.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-Dlpq5S-r.mjs → json-viewer-DF2oLr4x.mjs} +3 -3
- package/.output/server/_ssr/{router-6s7p8rsX.mjs → router-75eycvXZ.mjs} +6 -6
- package/.output/server/{_tanstack-start-manifest_v-ClIF-cfh.mjs → _tanstack-start-manifest_v-DjCobDeN.mjs} +1 -1
- package/.output/server/index.mjs +87 -87
- package/package.json +7 -1
- package/src/components/ProxyViewer.tsx +96 -328
- package/src/components/pi-agent/PiAgentPanel.tsx +23 -14
- package/src/components/pi-agent/piAgentChatLogic.ts +10 -0
- package/.output/public/assets/ProxyViewerContainer-CArcMNg_.js +0 -126
- package/.output/public/assets/index-BQqVblQv.css +0 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type ChangeEvent,
|
|
3
3
|
type JSX,
|
|
4
|
-
type KeyboardEvent,
|
|
5
4
|
type ReactNode,
|
|
6
5
|
useCallback,
|
|
7
6
|
useEffect,
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
PanelRightClose,
|
|
33
32
|
PanelRightOpen,
|
|
34
33
|
RefreshCw,
|
|
35
|
-
Search,
|
|
36
34
|
Siren,
|
|
37
35
|
Moon,
|
|
38
36
|
Sun,
|
|
@@ -79,7 +77,7 @@ import { AlertsDialog } from "./alerts/AlertsDialog";
|
|
|
79
77
|
import { GroupsDialog } from "./groups/GroupsDialog";
|
|
80
78
|
import { SettingsDialog } from "./providers/SettingsDialog";
|
|
81
79
|
import { AgentLabDialog } from "./ecosystem/AgentLabDialog";
|
|
82
|
-
import { PiAgentPanel } from "./pi-agent/PiAgentPanel";
|
|
80
|
+
import { PiAgentPanel, type PiAgentSearchResult } from "./pi-agent/PiAgentPanel";
|
|
83
81
|
import { LazyFeatureBoundary } from "./errors/SafeErrorBoundary";
|
|
84
82
|
import { computeCacheTrends } from "./proxy-viewer/cacheTrend";
|
|
85
83
|
import { LazyCompareDrawer, LazyInspectorPet } from "./proxy-viewer/lazy";
|
|
@@ -116,228 +114,6 @@ function truncateSessionId(id: string): string {
|
|
|
116
114
|
return `${id.slice(0, 12)}...${id.slice(-12)}`;
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
type SessionSearchSuggestion = {
|
|
120
|
-
id: string;
|
|
121
|
-
label: string;
|
|
122
|
-
detail: string;
|
|
123
|
-
kind: "session" | "model" | "all";
|
|
124
|
-
value: string;
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
function normalizeSearchQuery(value: string): string {
|
|
128
|
-
return value.trim().toLowerCase();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function includesSearchText(value: string | null | undefined, query: string): boolean {
|
|
132
|
-
return value !== null && value !== undefined && value.toLowerCase().includes(query);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function buildSessionSuggestionDetail(sessionId: string, logs: readonly CapturedLog[]): string {
|
|
136
|
-
const scopedLogs = logs.filter((log) => log.sessionId === sessionId);
|
|
137
|
-
if (scopedLogs.length === 0) return "Stored session";
|
|
138
|
-
const models = new Set<string>();
|
|
139
|
-
const providers = new Set<string>();
|
|
140
|
-
for (const log of scopedLogs) {
|
|
141
|
-
if (log.model !== null && log.model !== "") models.add(log.model);
|
|
142
|
-
if (
|
|
143
|
-
log.providerName !== null &&
|
|
144
|
-
log.providerName !== undefined &&
|
|
145
|
-
log.providerName.trim() !== ""
|
|
146
|
-
) {
|
|
147
|
-
providers.add(log.providerName);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const modelLabel = models.size === 0 ? "unknown model" : [...models].slice(0, 2).join(", ");
|
|
151
|
-
const providerLabel =
|
|
152
|
-
providers.size === 0 ? "unknown provider" : [...providers].slice(0, 2).join(", ");
|
|
153
|
-
return `${String(scopedLogs.length)} req · ${providerLabel} · ${modelLabel}`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function sessionMatchesSearchQuery(
|
|
157
|
-
sessionId: string,
|
|
158
|
-
logs: readonly CapturedLog[],
|
|
159
|
-
query: string,
|
|
160
|
-
): boolean {
|
|
161
|
-
if (includesSearchText(sessionId, query)) return true;
|
|
162
|
-
return logs.some(
|
|
163
|
-
(log) =>
|
|
164
|
-
log.sessionId === sessionId &&
|
|
165
|
-
(includesSearchText(log.model, query) ||
|
|
166
|
-
includesSearchText(log.providerName, query) ||
|
|
167
|
-
includesSearchText(log.path, query)),
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function buildSessionSearchSuggestions({
|
|
172
|
-
query,
|
|
173
|
-
sessions,
|
|
174
|
-
models,
|
|
175
|
-
logs,
|
|
176
|
-
}: {
|
|
177
|
-
query: string;
|
|
178
|
-
sessions: readonly string[];
|
|
179
|
-
models: readonly string[];
|
|
180
|
-
logs: readonly CapturedLog[];
|
|
181
|
-
}): SessionSearchSuggestion[] {
|
|
182
|
-
const normalizedQuery = normalizeSearchQuery(query);
|
|
183
|
-
const suggestions: SessionSearchSuggestion[] = [];
|
|
184
|
-
|
|
185
|
-
if (normalizedQuery === "") {
|
|
186
|
-
suggestions.push({
|
|
187
|
-
id: "all",
|
|
188
|
-
label: "All sessions",
|
|
189
|
-
detail: `${String(logs.length)} visible requests`,
|
|
190
|
-
kind: "all",
|
|
191
|
-
value: "__all__",
|
|
192
|
-
});
|
|
193
|
-
for (const session of sessions.slice(0, 5)) {
|
|
194
|
-
suggestions.push({
|
|
195
|
-
id: `session-${session}`,
|
|
196
|
-
label: truncateSessionId(session),
|
|
197
|
-
detail: buildSessionSuggestionDetail(session, logs),
|
|
198
|
-
kind: "session",
|
|
199
|
-
value: session,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return suggestions;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const matchingSessions = sessions.filter((session) =>
|
|
206
|
-
sessionMatchesSearchQuery(session, logs, normalizedQuery),
|
|
207
|
-
);
|
|
208
|
-
for (const session of matchingSessions.slice(0, 5)) {
|
|
209
|
-
suggestions.push({
|
|
210
|
-
id: `session-${session}`,
|
|
211
|
-
label: truncateSessionId(session),
|
|
212
|
-
detail: buildSessionSuggestionDetail(session, logs),
|
|
213
|
-
kind: "session",
|
|
214
|
-
value: session,
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const matchingModels = models.filter((model) => includesSearchText(model, normalizedQuery));
|
|
219
|
-
for (const model of matchingModels.slice(0, 3)) {
|
|
220
|
-
suggestions.push({
|
|
221
|
-
id: `model-${model}`,
|
|
222
|
-
label: model,
|
|
223
|
-
detail: "Model filter",
|
|
224
|
-
kind: "model",
|
|
225
|
-
value: model,
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return suggestions.slice(0, 7);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function buildAssistantQuestionFromSearch(query: string, selectedSession: string): string {
|
|
233
|
-
const trimmed = query.trim();
|
|
234
|
-
const scope =
|
|
235
|
-
selectedSession === "__all__" ? "all visible Inspector sessions" : `session ${selectedSession}`;
|
|
236
|
-
if (trimmed === "") {
|
|
237
|
-
return `Analyze ${scope}. What failed, slowed down, or deserves attention?`;
|
|
238
|
-
}
|
|
239
|
-
return `Search ${scope} for "${trimmed}" and explain the relevant failures, latency, token usage, provider behavior, and evidence.`;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function SessionSearchBox({
|
|
243
|
-
query,
|
|
244
|
-
suggestions,
|
|
245
|
-
selectedSession,
|
|
246
|
-
selectedModel,
|
|
247
|
-
onQueryChange,
|
|
248
|
-
onApplySuggestion,
|
|
249
|
-
onClear,
|
|
250
|
-
onAskAssistant,
|
|
251
|
-
}: {
|
|
252
|
-
query: string;
|
|
253
|
-
suggestions: readonly SessionSearchSuggestion[];
|
|
254
|
-
selectedSession: string;
|
|
255
|
-
selectedModel: string;
|
|
256
|
-
onQueryChange: (value: string) => void;
|
|
257
|
-
onApplySuggestion: (suggestion: SessionSearchSuggestion) => void;
|
|
258
|
-
onClear: () => void;
|
|
259
|
-
onAskAssistant: () => void;
|
|
260
|
-
}): JSX.Element {
|
|
261
|
-
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {
|
|
262
|
-
if (event.key !== "Enter") return;
|
|
263
|
-
const firstSuggestion = suggestions[0];
|
|
264
|
-
if (firstSuggestion === undefined) return;
|
|
265
|
-
event.preventDefault();
|
|
266
|
-
onApplySuggestion(firstSuggestion);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
return (
|
|
270
|
-
<div className="rounded-xl bg-black/20 p-2 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.055)]">
|
|
271
|
-
<div className="relative">
|
|
272
|
-
<Search className="pointer-events-none absolute left-3 top-1/2 size-3.5 -translate-y-1/2 text-cyan-100/70" />
|
|
273
|
-
<input
|
|
274
|
-
type="search"
|
|
275
|
-
value={query}
|
|
276
|
-
onChange={(event) => onQueryChange(event.currentTarget.value)}
|
|
277
|
-
onKeyDown={handleKeyDown}
|
|
278
|
-
placeholder="Search sessions, models, providers..."
|
|
279
|
-
className="h-10 w-full rounded-full bg-black/30 px-9 pr-9 text-xs text-foreground outline-none shadow-[inset_0_0_0_1px_rgba(103,232,249,0.13)] transition-colors placeholder:text-muted-foreground hover:bg-black/35 focus-visible:ring-1 focus-visible:ring-cyan-200/70"
|
|
280
|
-
aria-label="Search sessions and models"
|
|
281
|
-
/>
|
|
282
|
-
{(query !== "" || selectedSession !== "__all__" || selectedModel !== "__all__") && (
|
|
283
|
-
<button
|
|
284
|
-
type="button"
|
|
285
|
-
onClick={onClear}
|
|
286
|
-
className="absolute right-2 top-1/2 inline-flex size-6 -translate-y-1/2 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-white/[0.07] hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
|
|
287
|
-
aria-label="Clear session search"
|
|
288
|
-
title="Clear search"
|
|
289
|
-
>
|
|
290
|
-
<X className="size-3.5" />
|
|
291
|
-
</button>
|
|
292
|
-
)}
|
|
293
|
-
</div>
|
|
294
|
-
|
|
295
|
-
<div className="mt-2 flex flex-wrap gap-1.5">
|
|
296
|
-
<button
|
|
297
|
-
type="button"
|
|
298
|
-
onClick={onAskAssistant}
|
|
299
|
-
className="inline-flex h-7 items-center rounded-full bg-cyan-400/10 px-2.5 text-[11px] font-medium text-cyan-100 shadow-[inset_0_0_0_1px_rgba(103,232,249,0.14)] transition-colors hover:bg-cyan-400/16"
|
|
300
|
-
>
|
|
301
|
-
Ask Assistant
|
|
302
|
-
</button>
|
|
303
|
-
{selectedSession !== "__all__" && (
|
|
304
|
-
<span className="inline-flex h-7 max-w-full items-center rounded-full bg-white/[0.045] px-2.5 font-mono text-[10px] text-muted-foreground">
|
|
305
|
-
session:{truncateSessionId(selectedSession)}
|
|
306
|
-
</span>
|
|
307
|
-
)}
|
|
308
|
-
{selectedModel !== "__all__" && (
|
|
309
|
-
<span className="inline-flex h-7 max-w-full items-center rounded-full bg-white/[0.045] px-2.5 text-[10px] text-muted-foreground">
|
|
310
|
-
model:{selectedModel}
|
|
311
|
-
</span>
|
|
312
|
-
)}
|
|
313
|
-
</div>
|
|
314
|
-
|
|
315
|
-
<div className="mt-2 space-y-1">
|
|
316
|
-
{suggestions.map((suggestion) => (
|
|
317
|
-
<button
|
|
318
|
-
key={suggestion.id}
|
|
319
|
-
type="button"
|
|
320
|
-
onClick={() => onApplySuggestion(suggestion)}
|
|
321
|
-
className="grid w-full grid-cols-[minmax(0,1fr)_auto] items-center gap-2 rounded-lg px-2 py-1.5 text-left transition-colors hover:bg-white/[0.045] focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
|
|
322
|
-
>
|
|
323
|
-
<span className="min-w-0">
|
|
324
|
-
<span className="block truncate text-[11px] font-medium text-foreground">
|
|
325
|
-
{suggestion.label}
|
|
326
|
-
</span>
|
|
327
|
-
<span className="block truncate text-[10px] text-muted-foreground">
|
|
328
|
-
{suggestion.detail}
|
|
329
|
-
</span>
|
|
330
|
-
</span>
|
|
331
|
-
<span className="rounded-full bg-white/[0.045] px-2 py-0.5 text-[9px] uppercase tracking-[0.12em] text-muted-foreground">
|
|
332
|
-
{suggestion.kind}
|
|
333
|
-
</span>
|
|
334
|
-
</button>
|
|
335
|
-
))}
|
|
336
|
-
</div>
|
|
337
|
-
</div>
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
117
|
function buildDisplayNumberByLogId(groups: readonly ConversationGroupData[]): Map<number, number> {
|
|
342
118
|
const result = new Map<number, number>();
|
|
343
119
|
for (const group of groups) {
|
|
@@ -1514,11 +1290,9 @@ export type ProxyViewerProps = {
|
|
|
1514
1290
|
export function ProxyViewer({
|
|
1515
1291
|
logs,
|
|
1516
1292
|
sessions,
|
|
1517
|
-
models,
|
|
1518
1293
|
selectedSession,
|
|
1519
1294
|
selectedModel,
|
|
1520
1295
|
onSessionChange,
|
|
1521
|
-
onModelChange,
|
|
1522
1296
|
onClearAll,
|
|
1523
1297
|
onDeleteCurrentView,
|
|
1524
1298
|
onReloadSession,
|
|
@@ -1556,9 +1330,8 @@ export function ProxyViewer({
|
|
|
1556
1330
|
const [detailsPanelStorageLoaded, setDetailsPanelStorageLoaded] = useState(false);
|
|
1557
1331
|
const [petVisible, setPetVisible] = useState(true);
|
|
1558
1332
|
const [petVisibilityStorageLoaded, setPetVisibilityStorageLoaded] = useState(false);
|
|
1559
|
-
const [
|
|
1560
|
-
|
|
1561
|
-
undefined,
|
|
1333
|
+
const [assistantSearchResult, setAssistantSearchResult] = useState<PiAgentSearchResult | null>(
|
|
1334
|
+
null,
|
|
1562
1335
|
);
|
|
1563
1336
|
const [comparePair, setComparePair] = useState<[CapturedLog, CapturedLog] | null>(null);
|
|
1564
1337
|
const logListRef = useRef<HTMLDivElement>(null);
|
|
@@ -1710,6 +1483,7 @@ export function ProxyViewer({
|
|
|
1710
1483
|
useEffect(() => {
|
|
1711
1484
|
cancelTransientTasks();
|
|
1712
1485
|
setComparePair(null);
|
|
1486
|
+
setAssistantSearchResult(null);
|
|
1713
1487
|
}, [cancelTransientTasks, selectedSession, selectedModel]);
|
|
1714
1488
|
|
|
1715
1489
|
const handleClearCurrentView = useCallback(() => {
|
|
@@ -1770,7 +1544,18 @@ export function ProxyViewer({
|
|
|
1770
1544
|
setPetVisible((value) => !value);
|
|
1771
1545
|
}, []);
|
|
1772
1546
|
|
|
1773
|
-
const
|
|
1547
|
+
const assistantResultLogIds = useMemo(
|
|
1548
|
+
() => (assistantSearchResult === null ? null : new Set<number>(assistantSearchResult.logIds)),
|
|
1549
|
+
[assistantSearchResult],
|
|
1550
|
+
);
|
|
1551
|
+
const displayedLogs = useMemo(
|
|
1552
|
+
() =>
|
|
1553
|
+
assistantResultLogIds === null
|
|
1554
|
+
? logs
|
|
1555
|
+
: logs.filter((log) => assistantResultLogIds.has(log.id)),
|
|
1556
|
+
[assistantResultLogIds, logs],
|
|
1557
|
+
);
|
|
1558
|
+
const groups = useMemo(() => groupLogsByConversation(displayedLogs), [displayedLogs]);
|
|
1774
1559
|
const displayNumberByLogId = useMemo(() => buildDisplayNumberByLogId(groups), [groups]);
|
|
1775
1560
|
const sessionContextScope = useMemo(
|
|
1776
1561
|
() => resolveSessionContextScope({ pinnedSessionId, selectedSession, sessions, logs }),
|
|
@@ -1799,16 +1584,6 @@ export function ProxyViewer({
|
|
|
1799
1584
|
? "Deletes every stored log, streaming chunk, and session archive entry across all sessions."
|
|
1800
1585
|
: "Deletes every stored log, streaming chunk, and session archive entry for this session, including history that is not currently loaded.";
|
|
1801
1586
|
const reloadSessionLabel = logs.length === 0 ? "Load" : "Reload";
|
|
1802
|
-
const sessionSearchSuggestions = useMemo(
|
|
1803
|
-
() =>
|
|
1804
|
-
buildSessionSearchSuggestions({
|
|
1805
|
-
query: sessionSearchQuery,
|
|
1806
|
-
sessions,
|
|
1807
|
-
models,
|
|
1808
|
-
logs,
|
|
1809
|
-
}),
|
|
1810
|
-
[logs, models, sessionSearchQuery, sessions],
|
|
1811
|
-
);
|
|
1812
1587
|
const activeScopeLabel =
|
|
1813
1588
|
hasSessionContext && sessionContextScope !== null
|
|
1814
1589
|
? truncateSessionId(sessionContextScope.label)
|
|
@@ -1820,13 +1595,13 @@ export function ProxyViewer({
|
|
|
1820
1595
|
const slateStats = useMemo(
|
|
1821
1596
|
() =>
|
|
1822
1597
|
buildSessionSlateStats({
|
|
1823
|
-
logs,
|
|
1598
|
+
logs: displayedLogs,
|
|
1824
1599
|
conversationGroups: groups,
|
|
1825
1600
|
isLoading,
|
|
1826
1601
|
timeDisplayFormat,
|
|
1827
1602
|
slowResponseThresholdSeconds,
|
|
1828
1603
|
}),
|
|
1829
|
-
[groups, isLoading,
|
|
1604
|
+
[displayedLogs, groups, isLoading, slowResponseThresholdSeconds, timeDisplayFormat],
|
|
1830
1605
|
);
|
|
1831
1606
|
const handleCompareWithPrevious = useCallback(
|
|
1832
1607
|
(log: CapturedLog) => {
|
|
@@ -1835,36 +1610,6 @@ export function ProxyViewer({
|
|
|
1835
1610
|
},
|
|
1836
1611
|
[comparisonPredecessors],
|
|
1837
1612
|
);
|
|
1838
|
-
const handleApplySessionSearchSuggestion = useCallback(
|
|
1839
|
-
(suggestion: SessionSearchSuggestion) => {
|
|
1840
|
-
switch (suggestion.kind) {
|
|
1841
|
-
case "all":
|
|
1842
|
-
onSessionChange("__all__");
|
|
1843
|
-
onModelChange("__all__");
|
|
1844
|
-
setSessionSearchQuery("");
|
|
1845
|
-
break;
|
|
1846
|
-
case "session":
|
|
1847
|
-
onSessionChange(suggestion.value);
|
|
1848
|
-
setSessionSearchQuery(suggestion.value);
|
|
1849
|
-
break;
|
|
1850
|
-
case "model":
|
|
1851
|
-
onModelChange(suggestion.value);
|
|
1852
|
-
setSessionSearchQuery(suggestion.value);
|
|
1853
|
-
break;
|
|
1854
|
-
}
|
|
1855
|
-
},
|
|
1856
|
-
[onModelChange, onSessionChange],
|
|
1857
|
-
);
|
|
1858
|
-
const handleClearSessionSearch = useCallback(() => {
|
|
1859
|
-
setSessionSearchQuery("");
|
|
1860
|
-
onSessionChange("__all__");
|
|
1861
|
-
onModelChange("__all__");
|
|
1862
|
-
}, [onModelChange, onSessionChange]);
|
|
1863
|
-
const handleAskAssistantFromSearch = useCallback(() => {
|
|
1864
|
-
setAssistantSuggestedQuestion(
|
|
1865
|
-
buildAssistantQuestionFromSearch(sessionSearchQuery, selectedSession),
|
|
1866
|
-
);
|
|
1867
|
-
}, [selectedSession, sessionSearchQuery]);
|
|
1868
1613
|
const labSessionId =
|
|
1869
1614
|
pinnedSessionId !== undefined
|
|
1870
1615
|
? pinnedSessionId
|
|
@@ -1916,22 +1661,29 @@ export function ProxyViewer({
|
|
|
1916
1661
|
{!detailsPanelCollapsed && (
|
|
1917
1662
|
<aside className="bg-background/95 order-1 border-b border-white/[0.045] lg:order-2 lg:h-full lg:overflow-y-auto lg:border-b-0 lg:border-l lg:border-white/[0.055] inspector-scrollbar">
|
|
1918
1663
|
<div className="space-y-4 p-4">
|
|
1919
|
-
<
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
resolvedScale={resolvedUiScale}
|
|
1925
|
-
onChange={setUiScalePreference}
|
|
1926
|
-
/>
|
|
1927
|
-
<GroupsDialog />
|
|
1928
|
-
<AgentLabDialog currentSessionId={labSessionId} logCount={logs.length} />
|
|
1929
|
-
</div>
|
|
1930
|
-
</SidebarPanel>
|
|
1664
|
+
<PiAgentPanel
|
|
1665
|
+
currentSessionId={labSessionId}
|
|
1666
|
+
logCount={logs.length}
|
|
1667
|
+
onResultChange={setAssistantSearchResult}
|
|
1668
|
+
/>
|
|
1931
1669
|
|
|
1932
|
-
<SidebarPanel label="
|
|
1670
|
+
<SidebarPanel label="Current scope">
|
|
1671
|
+
<div className="mb-2 rounded-md bg-black/20 px-3 py-2 shadow-[inset_0_1px_0_rgba(255,255,255,0.035)]">
|
|
1672
|
+
<div
|
|
1673
|
+
className="truncate font-mono text-xs font-semibold text-cyan-100"
|
|
1674
|
+
title={activeScopeLabel}
|
|
1675
|
+
>
|
|
1676
|
+
{activeScopeLabel}
|
|
1677
|
+
</div>
|
|
1678
|
+
<div className="mt-1 text-[11px] text-muted-foreground">
|
|
1679
|
+
{isPinnedSessionPage ? "Pinned session" : "Live Inspector data"}
|
|
1680
|
+
</div>
|
|
1681
|
+
</div>
|
|
1933
1682
|
<div className="grid grid-cols-2 gap-2">
|
|
1934
|
-
<SidebarMetric
|
|
1683
|
+
<SidebarMetric
|
|
1684
|
+
label={assistantSearchResult === null ? "Requests" : "Results"}
|
|
1685
|
+
value={displayedLogs.length}
|
|
1686
|
+
/>
|
|
1935
1687
|
<SidebarMetric label="Sessions" value={sessions.length} />
|
|
1936
1688
|
<SidebarMetric
|
|
1937
1689
|
label="Input"
|
|
@@ -1949,26 +1701,19 @@ export function ProxyViewer({
|
|
|
1949
1701
|
)}
|
|
1950
1702
|
</SidebarPanel>
|
|
1951
1703
|
|
|
1952
|
-
<SidebarPanel label="
|
|
1953
|
-
<div className="
|
|
1954
|
-
<
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
{
|
|
1959
|
-
|
|
1960
|
-
<
|
|
1961
|
-
|
|
1962
|
-
</div>
|
|
1704
|
+
<SidebarPanel label="Tools">
|
|
1705
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
1706
|
+
<ThemeModeToggle />
|
|
1707
|
+
<UiScaleControl
|
|
1708
|
+
preference={uiScalePreference}
|
|
1709
|
+
resolvedScale={resolvedUiScale}
|
|
1710
|
+
onChange={setUiScalePreference}
|
|
1711
|
+
/>
|
|
1712
|
+
<GroupsDialog />
|
|
1713
|
+
<AgentLabDialog currentSessionId={labSessionId} logCount={logs.length} />
|
|
1963
1714
|
</div>
|
|
1964
1715
|
</SidebarPanel>
|
|
1965
1716
|
|
|
1966
|
-
<PiAgentPanel
|
|
1967
|
-
currentSessionId={labSessionId}
|
|
1968
|
-
logCount={logs.length}
|
|
1969
|
-
suggestedQuestion={assistantSuggestedQuestion}
|
|
1970
|
-
/>
|
|
1971
|
-
|
|
1972
1717
|
{hasSessionContext && sessionContextScope !== null && (
|
|
1973
1718
|
<SessionContextBar
|
|
1974
1719
|
label={sessionContextScope.label}
|
|
@@ -1986,25 +1731,6 @@ export function ProxyViewer({
|
|
|
1986
1731
|
sessionMemberships.length > 0 && (
|
|
1987
1732
|
<SessionMembershipPanel memberships={sessionMemberships} />
|
|
1988
1733
|
)}
|
|
1989
|
-
|
|
1990
|
-
<SidebarPanel label="Filters">
|
|
1991
|
-
{hideSessionFilter ? (
|
|
1992
|
-
<p className="text-[11px] leading-5 text-muted-foreground">
|
|
1993
|
-
This session is pinned by the current URL.
|
|
1994
|
-
</p>
|
|
1995
|
-
) : (
|
|
1996
|
-
<SessionSearchBox
|
|
1997
|
-
query={sessionSearchQuery}
|
|
1998
|
-
suggestions={sessionSearchSuggestions}
|
|
1999
|
-
selectedSession={selectedSession}
|
|
2000
|
-
selectedModel={selectedModel}
|
|
2001
|
-
onQueryChange={setSessionSearchQuery}
|
|
2002
|
-
onApplySuggestion={handleApplySessionSearchSuggestion}
|
|
2003
|
-
onClear={handleClearSessionSearch}
|
|
2004
|
-
onAskAssistant={handleAskAssistantFromSearch}
|
|
2005
|
-
/>
|
|
2006
|
-
)}
|
|
2007
|
-
</SidebarPanel>
|
|
2008
1734
|
</div>
|
|
2009
1735
|
</aside>
|
|
2010
1736
|
)}
|
|
@@ -2151,7 +1877,30 @@ export function ProxyViewer({
|
|
|
2151
1877
|
)}
|
|
2152
1878
|
</div>
|
|
2153
1879
|
)}
|
|
2154
|
-
|
|
1880
|
+
{assistantSearchResult !== null && (
|
|
1881
|
+
<div className="mt-2 flex items-center gap-3 rounded-md bg-cyan-400/[0.07] px-3 py-2 shadow-[inset_2px_0_0_rgba(103,232,249,0.35),inset_0_0_0_1px_rgba(103,232,249,0.08)]">
|
|
1882
|
+
<div className="min-w-0 flex-1">
|
|
1883
|
+
<div className="truncate text-xs font-medium text-cyan-50">
|
|
1884
|
+
{assistantSearchResult.question}
|
|
1885
|
+
</div>
|
|
1886
|
+
<div className="mt-0.5 text-[10px] text-muted-foreground">
|
|
1887
|
+
Showing {String(displayedLogs.length)} loaded logs from{" "}
|
|
1888
|
+
{String(assistantSearchResult.logIds.length)} linked results
|
|
1889
|
+
</div>
|
|
1890
|
+
</div>
|
|
1891
|
+
<button
|
|
1892
|
+
type="button"
|
|
1893
|
+
onClick={() => setAssistantSearchResult(null)}
|
|
1894
|
+
className="inline-flex h-7 shrink-0 items-center gap-1 rounded-md bg-black/20 px-2 text-[11px] text-muted-foreground transition-colors hover:bg-black/30 hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring focus-visible:outline-none"
|
|
1895
|
+
>
|
|
1896
|
+
<X className="size-3" />
|
|
1897
|
+
All logs
|
|
1898
|
+
</button>
|
|
1899
|
+
</div>
|
|
1900
|
+
)}
|
|
1901
|
+
{assistantSearchResult === null && (
|
|
1902
|
+
<LogPaginationBar logs={logs} pagination={pagination} embedded={true} />
|
|
1903
|
+
)}
|
|
2155
1904
|
{onNotify === undefined && exportError !== null && (
|
|
2156
1905
|
<div className="mt-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
|
2157
1906
|
{exportError}
|
|
@@ -2182,7 +1931,24 @@ export function ProxyViewer({
|
|
|
2182
1931
|
|
|
2183
1932
|
<div className="px-4 py-4 lg:px-5">
|
|
2184
1933
|
<SessionOpeningSlate stats={slateStats} />
|
|
2185
|
-
{
|
|
1934
|
+
{assistantSearchResult !== null && displayedLogs.length === 0 ? (
|
|
1935
|
+
<div className="py-16 text-center text-muted-foreground">
|
|
1936
|
+
<div className="mx-auto max-w-xl rounded-[8px] bg-white/[0.035] px-5 py-8 shadow-[inset_0_1px_0_rgba(255,255,255,0.045),0_18px_52px_rgba(0,0,0,0.18)]">
|
|
1937
|
+
<p className="text-sm font-medium text-foreground">No linked logs are loaded</p>
|
|
1938
|
+
<p className="mt-2 text-xs leading-5 text-muted-foreground">
|
|
1939
|
+
Assistant found evidence, but none of its linked log IDs are in the current
|
|
1940
|
+
loaded window. Open an evidence card in Assistant or restore the full log view.
|
|
1941
|
+
</p>
|
|
1942
|
+
<button
|
|
1943
|
+
type="button"
|
|
1944
|
+
onClick={() => setAssistantSearchResult(null)}
|
|
1945
|
+
className="mt-4 inline-flex h-8 items-center rounded-md bg-cyan-400/10 px-3 text-xs text-cyan-100 transition-colors hover:bg-cyan-400/16"
|
|
1946
|
+
>
|
|
1947
|
+
Show all logs
|
|
1948
|
+
</button>
|
|
1949
|
+
</div>
|
|
1950
|
+
</div>
|
|
1951
|
+
) : logs.length === 0 ? (
|
|
2186
1952
|
selectedSession !== "__all__" ? (
|
|
2187
1953
|
<div className="py-16 text-center text-muted-foreground">
|
|
2188
1954
|
<div className="bg-white/[0.035] shadow-[inset_0_1px_0_rgba(255,255,255,0.045),0_18px_52px_rgba(0,0,0,0.18)] mx-auto max-w-xl space-y-4 rounded-[8px] px-4 py-8">
|
|
@@ -2290,9 +2056,11 @@ export function ProxyViewer({
|
|
|
2290
2056
|
timeDisplayFormat={timeDisplayFormat}
|
|
2291
2057
|
/>
|
|
2292
2058
|
</div>
|
|
2293
|
-
|
|
2294
|
-
<
|
|
2295
|
-
|
|
2059
|
+
{assistantSearchResult === null && (
|
|
2060
|
+
<div className="mt-3">
|
|
2061
|
+
<LogPaginationBar logs={logs} pagination={pagination} />
|
|
2062
|
+
</div>
|
|
2063
|
+
)}
|
|
2296
2064
|
</div>
|
|
2297
2065
|
)}
|
|
2298
2066
|
</div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { BrainCircuit, Loader2, RotateCcw, Send, Sparkles } from "lucide-react";
|
|
2
|
-
import {
|
|
2
|
+
import { useMemo, useState, type ChangeEvent, type JSX } from "react";
|
|
3
3
|
import {
|
|
4
4
|
buildPiAgentHistory,
|
|
5
5
|
createEmptyPiAgentConversation,
|
|
6
|
+
getPiAgentResultLogIds,
|
|
6
7
|
normalizePiAgentResponse,
|
|
7
8
|
PI_AGENT_SCOPE_PILLS,
|
|
8
9
|
PI_AGENT_STARTERS,
|
|
@@ -19,7 +20,13 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ".
|
|
|
19
20
|
type PiAgentPanelProps = {
|
|
20
21
|
currentSessionId: string | null;
|
|
21
22
|
logCount: number;
|
|
22
|
-
|
|
23
|
+
onResultChange: (result: PiAgentSearchResult | null) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type PiAgentSearchResult = {
|
|
27
|
+
question: string;
|
|
28
|
+
logIds: number[];
|
|
29
|
+
reportedLogCount: number;
|
|
23
30
|
};
|
|
24
31
|
|
|
25
32
|
const DEFAULT_QUESTION =
|
|
@@ -200,7 +207,7 @@ function ConversationMessage({ message }: { message: PiAgentConversationMessage
|
|
|
200
207
|
export function PiAgentPanel({
|
|
201
208
|
currentSessionId,
|
|
202
209
|
logCount,
|
|
203
|
-
|
|
210
|
+
onResultChange,
|
|
204
211
|
}: PiAgentPanelProps): JSX.Element {
|
|
205
212
|
const { providers } = useProviders();
|
|
206
213
|
const piProviders = useMemo(
|
|
@@ -220,11 +227,6 @@ export function PiAgentPanel({
|
|
|
220
227
|
const question = conversation.question;
|
|
221
228
|
const canAsk = question.trim() !== "" && piProviders.length > 0 && !loading;
|
|
222
229
|
|
|
223
|
-
useEffect(() => {
|
|
224
|
-
if (suggestedQuestion === undefined || suggestedQuestion.trim() === "") return;
|
|
225
|
-
setConversation((current) => ({ ...current, question: suggestedQuestion, error: null }));
|
|
226
|
-
}, [suggestedQuestion]);
|
|
227
|
-
|
|
228
230
|
const handleQuestionChange = (event: ChangeEvent<HTMLTextAreaElement>): void => {
|
|
229
231
|
setConversation((current) => ({ ...current, question: event.currentTarget.value }));
|
|
230
232
|
};
|
|
@@ -236,6 +238,7 @@ export function PiAgentPanel({
|
|
|
236
238
|
|
|
237
239
|
const resetConversation = (): void => {
|
|
238
240
|
setConversation({ ...createEmptyPiAgentConversation(), question: DEFAULT_QUESTION });
|
|
241
|
+
onResultChange(null);
|
|
239
242
|
};
|
|
240
243
|
|
|
241
244
|
const setStarter = (questionText: string): void => {
|
|
@@ -308,6 +311,11 @@ export function PiAgentPanel({
|
|
|
308
311
|
},
|
|
309
312
|
],
|
|
310
313
|
}));
|
|
314
|
+
onResultChange({
|
|
315
|
+
question: trimmedQuestion,
|
|
316
|
+
logIds: getPiAgentResultLogIds(normalized),
|
|
317
|
+
reportedLogCount: normalized.logCount,
|
|
318
|
+
});
|
|
311
319
|
})
|
|
312
320
|
.catch((err: unknown) => {
|
|
313
321
|
setConversation((current) => ({
|
|
@@ -325,9 +333,9 @@ export function PiAgentPanel({
|
|
|
325
333
|
<div className="flex items-center gap-2">
|
|
326
334
|
<BrainCircuit className="size-4 text-cyan-200" />
|
|
327
335
|
<div className="min-w-0 flex-1">
|
|
328
|
-
<div className="text-xs font-semibold text-foreground">
|
|
336
|
+
<div className="text-xs font-semibold text-foreground">Search with Assistant</div>
|
|
329
337
|
<div className="text-[11px] text-muted-foreground">
|
|
330
|
-
{currentSessionId === null ? "
|
|
338
|
+
{currentSessionId === null ? "Search all Inspector evidence" : "Search this session"}
|
|
331
339
|
</div>
|
|
332
340
|
</div>
|
|
333
341
|
<button
|
|
@@ -343,8 +351,9 @@ export function PiAgentPanel({
|
|
|
343
351
|
{conversation.messages.length === 0 && (
|
|
344
352
|
<div className="mt-3 rounded-md bg-black/[0.18] px-3 py-2 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.045)]">
|
|
345
353
|
<p className="text-[11px] leading-5 text-muted-foreground">
|
|
346
|
-
|
|
347
|
-
alerts, runs, groups, and knowledge
|
|
354
|
+
Describe the traffic you want to find in plain language. Assistant searches saved
|
|
355
|
+
traffic, sessions, tools, alerts, runs, groups, and knowledge, then uses linked evidence
|
|
356
|
+
to shape the log results.
|
|
348
357
|
</p>
|
|
349
358
|
<div className="mt-2 grid gap-1.5">
|
|
350
359
|
{PI_AGENT_SCOPE_PILLS.map((pill) => (
|
|
@@ -427,7 +436,7 @@ export function PiAgentPanel({
|
|
|
427
436
|
value={question}
|
|
428
437
|
onChange={handleQuestionChange}
|
|
429
438
|
className="mt-3 min-h-20 w-full resize-y rounded-md bg-black/25 px-3 py-2 text-xs leading-5 text-foreground outline-none shadow-[inset_0_0_0_1px_rgba(255,255,255,0.06)] transition-colors placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring"
|
|
430
|
-
placeholder="
|
|
439
|
+
placeholder="Search failures, slow requests, token spikes, tool calls..."
|
|
431
440
|
/>
|
|
432
441
|
|
|
433
442
|
<button
|
|
@@ -437,7 +446,7 @@ export function PiAgentPanel({
|
|
|
437
446
|
className="mt-2 inline-flex h-8 w-full items-center justify-center gap-2 rounded-md bg-cyan-400/12 px-3 text-xs font-medium text-cyan-100 shadow-[inset_0_0_0_1px_rgba(103,232,249,0.18)] transition-colors hover:bg-cyan-400/18 disabled:cursor-not-allowed disabled:opacity-50"
|
|
438
447
|
>
|
|
439
448
|
{loading ? <Loader2 className="size-3.5 animate-spin" /> : <Send className="size-3.5" />}
|
|
440
|
-
<span>{loading ? "
|
|
449
|
+
<span>{loading ? "Searching..." : "Search logs"}</span>
|
|
441
450
|
</button>
|
|
442
451
|
|
|
443
452
|
{conversation.error !== null && (
|
|
@@ -44,6 +44,16 @@ export type PiAgentResponseView = {
|
|
|
44
44
|
queryMetrics: PiAgentMetricView[];
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
export function getPiAgentResultLogIds(response: PiAgentResponseView): number[] {
|
|
48
|
+
return [
|
|
49
|
+
...new Set(
|
|
50
|
+
response.evidence
|
|
51
|
+
.map((evidence) => evidence.logId)
|
|
52
|
+
.filter((logId): logId is number => logId !== null),
|
|
53
|
+
),
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
47
57
|
export type PiAgentConversationMessage = PiAgentHistoryMessage & {
|
|
48
58
|
id: string;
|
|
49
59
|
response: PiAgentResponseView | null;
|