qmd-web / src /components /FusionColumn.tsx
shreyask's picture
Deploy qmd-web
ac50275 verified
import type { RRFResult, RerankedResult, FinalResult } from '../types';
import { InfoTooltip } from './PipelineView';
interface FusionColumnState {
rrf: { status: 'idle' | 'done'; data?: { merged: RRFResult[] } };
rerank: { status: 'idle' | 'running' | 'done'; data?: { before: RRFResult[]; after: RerankedResult[] } };
finalResults?: FinalResult[];
}
interface FusionColumnProps {
state: FusionColumnState;
accent: string;
info: string;
}
function Spinner({ color }: { color: string }) {
return (
<span style={{
display: 'inline-block',
width: '14px',
height: '14px',
border: '2px solid var(--border)',
borderTopColor: color,
borderRadius: '50%',
animation: 'spin 0.7s linear infinite',
}} />
);
}
function SectionHeader({ label, color, badge }: { label: string; color: string; badge?: string }) {
return (
<div style={{
fontSize: '0.68rem',
fontWeight: 700,
fontFamily: 'system-ui, -apple-system, sans-serif',
color,
textTransform: 'uppercase',
letterSpacing: '0.06em',
marginBottom: '0.35rem',
display: 'flex',
alignItems: 'center',
gap: '0.4rem',
}}>
{label}
{badge && (
<span style={{ color: 'var(--text-muted)', fontWeight: 400, fontSize: '0.65rem' }}>{badge}</span>
)}
</div>
);
}
function RRFRow({ result, rank }: { result: RRFResult; rank: number }) {
return (
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.4rem',
padding: '0.3rem 0.5rem',
background: 'var(--bg-card)',
border: '1px solid var(--border)',
borderRadius: '5px',
marginBottom: '0.2rem',
fontSize: '0.72rem',
}}>
<span style={{
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
color: 'var(--text-muted)',
fontSize: '0.65rem',
minWidth: '18px',
}}>
#{rank}
</span>
<span style={{
flex: 1,
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'var(--text)',
fontWeight: 500,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{result.title}
</span>
<span style={{
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
fontSize: '0.65rem',
color: '#2e7d32',
fontWeight: 700,
flexShrink: 0,
}}>
{result.score.toFixed(4)}
</span>
</div>
);
}
// Rank badge: shows rank # with color coding for movement
function RankBadge({ label, rank, color }: { label: string; rank: number; color: string }) {
return (
<span style={{
display: 'inline-flex',
alignItems: 'center',
gap: '0.15rem',
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
fontSize: '0.6rem',
color,
fontWeight: 600,
}}>
<span style={{ color: 'var(--text-muted)', fontWeight: 400, fontSize: '0.55rem' }}>{label}</span>
#{rank}
</span>
);
}
interface RankJourneyRow {
docId: string;
title: string;
rrfRank?: number;
rerankRank?: number;
finalRank?: number;
}
// Shows each doc's rank journey: RRF #N → Reranker #N → Final #N
function RankJourney({ before, after, finalResults }: {
before: RRFResult[];
after: RerankedResult[];
finalResults?: FinalResult[];
}) {
const topLimit = 5;
const topBefore = before.slice(0, topLimit);
const topFinal = (finalResults ?? []).slice(0, topLimit);
const rerankOrder = [...after].sort((a, b) => b.rerankScore - a.rerankScore);
const titleMap = new Map<string, string>([
...before.map((result) => [result.docId, result.title] as const),
...after.map((result) => [result.docId, result.title] as const),
...topFinal.map((result) => [result.docId, result.title] as const),
]);
const rrfRankMap = new Map(before.map((result, index) => [result.docId, index + 1]));
const rerankRankMap = new Map(rerankOrder.map((r, i) => [r.docId, i + 1]));
const finalRankMap = new Map((finalResults ?? []).map((result, index) => [result.docId, index + 1]));
const rowMap = new Map<string, RankJourneyRow>();
for (const result of [...topBefore, ...topFinal]) {
const existing = rowMap.get(result.docId);
rowMap.set(result.docId, {
docId: result.docId,
title: titleMap.get(result.docId) ?? result.title,
rrfRank: rrfRankMap.get(result.docId),
rerankRank: rerankRankMap.get(result.docId),
finalRank: finalRankMap.get(result.docId),
...existing,
});
}
const rows = [...rowMap.values()]
.sort((a, b) =>
(a.finalRank ?? Number.POSITIVE_INFINITY) - (b.finalRank ?? Number.POSITIVE_INFINITY) ||
(a.rrfRank ?? Number.POSITIVE_INFINITY) - (b.rrfRank ?? Number.POSITIVE_INFINITY) ||
(a.rerankRank ?? Number.POSITIVE_INFINITY) - (b.rerankRank ?? Number.POSITIVE_INFINITY),
)
.slice(0, topLimit);
return (
<div>
{rows.map((row) => {
return (
<div key={row.docId} style={{
padding: '0.3rem 0.5rem',
background: 'var(--bg-card)',
border: '1px solid var(--border)',
borderRadius: '5px',
marginBottom: '0.2rem',
fontSize: '0.7rem',
}}>
<div style={{
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'var(--text)',
fontWeight: 500,
marginBottom: '0.2rem',
}}>
{row.title}
</div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.3rem',
}}>
{row.rrfRank !== undefined && (
<>
<RankBadge label="RRF" rank={row.rrfRank} color="var(--text-secondary)" />
<span style={{ color: 'var(--text-muted)', fontSize: '0.55rem' }}>{'\u2192'}</span>
</>
)}
{row.rerankRank !== undefined && (
<>
<RankBadge label="Reranker" rank={row.rerankRank} color="#f57f17" />
<span style={{ color: 'var(--text-muted)', fontSize: '0.55rem' }}>{'\u2192'}</span>
</>
)}
{row.finalRank !== undefined ? (
<RankBadge label="Final" rank={row.finalRank} color="#1b5e20" />
) : (
<span style={{
fontSize: '0.55rem',
color: 'var(--text-muted)',
fontStyle: 'italic',
}}>
blending...
</span>
)}
</div>
</div>
);
})}
<div style={{
fontSize: '0.62rem',
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'var(--text-muted)',
marginTop: '0.3rem',
fontStyle: 'italic',
lineHeight: 1.4,
}}>
Final ranking blends reranker scores with retrieval position.
</div>
</div>
);
}
export default function FusionColumn({ state, accent, info }: FusionColumnProps) {
const rrfDone = state.rrf.status === 'done';
const rerankRunning = state.rerank.status === 'running';
const rerankDone = state.rerank.status === 'done';
const isIdle = !rrfDone && !rerankRunning && !rerankDone;
return (
<div style={{ opacity: isIdle ? 0.45 : 1, transition: 'opacity 0.3s' }}>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '0.4rem',
marginBottom: '0.75rem',
paddingBottom: '0.5rem',
borderBottom: '1px solid var(--stage-divider)',
}}>
<span style={{
width: '3px',
height: '14px',
borderRadius: '2px',
background: accent,
flexShrink: 0,
}} />
<h3 style={{
margin: 0,
fontSize: '0.78rem',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontWeight: 700,
color: accent,
textTransform: 'uppercase',
letterSpacing: '0.05em',
}}>
Fusion & Reranking
</h3>
{rerankRunning && <Spinner color={accent} />}
<InfoTooltip text={info} />
</div>
{isIdle && (
<p style={{ fontFamily: 'system-ui, -apple-system, sans-serif', fontSize: '0.75rem', color: 'var(--text-muted)', margin: 0 }}>
Awaiting search...
</p>
)}
{rrfDone && state.rrf.data && (
<div style={{ marginBottom: '0.7rem' }}>
<SectionHeader
label="RRF Fusion"
color="#558b2f"
badge={`(${state.rrf.data.merged.length} docs)`}
/>
{state.rrf.data.merged.slice(0, 5).map((r, i) => (
<RRFRow key={r.docId} result={r} rank={i + 1} />
))}
{state.rrf.data.merged.length > 5 && (
<div style={{ fontSize: '0.68rem', color: 'var(--text-muted)', fontFamily: 'system-ui, -apple-system, sans-serif', paddingLeft: '0.25rem' }}>
+{state.rrf.data.merged.length - 5} more
</div>
)}
</div>
)}
{rerankRunning && !rerankDone && (
<p style={{ fontFamily: 'system-ui, -apple-system, sans-serif', fontSize: '0.75rem', color: 'var(--text-secondary)', margin: '0 0 0.6rem 0', fontStyle: 'italic' }}>
Reranking with cross-encoder...
</p>
)}
{rerankDone && state.rerank.data && (
<div>
<SectionHeader label="Rank Journey" color="#33691e" />
<RankJourney
before={state.rerank.data.before}
after={state.rerank.data.after}
finalResults={state.finalResults}
/>
</div>
)}
</div>
);
}