import { useEffect, useState } from 'react'; import type { ConversationSummary } from '@chat-app/shared/chat'; import { useCall } from '../context/CallContext'; interface Props { conversation: ConversationSummary; } const STALE_AFTER_MS = 5000; /** Discord-style live-captions overlay. Pinned to the bottom-center of the * call surface; renders the most recent caption per participant, fading * entries out after `STALE_AFTER_MS` of silence. Self-captions are shown * too so the speaker can sanity-check what's being broadcast. */ export function CallCaptionsOverlay({ conversation }: Props) { const { captions } = useCall(); // Re-render every second so stale entries fade without needing the data // channel to fire — captions module just stores timestamps. const [, setNow] = useState(Date.now()); useEffect(() => { const id = window.setInterval(() => setNow(Date.now()), 1000); return () => window.clearInterval(id); }, []); const now = Date.now(); const visible = Object.entries(captions) .filter(([, v]) => now - v.timestamp < STALE_AFTER_MS) .sort(([, a], [, b]) => a.timestamp - b.timestamp); if (visible.length === 0) return null; return (
{visible.map(([identity, c]) => { const member = conversation.members.find((m) => m.userId === identity); const name = member?.profile?.displayName ?? '?'; const age = now - c.timestamp; const opacity = age > 3500 ? 1 - (age - 3500) / (STALE_AFTER_MS - 3500) : 1; return (
{name} {c.text}
); })}
); }