import { useState } from "react"; import { notebooksApi } from "../api/endpoints"; import type { AnswerFeedback as Feedback } from "../api/types"; const REASONS: { id: string; label: string }[] = [ { id: "forgot_chat", label: "Forgot something we said" }, { id: "mixed_chat", label: "Mixed up an earlier chat" }, { id: "wrong", label: "Wrong or made up" }, { id: "missed", label: "Missed something in my posts" }, { id: "bad_sources", label: "Bad sources" }, { id: "other", label: "Something else" }, ]; export const Thumb = ({ down }: { down?: boolean }) => ( ); const CopyGlyph = ({ done }: { done?: boolean }) => ( {done ? : ( <> > )} ); /** The answer as plain text for pasting: the [1] citation markers are left out. */ const forCopy = (text: string) => text.replace(/\s*\[\d+\](?:\[\d+\])*/g, "").trim(); /** Copy, thumbs up and thumbs down under an answer. A thumbs down opens a few reasons and an optional note. Saved as soon as it is chosen. */ export function AnswerFeedback({ messageId, initial, text }: { messageId: string; initial?: Feedback | null; text?: string }) { const [fb, setFb] = useState(initial ?? null); const [open, setOpen] = useState(false); const [reasons, setReasons] = useState(initial?.reasons ?? []); const [comment, setComment] = useState(initial?.comment ?? ""); const [error, setError] = useState(false); const [thanks, setThanks] = useState(false); const [copied, setCopied] = useState(false); const copy = async () => { try { await navigator.clipboard.writeText(forCopy(text ?? "")); setCopied(true); setTimeout(() => setCopied(false), 1600); } catch { setError(true); // the browser refused the clipboard } }; const save = async (rating: "up" | "down" | null, r: string[] = [], c = "") => { const before = fb; setError(false); setFb(rating ? { rating, reasons: r, comment: c || null } : null); // shows at once; undone if the save fails try { const res = await notebooksApi.rate(messageId, { rating, reasons: r, comment: c || undefined }); setFb(res.feedback); } catch { setFb(before); setError(true); } }; const choose = (rating: "up" | "down") => { setThanks(false); if (fb?.rating === rating) { setOpen(false); void save(null); return; } setOpen(rating === "down"); if (rating === "up") { setReasons([]); setComment(""); } void save(rating, rating === "down" ? reasons : []); }; return ( {text && ( )} choose("up")}> choose("down")}> {thanks && Thanks, saved.} {error && Could not save that. Try again.} {open && fb?.rating === "down" && ( What went wrong? (optional) {REASONS.map((r) => ( setReasons((list) => (list.includes(r.id) ? list.filter((x) => x !== r.id) : [...list, r.id]))} > {r.label} ))} setComment(e.target.value)} placeholder="Tell us more, like what it should have said" aria-label="What went wrong" /> setOpen(false)}> Skip { await save("down", reasons, comment.trim()); setOpen(false); setThanks(true); }} > Send )} ); }
What went wrong? (optional)