const { useMemo, useRef, useState, useEffect } = React; function normalizeInputMessage(raw) { return (raw || "") .replace(/\r\n/g, "\n") .replace(/\n{3,}/g, "\n\n") .trim(); } function clip(value, max = 80) { if (!value || value.length <= max) { return value || ""; } return `${value.slice(0, max - 3)}...`; } function titleCase(text) { return (text || "") .split(" ") .filter(Boolean) .map((token) => token.charAt(0).toUpperCase() + token.slice(1)) .join(" "); } function inferTitleFromPath(sourcePath) { const raw = (sourcePath || "").split("/").pop() || ""; const withoutExt = raw.replace(/\.md$/i, ""); if (!withoutExt) { return ""; } const segments = withoutExt.split("_").filter(Boolean); let core = segments.length > 1 ? segments[1] : segments[0]; const platform = segments.length > 2 ? segments[segments.length - 1] : ""; core = core.replace(/[-_]+/g, " ").trim(); const prettyCore = titleCase(core); if (platform && /^[a-z0-9-]+$/i.test(platform)) { return `${prettyCore} (${titleCase(platform.replace(/-/g, " "))})`; } return prettyCore; } function formatReferenceLabel(item, index) { const heading = (item.heading || "").trim(); let label = ""; if (heading && heading.toLowerCase() !== "unknown heading") { const parts = heading .split(">") .map((part) => part.trim()) .filter(Boolean); if (parts.length >= 2) { label = `${parts[0]} - ${parts[1]}`; } else if (parts.length === 1) { label = parts[0]; } } if (!label) { label = inferTitleFromPath(item.source_path); } if (!label) { label = `Reference ${index}`; } return `[${index}] ${clip(label, 64)}`; } function dedupeCitations(citations) { const seen = new Set(); const list = []; (citations || []).forEach((item) => { const key = `${item.source_link || ""}::${item.heading || ""}`; if (seen.has(key)) { return; } seen.add(key); list.push(item); }); return list; } function TypingBubble() { return (
Thinking
); } function MessageItem({ turn, meta, index }) { const isUser = turn.role === "user"; const citations = useMemo(() => dedupeCitations(meta?.citations), [meta?.citations]); return (

{(turn.content || "").trim()}

{!isUser && citations.length > 0 && (

References

{citations.map((item, refIdx) => ( {formatReferenceLabel(item, refIdx + 1)} ))}
{Number.isFinite(meta?.latency_ms) && (

latency {meta.latency_ms} ms

)}
)}
{isUser ? "You" : "DocAgent"} {isUser ? "" : `• #${index + 1}`}
); } function ChatApp() { const [sessionId, setSessionId] = useState(null); const [input, setInput] = useState(""); const [history, setHistory] = useState([]); const [assistantMetaHistory, setAssistantMetaHistory] = useState([]); const [sending, setSending] = useState(false); const listRef = useRef(null); useEffect(() => { if (listRef.current) { listRef.current.scrollTop = listRef.current.scrollHeight; } }, [history, sending]); function alignAssistantMeta(nextHistory, appendedMeta) { const nextAssistantCount = nextHistory.filter((turn) => turn.role === "assistant").length; let nextMeta = [...assistantMetaHistory]; if (typeof appendedMeta !== "undefined") { nextMeta = [...nextMeta, appendedMeta]; } if (nextAssistantCount === 0) { return []; } if (nextMeta.length > nextAssistantCount) { return nextMeta.slice(-nextAssistantCount); } if (nextMeta.length < nextAssistantCount) { return [ ...Array.from({ length: nextAssistantCount - nextMeta.length }, () => null), ...nextMeta, ]; } return nextMeta; } async function callApi(path, body) { const resp = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!resp.ok) { const text = await resp.text(); throw new Error(`HTTP ${resp.status}: ${text}`); } return resp.json(); } async function sendMessage(event) { if (event) { event.preventDefault(); } const message = normalizeInputMessage(input); if (!message || sending) { return; } const optimisticHistory = [...history, { role: "user", content: message }]; setHistory(optimisticHistory); setInput(""); setSending(true); try { const data = await callApi("/api/chat", { message, session_id: sessionId, }); const nextHistory = Array.isArray(data.history) ? data.history : optimisticHistory; setSessionId(data.session_id || sessionId); setHistory(nextHistory); setAssistantMetaHistory( alignAssistantMeta(nextHistory, { citations: data.citations || [], latency_ms: data.latency_ms, }) ); } catch (err) { const failHistory = [ ...optimisticHistory, { role: "assistant", content: `Request failed: ${err.message}` }, ]; setHistory(failHistory); setAssistantMetaHistory(alignAssistantMeta(failHistory, null)); } finally { setSending(false); } } async function resetSession() { if (sending) { return; } try { if (sessionId) { await callApi("/api/chat/reset", { session_id: sessionId }); } setSessionId(null); setHistory([]); setAssistantMetaHistory([]); } catch {} } let assistantIndex = 0; const showLanding = history.length === 0 && !sending; const composer = (
setInput(event.target.value)} placeholder="Ask Anything About Agora Product" className="h-11 flex-1 rounded-full bg-transparent px-3 text-[15px] text-slate-800 outline-none placeholder:text-slate-400" />
); return (

Agora Document Agent

Session {sessionId || "-"}
{showLanding ? (

What can I help with?

{composer}
) : ( <>
{history.map((turn, idx) => { const meta = turn.role === "assistant" ? assistantMetaHistory[assistantIndex++] : null; return ( ); })} {sending && }
{composer} )}
); } const root = ReactDOM.createRoot(document.getElementById("chat-root")); root.render();