"use client"; import { ArrowRightIcon, LoaderCircleIcon } from "lucide-react"; import { type FormEvent, useId, useRef, useState } from "react"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./ui/accordion"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Label } from "./ui/label"; export interface FaqEntry { id: string; question: string; answer: string; } export interface FaqAnswer { status: "answered" | "not_covered"; answer: string | null; sources: FaqEntry[]; } export type FaqEvent = "question_opened" | "question_submitted" | "answer_shown" | "answer_missing"; interface InfiniteFaqProps { questions: FaqEntry[]; onAsk: (question: string) => Promise; label?: string; description?: string; onEvent?: (event: FaqEvent) => void; } type AnswerState = { kind: "idle" } | { kind: "loading" } | { kind: "error" } | { kind: "ready"; result: FaqAnswer }; export function InfiniteFaq({ questions, onAsk, label = "Ask anything else", description = "Answers from this FAQ. No invented details.", onEvent }: InfiniteFaqProps) { const id = useId(); const [question, setQuestion] = useState(""); const [state, setState] = useState({ kind: "idle" }); const request = useRef(0); const busy = state.kind === "loading"; async function submit(event: FormEvent) { event.preventDefault(); const query = question.trim(); if (!query || busy) return; const current = ++request.current; setState({ kind: "loading" }); onEvent?.("question_submitted"); try { const result = await onAsk(query); if (current !== request.current) return; setState({ kind: "ready", result }); onEvent?.(result.status === "answered" ? "answer_shown" : "answer_missing"); } catch { if (current === request.current) setState({ kind: "error" }); } } return (
value && onEvent?.("question_opened")}> {questions.map(entry => ( {entry.question} {entry.answer} ))}
{ ++request.current; setQuestion(event.target.value); setState({ kind: "idle" }); }} placeholder="What's on your mind?" maxLength={500} autoComplete="off" aria-describedby={description ? `${id}-description` : undefined} className="h-12 min-w-0 flex-1 border-border bg-background text-base" data-ph-no-capture />
{description && (

{description}

)}
{busy &&

Looking through the questions…

} {state.kind === "error" && (

Couldn't load an answer. Try again.

)} {state.kind === "ready" && (
{state.result.status === "answered" ? ( <>

{state.result.answer}

Source {state.result.sources.map(source => (

{source.question}

))}
) : (

That’s not covered in these questions. Try a more specific question, or contact the team for help.

)}
)}
); }