Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion mcp/benchmark/src/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ProductionRun, Annotation, AnnotationMarker } from "./types";
import type { ProductionRun, SearchResult, Annotation, AnnotationMarker } from "./types";

const BASE = "/api";

Expand All @@ -18,6 +18,7 @@ export const api = {
sessions: {
list: () => req<ProductionRun[]>("/sessions"),
get: (id: string) => req<ProductionRun>(`/sessions/${id}`),
search: (q: string) => req<SearchResult[]>(`/sessions/search?q=${encodeURIComponent(q)}`),
annotate: (
id: string,
body: { target: "session" | "tool_call"; target_id?: string; marker: AnnotationMarker; note?: string; author?: string },
Expand Down
4 changes: 4 additions & 0 deletions mcp/benchmark/src/components/Sessions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function Sessions() {
runs={state.runs}
filteredRuns={state.filteredRuns}
selected={state.selected}
quickSearch={state.quickSearch}
repoSearch={state.repoSearch}
sourceFilter={state.sourceFilter}
rangeFilter={state.rangeFilter}
Expand All @@ -19,11 +20,14 @@ export function Sessions() {
sourceOptions={state.sourceOptions}
load={state.load}
loadDetail={state.loadDetail}
setQuickSearch={state.setQuickSearch}
setRepoSearch={state.setRepoSearch}
setSourceFilter={state.setSourceFilter}
setRangeFilter={state.setRangeFilter}
setDayFilter={state.setDayFilter}
clearFilters={state.clearFilters}
searching={state.searching}
searchMatchMap={state.searchMatchMap}
/>
<SessionDetail
selected={state.selected}
Expand Down
43 changes: 41 additions & 2 deletions mcp/benchmark/src/components/sessions/SessionSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { shortId } from "../ui";
import { SourceBadge } from "./SessionBadges";
import { card, muted } from "./styles";
import { formatNumber, formatSourceLabel } from "../../utils";
import type { ProductionRun } from "../../types";
import type { ProductionRun, SearchMatchInfo } from "../../types";

interface SessionSidebarProps {
loading: boolean;
runs: ProductionRun[];
filteredRuns: ProductionRun[];
selected: ProductionRun | null;
quickSearch: string;
repoSearch: string;
sourceFilter: string;
rangeFilter: "24h" | "7d" | "30d" | "all";
Expand All @@ -18,18 +19,22 @@ interface SessionSidebarProps {
sourceOptions: string[];
load: () => void;
loadDetail: (run: ProductionRun) => void;
setQuickSearch: (v: string) => void;
setRepoSearch: (v: string) => void;
setSourceFilter: (v: string) => void;
setRangeFilter: (v: "24h" | "7d" | "30d" | "all") => void;
setDayFilter: (v: string) => void;
clearFilters: () => void;
searching: boolean;
searchMatchMap: Map<string, SearchMatchInfo>;
}

export function SessionSidebar({
loading,
runs,
filteredRuns,
selected,
quickSearch,
repoSearch,
sourceFilter,
rangeFilter,
Expand All @@ -38,11 +43,14 @@ export function SessionSidebar({
sourceOptions,
load,
loadDetail,
setQuickSearch,
setRepoSearch,
setSourceFilter,
setRangeFilter,
setDayFilter,
clearFilters,
searching,
searchMatchMap,
}: SessionSidebarProps) {
const navigate = useNavigate();
const location = useLocation();
Expand Down Expand Up @@ -150,6 +158,17 @@ export function SessionSidebar({
gap: "6px",
}}
>
<input
placeholder={"Search traces, repos, models\u2026"}
value={quickSearch}
onChange={(e) => setQuickSearch(e.target.value)}
style={inputStyle}
/>
{searching && (
<p style={{ ...muted, fontSize: "10px", margin: 0, textAlign: "center" }}>
Searching traces\u2026
</p>
)}
<input
placeholder={"Filter by repo\u2026"}
value={repoSearch}
Expand Down Expand Up @@ -189,7 +208,8 @@ export function SessionSidebar({
<option key={r} value={r} />
))}
</datalist>
{(repoSearch ||
{(quickSearch ||
repoSearch ||
sourceFilter !== "all" ||
rangeFilter !== "all" ||
dayFilter) && (
Expand Down Expand Up @@ -318,6 +338,25 @@ export function SessionSidebar({
{run.user_prompt_preview}
</p>
)}
{searchMatchMap.has(run.id) && (() => {
const m = searchMatchMap.get(run.id)!;
return (
<p
style={{
fontSize: "10px",
color: "#22d3ee",
margin: "3px 0 0 0",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
{m.first_match_at_call === 0
? "match in prompt"
: `match at call #${m.first_match_at_call}/${m.total_tool_calls}`}
</p>
);
})()}
</button>
))
)}
Expand Down
56 changes: 51 additions & 5 deletions mcp/benchmark/src/hooks/useSessionsState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback, useMemo } from "react";
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { useSearchParams } from "react-router-dom";
import { toast } from "sonner";
import { api } from "../api";
Expand All @@ -10,7 +10,7 @@ import {
import { parseTrace } from "../trace/parse";
import { analyzeTrace } from "../trace/analyze";
import type { ParsedTrace, TraceAnalysis, IssueKind } from "../trace/types";
import type { ProductionRun, TokenUsage } from "../types";
import type { ProductionRun, TokenUsage, SearchResult, SearchMatchInfo } from "../types";
import type { Annotation, AnnotationMarker } from "../components/Annotations";

export interface SessionsState {
Expand All @@ -19,6 +19,7 @@ export interface SessionsState {
filteredRuns: ProductionRun[];
selected: ProductionRun | null;
annotations: Annotation[];
quickSearch: string;
repoSearch: string;
sourceFilter: string;
rangeFilter: "24h" | "7d" | "30d" | "all";
Expand All @@ -42,6 +43,9 @@ export interface SessionsState {
toolCallId?: string,
) => void;
handleTurnToggle: (turnId: string) => void;
searching: boolean;
searchMatchMap: Map<string, SearchMatchInfo>;
setQuickSearch: (v: string) => void;
setRepoSearch: (v: string) => void;
setSourceFilter: (v: string) => void;
setRangeFilter: (v: "24h" | "7d" | "30d" | "all") => void;
Expand All @@ -67,6 +71,12 @@ export function useSessionsState(): SessionsState {
const [showSessionAnnotationForm, setShowSessionAnnotationForm] =
useState(false);
const [loading, setLoading] = useState(true);
const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState<SearchResult[] | null>(null);
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const [quickSearch, setQuickSearch] = useState(
searchParams.get("q") || "",
);
const [repoSearch, setRepoSearch] = useState(
searchParams.get("repo") || "",
);
Expand Down Expand Up @@ -166,6 +176,7 @@ export function useSessionsState(): SessionsState {
);

useEffect(() => {
setQuickSearch(searchParams.get("q") || "");
setRepoSearch(searchParams.get("repo") || "");
setSourceFilter(searchParams.get("source") || "all");
setRangeFilter(
Expand All @@ -176,12 +187,37 @@ export function useSessionsState(): SessionsState {

useEffect(() => {
const nextParams = new URLSearchParams();
if (quickSearch) nextParams.set("q", quickSearch);
if (repoSearch) nextParams.set("repo", repoSearch);
if (sourceFilter !== "all") nextParams.set("source", sourceFilter);
if (rangeFilter !== "all") nextParams.set("range", rangeFilter);
if (dayFilter) nextParams.set("day", dayFilter);
setSearchParams(nextParams, { replace: true });
}, [dayFilter, repoSearch, rangeFilter, setSearchParams, sourceFilter]);
}, [dayFilter, quickSearch, repoSearch, rangeFilter, setSearchParams, sourceFilter]);

useEffect(() => {
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
if (!quickSearch || quickSearch.trim().length < 2) {
setSearchResults(null);
setSearching(false);
return;
}
setSearching(true);
searchTimerRef.current = setTimeout(async () => {
try {
const results = await api.sessions.search(quickSearch.trim());
setSearchResults(results);
} catch (e: any) {
toast.error(e.message);
setSearchResults([]);
} finally {
setSearching(false);
}
}, 300);
return () => {
if (searchTimerRef.current) clearTimeout(searchTimerRef.current);
};
}, [quickSearch]);

useEffect(() => {
if (parsed.turns.length === 0) {
Expand All @@ -195,7 +231,7 @@ export function useSessionsState(): SessionsState {

const filteredRuns = useMemo(
() =>
runs.filter((r) => {
(searchResults ?? runs).filter((r) => {
if (sourceFilter !== "all" && (r.source || "unknown") !== sourceFilter)
return false;
if (
Expand All @@ -213,10 +249,16 @@ export function useSessionsState(): SessionsState {
return false;
return true;
}),
[runs, dayFilter, repoSearch, rangeFilter, sourceFilter],
[runs, searchResults, dayFilter, repoSearch, rangeFilter, sourceFilter],
);

const searchMatchMap = useMemo(() => {
if (!searchResults) return new Map<string, SearchMatchInfo>();
return new Map(searchResults.map((r) => [r.id, r.search_match]));
}, [searchResults]);

const clearFilters = () => {
setQuickSearch("");
setRepoSearch("");
setSourceFilter("all");
setRangeFilter("all");
Expand All @@ -229,6 +271,7 @@ export function useSessionsState(): SessionsState {
filteredRuns,
selected,
annotations,
quickSearch,
repoSearch,
sourceFilter,
rangeFilter,
Expand All @@ -243,11 +286,14 @@ export function useSessionsState(): SessionsState {
selectedUsage,
prompt,
answer,
searching,
searchMatchMap,
showSessionAnnotationForm,
load,
loadDetail,
handleAnnotate,
handleTurnToggle,
setQuickSearch,
setRepoSearch,
setSourceFilter,
setRangeFilter,
Expand Down
10 changes: 10 additions & 0 deletions mcp/benchmark/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,13 @@ export interface ProductionRun {
annotations?: Annotation[];
trace?: unknown;
}

export interface SearchMatchInfo {
first_match_at_call: number;
total_tool_calls: number;
match_context: string;
}

export interface SearchResult extends ProductionRun {
search_match: SearchMatchInfo;
}
3 changes: 2 additions & 1 deletion mcp/src/benchmark/router.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Router } from "express";
import { list_sessions, get_session, session_stats, add_annotation } from "./sessions.js";
import { list_sessions, get_session, session_stats, add_annotation, search_sessions } from "./sessions.js";

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

// Sessions
router.get("/sessions", list_sessions);
router.get("/sessions/search", search_sessions);
router.get("/sessions/stats", session_stats);
router.get("/sessions/:id", get_session);
router.post("/sessions/:id/annotations", add_annotation);
Expand Down
Loading
Loading