import { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import type { TranscriptEntry } from '../state/seance' export type TranscriptProps = { entries: TranscriptEntry[] streaming: { active: boolean; text: string } speakingId: string | null onReplay: (utteranceId: string) => void } function fmtFreq(source: string, f: number): string { // EVP reports Hz (voice band); radio reports MHz (FM band); emf reports a // field-strength estimate; wire is ambient. if (source === 'evp') return `${f.toFixed(0)}Hz` if (source === 'radio') return `${f.toFixed(1)}MHz` if (source === 'emf') return `field ${f.toFixed(2)}` return f >= 1000 ? `${(f / 1000).toFixed(2)}kHz` : `${f.toFixed(1)}Hz` } /** Terminal-style scrolling transmission log. */ export function Transcript({ entries, streaming, speakingId, onReplay }: TranscriptProps) { const { t } = useTranslation() const boxRef = useRef(null) useEffect(() => { const box = boxRef.current if (box) box.scrollTop = box.scrollHeight }, [entries.length, streaming.text]) return (
{entries.length === 0 && !streaming.active && (
{t('seance.transcriptEmpty')}
)} {entries.map((e) => { switch (e.kind) { case 'anomaly': return (
⚡ {e.source}{' '} {fmtFreq(e.source, e.frequency)}{' '} +{e.magnitude.toFixed(1)}dB
) case 'question': return (
{t('seance.you')} {e.text}
) case 'system': return (
{e.text}
) case 'utterance': { const speaking = speakingId === e.utteranceId return (
{e.speaker ?? '???'} “{e.text}” {e.audioUrl && ( )}
) } } })} {streaming.active && (
{t('seance.streaming')} {streaming.text} ▌
)}
) }