import { useEffect, useRef, useState } from 'react' import { useNavigate, useLocation } from 'react-router-dom' import { WEBHOOK_URL } from '../lib/webrtc' const TYPE_META = { university: { label: 'University Call Agent', accent: '#60d0f0', icon: '⬡' }, realestate: { label: 'Real Estate Call Agent', accent: '#f0b060', icon: '⬢' }, coaching: { label: 'Coaching Institute Agent', accent: '#b088f0', icon: '◆' }, medical: { label: 'Medical OPD Agent', accent: '#5fd0a0', icon: '✚' }, hotel: { label: 'Hotel Concierge Agent', accent: '#f0a0c0', icon: '❖' }, } export default function CallRoom({ type, sessionId }) { const navigate = useNavigate() const { state } = useLocation() const chatEndRef = useRef(null) const [messages, setMessages] = useState([]) const [connected, setConnected] = useState(false) const [copied, setCopied] = useState(false) const [elapsed, setElapsed] = useState(0) const meta = TYPE_META[type] || TYPE_META.university const phone = state?.phone || '' const agentName = state?.agentName || 'Agent' const watchUrl = `${window.location.origin}${window.location.pathname}#/call/${type}/${sessionId}` useEffect(() => { const t = setInterval(() => setElapsed(s => s + 1), 1000) return () => clearInterval(t) }, []) const fmtTime = s => { const m = Math.floor(s / 60).toString().padStart(2, '0') const ss = (s % 60).toString().padStart(2, '0') return `${m}:${ss}` } useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) useEffect(() => { const es = new EventSource(`${WEBHOOK_URL}/events?session=${sessionId}`) es.onopen = () => setConnected(true) es.onmessage = e => { try { const data = JSON.parse(e.data) console.log('[sse] raw event:', JSON.stringify(data).slice(0, 400)) const responses = data.responses || [] const msgs = [] // User transcript const userEntry = responses.find(r => !r.streaming && r.out?.user_input) if (userEntry) { const text = userEntry.out.user_input .replace(/[\s\S]*?<\/turn-visual-context>/g, '') .trim() if (text) msgs.push({ role: 'user', text }) } // AI text — main LLM only (prev_llm is audio filler, not transcript) const aiRaw = responses .filter(r => r.streaming && r.chunk && r.node === 'llm') .map(r => r.chunk) .join('') const aiText = aiRaw .split(/<\|speak\|>/) .map(seg => seg.replace(/<\|hangup\|>\S*/g, '').trim()) .filter(Boolean) .join(' ') .trim() if (aiText) msgs.push({ role: 'ai', text: aiText }) if (msgs.length) setMessages(prev => [...prev, ...msgs]) } catch (err) { console.error('[sse] parse/render error:', err, 'raw:', e.data?.slice(0, 200)) } } es.onerror = () => setConnected(false) return () => es.close() }, [sessionId]) function copyLink() { navigator.clipboard.writeText(watchUrl).then(() => { setCopied(true) setTimeout(() => setCopied(false), 2000) }) } return (
{/* topbar */}
{meta.icon} {meta.label} {phone && ( {phone} )}
{fmtTime(elapsed)}
{connected ? 'Live' : 'Waiting'}
{/* body */}
{/* main chat */}
{messages.length === 0 ? (
{meta.icon}

{connected ? 'Call connected — waiting for conversation…' : 'Connecting to call…'}

Messages will appear here as the conversation unfolds.

) : ( messages.map((msg, i) => (
{msg.role === 'user' ? 'Caller' : agentName}
{msg.text}
)) )}
{/* sidebar */}
) } const s = { root: { height: '100vh', display: 'flex', flexDirection: 'column', overflow: 'hidden' }, topbar: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 28px', height: 56, borderBottom: '1px solid var(--border)', flexShrink: 0, }, topLeft: { display: 'flex', alignItems: 'center', gap: 12 }, topRight: { display: 'flex', alignItems: 'center', gap: 12 }, typeChip: { fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '.12em', textTransform: 'uppercase', padding: '3px 9px', borderRadius: 20, border: '1px solid', }, phone: { fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--muted)' }, timer: { fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--muted)', letterSpacing: '.1em' }, liveChip: { display: 'flex', alignItems: 'center', gap: 6, fontFamily: 'var(--mono)', fontSize: 10, letterSpacing: '.14em', textTransform: 'uppercase', padding: '4px 10px', borderRadius: 20, border: '1px solid var(--border2)', }, liveOn: { color: 'var(--accent)', borderColor: 'rgba(200,240,96,.3)' }, liveOff: { color: 'var(--muted)' }, liveDot: { width: 5, height: 5, borderRadius: '50%', flexShrink: 0 }, body: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'row', overflow: 'hidden' }, chatArea: { flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, chatScroll: { flex: 1, overflowY: 'auto', padding: '24px 28px', display: 'flex', flexDirection: 'column', gap: 16, }, emptyState: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 12, flex: 1, paddingTop: 80, }, emptyIcon: { fontSize: 32, opacity: .4 }, emptyTitle: { fontFamily: 'var(--sans)', fontSize: 15, fontWeight: 600, color: 'var(--text)', opacity: .6 }, emptyHint: { fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--muted)', textAlign: 'center', maxWidth: 360, lineHeight: 1.6, letterSpacing: '.06em', }, msgRow: { display: 'flex', flexDirection: 'column', gap: 5, maxWidth: 640 }, msgRowUser: { alignSelf: 'flex-end', alignItems: 'flex-end' }, msgRowAi: { alignSelf: 'flex-start', alignItems: 'flex-start' }, role: { fontFamily: 'var(--mono)', fontSize: 8, letterSpacing: '.2em', textTransform: 'uppercase' }, bubble: { fontSize: 13, lineHeight: 1.6, padding: '10px 14px', borderRadius: 12, wordBreak: 'break-word' }, bubbleUser: { background: 'rgba(200,240,96,.08)', border: '1px solid rgba(200,240,96,.18)', color: 'var(--text)', borderBottomRightRadius: 3, }, bubbleAi: { background: 'var(--surface)', border: '1px solid', color: 'var(--text)', borderBottomLeftRadius: 3, }, sidebar: { width: 220, flexShrink: 0, borderLeft: '1px solid var(--border)', background: 'var(--surface)', display: 'flex', flexDirection: 'column', padding: '24px 18px', gap: 0, }, sideSection: { display: 'flex', flexDirection: 'column', gap: 6 }, sideLabel: { fontFamily: 'var(--mono)', fontSize: 8, letterSpacing: '.22em', textTransform: 'uppercase', color: 'var(--muted)', }, sideValue: { fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--text)' }, sideDivider: { height: 1, background: 'var(--border)', margin: '20px 0' }, shareUrl: { fontFamily: 'var(--mono)', fontSize: 9, color: 'var(--muted)', wordBreak: 'break-all', lineHeight: 1.5, marginBottom: 8, }, copyBtn: { fontFamily: 'var(--mono)', fontSize: 9, letterSpacing: '.12em', padding: '7px 0', borderRadius: 6, border: '1px solid', background: 'transparent', cursor: 'pointer', width: '100%', textAlign: 'center', transition: 'border-color .15s, color .15s', }, homeBtn: { fontFamily: 'var(--mono)', fontSize: 10, color: 'var(--muted)', background: 'none', border: 'none', cursor: 'pointer', letterSpacing: '.08em', padding: 0, textAlign: 'left', marginTop: 'auto', }, }