import { useState } from 'react'; import type { ScoredChunk } from '../types'; import { InfoTooltip } from './PipelineView'; interface SearchColumnState { status: 'idle' | 'running' | 'done'; data?: { bm25Hits: ScoredChunk[]; vectorHits: ScoredChunk[] }; } interface SearchColumnProps { state: SearchColumnState; accent: string; info: string; } function Spinner({ color }: { color: string }) { return ( ); } function ScoreBadge({ score, source }: { score: number; source: 'bm25' | 'vector' }) { const label = source === 'bm25' ? score.toFixed(2) : (score * 100).toFixed(1) + '%'; return ( {label} ); } function HitRow({ hit }: { hit: ScoredChunk }) { const [open, setOpen] = useState(false); return (
setOpen(o => !o)} style={{ padding: '0.4rem 0.6rem', background: 'var(--bg-card)', border: '1px solid var(--border)', borderRadius: '5px', marginBottom: '0.25rem', cursor: 'pointer', fontSize: '0.75rem', }} onMouseEnter={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = '0 1px 5px var(--shadow)'; }} onMouseLeave={e => { (e.currentTarget as HTMLDivElement).style.boxShadow = 'none'; }} >
{hit.chunk.title} {open ? '\u25B2' : '\u25BC'}
{open && (
{hit.chunk.text}
)}
); } // Deduplicate hits by docId, keeping the highest score per document. function dedupeByDoc(hits: ScoredChunk[]): ScoredChunk[] { const best = new Map(); for (const hit of hits) { const existing = best.get(hit.chunk.docId); if (!existing || hit.score > existing.score) { best.set(hit.chunk.docId, hit); } } return [...best.values()].sort((a, b) => b.score - a.score); } function HitsSection({ label, hits, color, dedupe }: { label: string; hits: ScoredChunk[]; color: string; dedupe?: boolean }) { const displayHits = dedupe ? dedupeByDoc(hits) : hits; const top = displayHits.slice(0, 5); return (
{label} ({displayHits.length} docs)
{top.map((hit, i) => ( ))} {displayHits.length > 5 && (
+{displayHits.length - 5} more
)}
); } export default function SearchColumn({ state, accent, info }: SearchColumnProps) { const isIdle = state.status === 'idle'; const isRunning = state.status === 'running'; const isDone = state.status === 'done'; return (

Parallel Search

{isRunning && }
{isIdle && (

Awaiting expansion...

)} {isRunning && (

Running vector + BM25 search...

)} {isDone && state.data && ( <> )}
); }