1- import { useState , useEffect , useCallback , useMemo } from "react" ;
1+ import { useState , useEffect , useCallback , useMemo , useRef } from "react" ;
22import { useSearchParams } from "react-router-dom" ;
33import { toast } from "sonner" ;
44import { api } from "../api" ;
@@ -10,7 +10,7 @@ import {
1010import { parseTrace } from "../trace/parse" ;
1111import { analyzeTrace } from "../trace/analyze" ;
1212import type { ParsedTrace , TraceAnalysis , IssueKind } from "../trace/types" ;
13- import type { ProductionRun , TokenUsage } from "../types" ;
13+ import type { ProductionRun , TokenUsage , SearchResult , SearchMatchInfo } from "../types" ;
1414import type { Annotation , AnnotationMarker } from "../components/Annotations" ;
1515
1616export 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,
0 commit comments