"use client";
import * as React from "react";
import { BookOpen, RotateCcw } from "lucide-react";
import { CitedAnswerChatInput } from "@/components/ui/cited-answer-chat-input";
import type { CitedSentence, CitedSource, CitedStatus } from "@/components/ui/cited-answer-chat-input";
const PROMPT = "Is cold brew really less acidic than hot coffee?";
export const DEMO_SOURCES: CitedSource[] = [
{ id: "lab", title: "Acidity of cold versus hot brewed coffee", domain: "coffeescience.example", tone: 1 },
{ id: "guide", title: "Brewing temperature and extraction basics", domain: "brewguide.example", tone: 2 },
{ id: "health", title: "Coffee and stomach comfort: what the data says", domain: "wellness.example", tone: 3 },
];
export const DEMO_ANSWER: CitedSentence[] = [
{ text: "Mostly yes: cold brew usually measures a slightly higher pH, so it tastes less sharp.", cite: ["lab"] },
{ text: "Lower temperatures pull out fewer of the acids that give hot coffee its bite.", cite: ["guide"] },
{ text: "The difference is small, and many people notice comfort gains only with lighter roasts.", cite: ["lab", "health"] },
];
type DemoProps = { autoplay?: boolean; loop?: boolean; bare?: boolean; stage?: number };
type DemoState = { status: CitedStatus; prompt: string; value: string; sources: CitedSource[]; answer: CitedSentence[]; active: string | null };
const initial = (): DemoState => ({ status: "idle", prompt: "", value: "", sources: [], answer: [], active: null });
export default function CitedAnswerDemo({ autoplay = true, loop = true, bare = false, stage = 464 }: DemoProps) {
const [state, setState] = React.useState(initial);
const [motion, setMotion] = React.useState({ ready: false, reduced: true });
const [run, setRun] = React.useState({ id: 0, source: "auto", prompt: PROMPT });
const [paused, setPaused] = React.useState(false);
React.useEffect(() => {
const media = window.matchMedia("(prefers-reduced-motion: reduce)");
const update = () => setMotion({ ready: true, reduced: media.matches });
update();
media.addEventListener("change", update);
return () => media.removeEventListener("change", update);
}, []);
React.useEffect(() => {
if (!motion.ready) return;
if (run.source === "auto" && (!autoplay || motion.reduced || paused)) return;
const timers: number[] = [];
const later = (ms: number, callback: () => void) => timers.push(window.setTimeout(callback, ms));
const example = run.prompt === PROMPT;
const sources = example ? DEMO_SOURCES : [{ id: "notes", title: "Your connected knowledge base", domain: "docs.example", tone: 4 as const }];
const answer: CitedSentence[] = example ? DEMO_ANSWER : [
{ text: "This is a local demo, so there is no live search behind it.", cite: ["notes"] },
{ text: `Your question was: “${run.prompt}”`, cite: [] },
{ text: "Connect your own retrieval pipeline to stream a cited answer here.", cite: ["notes"] },
];
const offset = run.source === "auto" ? 1000 : 0;
if (run.source === "auto") {
setState(initial());
for (let index = 1; index <= PROMPT.length; index += 1) later(200 + (index / PROMPT.length) * 620, () => setState((previous) => ({ ...previous, value: PROMPT.slice(0, index) })));
}
later(offset, () => setState({ status: "reading", prompt: run.prompt, value: "", sources: [], answer: [], active: null }));
sources.forEach((source, index) => {
const start = offset + 300 + index * 500;
later(start, () => setState((previous) => ({ ...previous, sources: [...previous.sources, { ...source, progress: 0.1 }] })));
[0.45, 0.8, 1].forEach((progress, step) => later(start + 200 + step * 200, () => setState((previous) => ({ ...previous, sources: previous.sources.map((s) => (s.id === source.id ? { ...s, progress } : s)) }))));
});
const answerAt = offset + 300 + sources.length * 500 + 400;
later(answerAt, () => setState((previous) => ({ ...previous, status: "answering", sources: previous.sources.map((s) => ({ ...s, progress: 1 })) })));
answer.forEach((_sentence, index) => later(answerAt + 200 + index * 700, () => setState((previous) => ({ ...previous, answer: answer.slice(0, index + 1) }))));
const doneAt = answerAt + 200 + answer.length * 700 + 100;
later(doneAt, () => setState((previous) => ({ ...previous, status: "done" })));
if (example) {
later(doneAt + 300, () => setState((previous) => ({ ...previous, active: "lab" })));
later(doneAt + 1300, () => setState((previous) => ({ ...previous, active: null })));
}
if (loop && run.source === "auto") later(doneAt + 2600, () => setRun((previous) => ({ ...previous, id: previous.id + 1 })));
return () => timers.forEach((timer) => window.clearTimeout(timer));
}, [autoplay, loop, motion.ready, motion.reduced, paused, run]);
const takeOver = () => setPaused(true);
return (
<div className="flex min-h-screen w-full items-center justify-center bg-background p-5 font-sans text-foreground">
<div className="w-full max-w-[480px]">
{!bare && (
<header className="mb-4">
<div className="mb-2 flex items-center gap-2 text-[10px] font-medium uppercase tracking-[0.2em] text-muted-foreground"><BookOpen className="size-3" aria-hidden="true" /> Every claim, footnoted</div>
<h1 className="text-[25px] font-medium leading-tight tracking-tight">Answers you can check.</h1>
</header>
)}
<div className="flex items-end" style={{ height: stage }} data-demo-stage>
<CitedAnswerChatInput
status={state.status}
prompt={state.prompt}
value={state.value}
onValueChange={(value) => { if (run.source === "auto") takeOver(); setState((previous) => ({ ...previous, value })); }}
sources={state.sources}
answer={state.answer}
activeSource={state.active}
onActiveSourceChange={(active) => setState((previous) => ({ ...previous, active }))}
onSubmit={(prompt) => { setPaused(false); setRun((previous) => ({ id: previous.id + 1, source: "user", prompt })); }}
onStop={() => { takeOver(); setRun((previous) => ({ ...previous, source: "auto" })); setState((previous) => ({ ...initial(), value: previous.prompt })); }}
answerNote={state.prompt === PROMPT ? "Sample answer · Sources are illustrative." : undefined}
/>
</div>
{!bare && (
<footer className="mt-4 flex items-center justify-between gap-3 text-[10px] text-muted-foreground">
<span>Sample conversation · No live search</span>
<button type="button" data-replay onClick={() => { setPaused(false); setRun((previous) => ({ id: previous.id + 1, source: "user", prompt: PROMPT })); }} className="flex items-center gap-1.5 rounded px-1 py-1 outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring"><RotateCcw className="size-3" aria-hidden="true" /> Replay demo</button>
</footer>
)}
</div>
</div>
);
}