qmd-web / src /components /ExpansionColumn.tsx
shreyask's picture
Deploy qmd-web
ac50275 verified
import type { ExpandedQuery } from '../types';
import { InfoTooltip } from './PipelineView';
interface ExpansionColumnState {
status: 'idle' | 'running' | 'done' | 'error';
data?: ExpandedQuery;
error?: string;
}
interface ExpansionColumnProps {
state: ExpansionColumnState;
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 CompactCard({ label, content }: { label: string; content: string | string[] }) {
const text = Array.isArray(content) ? content.join('\n') : content;
return (
<div style={{
background: 'var(--bg-card)',
border: '1px solid var(--border)',
borderRadius: '5px',
padding: '0.4rem 0.6rem',
marginBottom: '0.3rem',
}}>
<div style={{
fontSize: '0.65rem',
fontWeight: 700,
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'var(--text-muted)',
textTransform: 'uppercase',
letterSpacing: '0.06em',
marginBottom: '0.2rem',
}}>
{label}
</div>
<div style={{
fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace",
fontSize: '0.68rem',
color: 'var(--text)',
lineHeight: 1.5,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}>
{text}
</div>
</div>
);
}
function VariantSummary({ data }: { data: ExpandedQuery }) {
const count =
(data.lex.trim() ? 1 : 0) +
data.vec.length +
(data.hyde.trim() ? 1 : 0);
if (count === 0) return null;
return (
<div style={{
fontSize: '0.72rem',
fontFamily: 'system-ui, -apple-system, sans-serif',
color: 'var(--text-secondary)',
marginBottom: '0.4rem',
padding: '0.3rem 0.5rem',
background: 'var(--bg-card)',
border: '1px solid var(--border)',
borderRadius: '5px',
}}>
Expanded into <strong style={{ color: 'var(--text)' }}>{count}</strong> search variant{count !== 1 ? 's' : ''}
</div>
);
}
export default function ExpansionColumn({ state, accent, info }: ExpansionColumnProps) {
const isIdle = state.status === 'idle';
const isRunning = state.status === 'running';
const isDone = state.status === 'done';
const isError = state.status === 'error';
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',
}}>
Query Expansion
</h3>
{isRunning && <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 query...
</p>
)}
{isRunning && (
<p style={{
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '0.75rem',
color: 'var(--text-secondary)',
margin: 0,
fontStyle: 'italic',
}}>
Generating...
</p>
)}
{isError && (
<div style={{
padding: '0.45rem 0.6rem',
background: 'var(--error-bg)',
border: '1px solid var(--error-border)',
borderRadius: '5px',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '0.75rem',
color: '#c62828',
}}>
{state.error}
</div>
)}
{isDone && state.data && (
<>
{state.data.note && (
<div style={{
padding: '0.35rem 0.5rem',
marginBottom: '0.3rem',
background: 'var(--bg-card)',
border: '1px solid var(--border)',
borderRadius: '5px',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '0.72rem',
color: 'var(--text-secondary)',
lineHeight: 1.45,
}}>
{state.data.note}
</div>
)}
<VariantSummary data={state.data} />
{state.data.lex.trim() && (
<CompactCard label="Lex" content={state.data.lex} />
)}
{state.data.vec.length > 0 && (
<CompactCard label="Vec" content={state.data.vec} />
)}
{state.data.hyde.trim() && (
<CompactCard label="HyDE" content={state.data.hyde} />
)}
</>
)}
</div>
);
}