Skip to content

Commit 84203e6

Browse files
committed
feat: search api
1 parent deee3fb commit 84203e6

7 files changed

Lines changed: 283 additions & 18 deletions

File tree

mcp/benchmark/src/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ProductionRun, Annotation, AnnotationMarker } from "./types";
1+
import type { ProductionRun, SearchResult, Annotation, AnnotationMarker } from "./types";
22

33
const BASE = "/api";
44

@@ -18,6 +18,7 @@ export const api = {
1818
sessions: {
1919
list: () => req<ProductionRun[]>("/sessions"),
2020
get: (id: string) => req<ProductionRun>(`/sessions/${id}`),
21+
search: (q: string) => req<SearchResult[]>(`/sessions/search?q=${encodeURIComponent(q)}`),
2122
annotate: (
2223
id: string,
2324
body: { target: "session" | "tool_call"; target_id?: string; marker: AnnotationMarker; note?: string; author?: string },

mcp/benchmark/src/components/Sessions.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export function Sessions() {
2626
setRangeFilter={state.setRangeFilter}
2727
setDayFilter={state.setDayFilter}
2828
clearFilters={state.clearFilters}
29+
searching={state.searching}
30+
searchMatchMap={state.searchMatchMap}
2931
/>
3032
<SessionDetail
3133
selected={state.selected}

mcp/benchmark/src/components/sessions/SessionSidebar.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { shortId } from "../ui";
33
import { SourceBadge } from "./SessionBadges";
44
import { card, muted } from "./styles";
55
import { formatNumber, formatSourceLabel } from "../../utils";
6-
import type { ProductionRun } from "../../types";
6+
import type { ProductionRun, SearchMatchInfo } from "../../types";
77

88
interface SessionSidebarProps {
99
loading: boolean;
@@ -25,6 +25,8 @@ interface SessionSidebarProps {
2525
setRangeFilter: (v: "24h" | "7d" | "30d" | "all") => void;
2626
setDayFilter: (v: string) => void;
2727
clearFilters: () => void;
28+
searching: boolean;
29+
searchMatchMap: Map<string, SearchMatchInfo>;
2830
}
2931

3032
export function SessionSidebar({
@@ -47,6 +49,8 @@ export function SessionSidebar({
4749
setRangeFilter,
4850
setDayFilter,
4951
clearFilters,
52+
searching,
53+
searchMatchMap,
5054
}: SessionSidebarProps) {
5155
const navigate = useNavigate();
5256
const location = useLocation();
@@ -155,11 +159,16 @@ export function SessionSidebar({
155159
}}
156160
>
157161
<input
158-
placeholder={"Search id, repo, model, prompt\u2026"}
162+
placeholder={"Search traces, repos, models\u2026"}
159163
value={quickSearch}
160164
onChange={(e) => setQuickSearch(e.target.value)}
161165
style={inputStyle}
162166
/>
167+
{searching && (
168+
<p style={{ ...muted, fontSize: "10px", margin: 0, textAlign: "center" }}>
169+
Searching traces\u2026
170+
</p>
171+
)}
163172
<input
164173
placeholder={"Filter by repo\u2026"}
165174
value={repoSearch}
@@ -329,6 +338,25 @@ export function SessionSidebar({
329338
{run.user_prompt_preview}
330339
</p>
331340
)}
341+
{searchMatchMap.has(run.id) && (() => {
342+
const m = searchMatchMap.get(run.id)!;
343+
return (
344+
<p
345+
style={{
346+
fontSize: "10px",
347+
color: "#22d3ee",
348+
margin: "3px 0 0 0",
349+
overflow: "hidden",
350+
textOverflow: "ellipsis",
351+
whiteSpace: "nowrap",
352+
}}
353+
>
354+
{m.first_match_at_call === 0
355+
? "match in prompt"
356+
: `match at call #${m.first_match_at_call}/${m.total_tool_calls}`}
357+
</p>
358+
);
359+
})()}
332360
</button>
333361
))
334362
)}

mcp/benchmark/src/hooks/useSessionsState.ts

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useCallback, useMemo } from "react";
1+
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
22
import { useSearchParams } from "react-router-dom";
33
import { toast } from "sonner";
44
import { api } from "../api";
@@ -10,7 +10,7 @@ import {
1010
import { parseTrace } from "../trace/parse";
1111
import { analyzeTrace } from "../trace/analyze";
1212
import type { ParsedTrace, TraceAnalysis, IssueKind } from "../trace/types";
13-
import type { ProductionRun, TokenUsage } from "../types";
13+
import type { ProductionRun, TokenUsage, SearchResult, SearchMatchInfo } from "../types";
1414
import type { Annotation, AnnotationMarker } from "../components/Annotations";
1515

1616
export interface SessionsState {
@@ -43,6 +43,8 @@ export interface SessionsState {
4343
toolCallId?: string,
4444
) => void;
4545
handleTurnToggle: (turnId: string) => void;
46+
searching: boolean;
47+
searchMatchMap: Map<string, SearchMatchInfo>;
4648
setQuickSearch: (v: string) => void;
4749
setRepoSearch: (v: string) => void;
4850
setSourceFilter: (v: string) => void;
@@ -69,6 +71,9 @@ export function useSessionsState(): SessionsState {
6971
const [showSessionAnnotationForm, setShowSessionAnnotationForm] =
7072
useState(false);
7173
const [loading, setLoading] = useState(true);
74+
const [searching, setSearching] = useState(false);
75+
const [searchResults, setSearchResults] = useState<SearchResult[] | null>(null);
76+
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
7277
const [quickSearch, setQuickSearch] = useState(
7378
searchParams.get("q") || "",
7479
);
@@ -190,6 +195,30 @@ export function useSessionsState(): SessionsState {
190195
setSearchParams(nextParams, { replace: true });
191196
}, [dayFilter, quickSearch, repoSearch, rangeFilter, setSearchParams, sourceFilter]);
192197

198+
useEffect(() => {
199+
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
200+
if (!quickSearch || quickSearch.trim().length < 2) {
201+
setSearchResults(null);
202+
setSearching(false);
203+
return;
204+
}
205+
setSearching(true);
206+
searchTimerRef.current = setTimeout(async () => {
207+
try {
208+
const results = await api.sessions.search(quickSearch.trim());
209+
setSearchResults(results);
210+
} catch (e: any) {
211+
toast.error(e.message);
212+
setSearchResults([]);
213+
} finally {
214+
setSearching(false);
215+
}
216+
}, 300);
217+
return () => {
218+
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
219+
};
220+
}, [quickSearch]);
221+
193222
useEffect(() => {
194223
if (parsed.turns.length === 0) {
195224
setOpenTurnId(null);
@@ -202,7 +231,7 @@ export function useSessionsState(): SessionsState {
202231

203232
const filteredRuns = useMemo(
204233
() =>
205-
runs.filter((r) => {
234+
(searchResults ?? runs).filter((r) => {
206235
if (sourceFilter !== "all" && (r.source || "unknown") !== sourceFilter)
207236
return false;
208237
if (
@@ -218,21 +247,16 @@ export function useSessionsState(): SessionsState {
218247
const rangeStart = getRangeStart(rangeFilter);
219248
if (rangeStart && new Date(r.timestamp).getTime() < rangeStart)
220249
return false;
221-
if (quickSearch) {
222-
const q = quickSearch.toLowerCase();
223-
const matches =
224-
r.id.toLowerCase().includes(q) ||
225-
r.repo.toLowerCase().includes(q) ||
226-
(r.source || "").toLowerCase().includes(q) ||
227-
(r.model || "").toLowerCase().includes(q) ||
228-
(r.user_prompt_preview || "").toLowerCase().includes(q);
229-
if (!matches) return false;
230-
}
231250
return true;
232251
}),
233-
[runs, dayFilter, quickSearch, repoSearch, rangeFilter, sourceFilter],
252+
[runs, searchResults, dayFilter, repoSearch, rangeFilter, sourceFilter],
234253
);
235254

255+
const searchMatchMap = useMemo(() => {
256+
if (!searchResults) return new Map<string, SearchMatchInfo>();
257+
return new Map(searchResults.map((r) => [r.id, r.search_match]));
258+
}, [searchResults]);
259+
236260
const clearFilters = () => {
237261
setQuickSearch("");
238262
setRepoSearch("");
@@ -262,6 +286,8 @@ export function useSessionsState(): SessionsState {
262286
selectedUsage,
263287
prompt,
264288
answer,
289+
searching,
290+
searchMatchMap,
265291
showSessionAnnotationForm,
266292
load,
267293
loadDetail,

mcp/benchmark/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,13 @@ export interface ProductionRun {
7979
annotations?: Annotation[];
8080
trace?: unknown;
8181
}
82+
83+
export interface SearchMatchInfo {
84+
first_match_at_call: number;
85+
total_tool_calls: number;
86+
match_context: string;
87+
}
88+
89+
export interface SearchResult extends ProductionRun {
90+
search_match: SearchMatchInfo;
91+
}

mcp/src/benchmark/router.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { Router } from "express";
2-
import { list_sessions, get_session, session_stats, add_annotation } from "./sessions.js";
2+
import { list_sessions, get_session, session_stats, add_annotation, search_sessions } from "./sessions.js";
33

44
export function benchmarkRouter(): Router {
55
const router = Router();
66

77
// Sessions
88
router.get("/sessions", list_sessions);
9+
router.get("/sessions/search", search_sessions);
910
router.get("/sessions/stats", session_stats);
1011
router.get("/sessions/:id", get_session);
1112
router.post("/sessions/:id/annotations", add_annotation);

0 commit comments

Comments
 (0)