11import { useEffect , useMemo , useRef , useState } from 'react' ;
2- import { searchPrompts , type PromptHit } from '../api' ;
2+ import { searchPrompts , type PromptHit , type PromptSearchFilters } from '../api' ;
33import { useT } from '../i18n' ;
44
55const AGENT_BADGE : Record < string , string > = {
@@ -8,6 +8,8 @@ const AGENT_BADGE: Record<string, string> = {
88 codex : 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30' ,
99} ;
1010
11+ const RANGE_HOURS : Record < string , number > = { '24h' : 24 , '7d' : 24 * 7 , '30d' : 24 * 30 } ;
12+
1113function relTime ( ts : string | null ) : string {
1214 if ( ! ts ) return '' ;
1315 const dt = new Date ( ts ) . getTime ( ) ;
@@ -45,29 +47,48 @@ interface Props {
4547export function PromptsPanel ( { onOpenSession } : Props ) {
4648 const { t } = useT ( ) ;
4749 const [ q , setQ ] = useState ( '' ) ;
50+ const [ agent , setAgent ] = useState < string > ( '' ) ;
51+ const [ repo , setRepo ] = useState < string > ( '' ) ;
52+ const [ range , setRange ] = useState < string > ( '' ) ;
4853 const [ hits , setHits ] = useState < PromptHit [ ] > ( [ ] ) ;
4954 const [ loading , setLoading ] = useState ( false ) ;
5055 const debounceRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
5156
57+ const filters : PromptSearchFilters = useMemo ( ( ) => {
58+ const f : PromptSearchFilters = { } ;
59+ if ( agent ) f . agent = agent ;
60+ if ( repo . trim ( ) ) f . repo = repo . trim ( ) ;
61+ if ( range && RANGE_HOURS [ range ] ) {
62+ const since = new Date ( Date . now ( ) - RANGE_HOURS [ range ] * 3600_000 ) ;
63+ f . since = since . toISOString ( ) ;
64+ }
65+ return f ;
66+ } , [ agent , repo , range ] ) ;
67+
5268 useEffect ( ( ) => {
5369 if ( debounceRef . current ) clearTimeout ( debounceRef . current ) ;
5470 debounceRef . current = setTimeout ( ( ) => {
5571 setLoading ( true ) ;
56- searchPrompts ( q . trim ( ) , 100 )
72+ searchPrompts ( q . trim ( ) , 100 , filters )
5773 . then ( setHits )
5874 . catch ( ( ) => setHits ( [ ] ) )
5975 . finally ( ( ) => setLoading ( false ) ) ;
6076 } , 250 ) ;
6177 return ( ) => {
6278 if ( debounceRef . current ) clearTimeout ( debounceRef . current ) ;
6379 } ;
64- } , [ q ] ) ;
80+ } , [ q , filters ] ) ;
81+
82+ const filterActive = ! ! ( agent || repo . trim ( ) || range ) ;
6583
6684 const header = useMemo ( ( ) => {
6785 if ( loading ) return t ( 'prompts.loading' ) ;
68- if ( q . trim ( ) ) return `${ hits . length } ${ t ( 'prompts.results' ) } ` ;
86+ if ( q . trim ( ) || filterActive ) return `${ hits . length } ${ t ( 'prompts.results' ) } ` ;
6987 return t ( 'prompts.recent' ) ;
70- } , [ loading , hits . length , q , t ] ) ;
88+ } , [ loading , hits . length , q , filterActive , t ] ) ;
89+
90+ const selectClass =
91+ 'bg-slate-900 border border-slate-700 rounded px-2 py-1 text-xs text-slate-200 focus:outline-none focus:border-emerald-500/60' ;
7192
7293 return (
7394 < div className = "flex-1 flex flex-col overflow-hidden bg-slate-950" >
@@ -95,7 +116,49 @@ export function PromptsPanel({ onOpenSession }: Props) {
95116 className = "w-full bg-slate-900 border border-slate-700 rounded-md pl-9 pr-3 py-2 text-sm text-slate-100 placeholder-slate-500 focus:outline-none focus:border-emerald-500/60 focus:ring-1 focus:ring-emerald-500/30"
96117 />
97118 </ div >
98- < div className = "mt-2 text-[11px] uppercase tracking-wider text-slate-500" > { header } </ div >
119+ < div className = "mt-3 flex flex-wrap items-center gap-2" >
120+ < label className = "flex items-center gap-1.5 text-[11px] text-slate-400" >
121+ < span className = "uppercase tracking-wider" > { t ( 'prompts.filter.agent' ) } </ span >
122+ < select className = { selectClass } value = { agent } onChange = { ( e ) => setAgent ( e . target . value ) } >
123+ < option value = "" > { t ( 'prompts.filter.agent.all' ) } </ option >
124+ < option value = "copilot" > copilot</ option >
125+ < option value = "claude" > claude</ option >
126+ < option value = "codex" > codex</ option >
127+ </ select >
128+ </ label >
129+ < label className = "flex items-center gap-1.5 text-[11px] text-slate-400" >
130+ < span className = "uppercase tracking-wider" > { t ( 'prompts.filter.repo' ) } </ span >
131+ < input
132+ type = "text"
133+ value = { repo }
134+ onChange = { ( e ) => setRepo ( e . target . value ) }
135+ placeholder = "owner/name"
136+ className = { `${ selectClass } w-44` }
137+ />
138+ </ label >
139+ < label className = "flex items-center gap-1.5 text-[11px] text-slate-400" >
140+ < span className = "uppercase tracking-wider" > { t ( 'prompts.filter.range' ) } </ span >
141+ < select className = { selectClass } value = { range } onChange = { ( e ) => setRange ( e . target . value ) } >
142+ < option value = "" > { t ( 'prompts.filter.range.all' ) } </ option >
143+ < option value = "24h" > { t ( 'prompts.filter.range.24h' ) } </ option >
144+ < option value = "7d" > { t ( 'prompts.filter.range.7d' ) } </ option >
145+ < option value = "30d" > { t ( 'prompts.filter.range.30d' ) } </ option >
146+ </ select >
147+ </ label >
148+ { filterActive && (
149+ < button
150+ onClick = { ( ) => {
151+ setAgent ( '' ) ;
152+ setRepo ( '' ) ;
153+ setRange ( '' ) ;
154+ } }
155+ className = "text-[11px] text-slate-400 hover:text-slate-200 underline underline-offset-2"
156+ >
157+ { t ( 'prompts.filter.clear' ) }
158+ </ button >
159+ ) }
160+ < span className = "ml-auto text-[11px] uppercase tracking-wider text-slate-500" > { header } </ span >
161+ </ div >
99162 </ div >
100163
101164 < div className = "flex-1 overflow-y-auto" >
@@ -120,9 +183,7 @@ export function PromptsPanel({ onOpenSession }: Props) {
120183 { h . repo && (
121184 < span className = "text-[11px] text-slate-400 truncate max-w-xs" > { h . repo } </ span >
122185 ) }
123- { h . branch && (
124- < span className = "text-[11px] text-slate-500" > · { h . branch } </ span >
125- ) }
186+ { h . branch && < span className = "text-[11px] text-slate-500" > · { h . branch } </ span > }
126187 < span className = "ml-auto text-[11px] text-slate-500 font-mono" >
127188 { relTime ( h . timestamp ) }
128189 </ span >
0 commit comments