import { useCallback, useEffect, useMemo, useState } from "react"; import type { GenerateBody } from "../api/endpoints"; import type { Artifact, ChatSummary, FlashcardData } from "../api/types"; import { Pills, SourceFocusFields, sourceSummary, type SourceSelection } from "./SourceFocusFields"; import { ArtifactFeedback } from "./ArtifactFeedback"; import { StudyOverlay } from "./StudyOverlay"; import { Modal } from "./ui"; const LEVELS = [ { id: "easy", label: "Easy" }, { id: "medium", label: "Medium (Default)" }, { id: "hard", label: "Hard" }, ] as const; export type FlashcardsRequest = Omit; /** The flashcard settings: the same source and focus fields as the quiz and the video wizard, then how many cards. */ export function FlashcardsDialog({ notebookId, notebookTitle, chats, currentChatId, busy, error, onClose, onCreate }: { notebookId: string; notebookTitle?: string; chats: ChatSummary[]; currentChatId: string | null; busy: boolean; error: string | null; onClose: () => void; onCreate: (body: FlashcardsRequest) => void; }) { const [sel, setSel] = useState(null); const [level, setLevel] = useState<(typeof LEVELS)[number]["id"]>("medium"); return (
Level of difficulty
{!sel?.ready &&

Select at least one post or chat to generate flashcards.

} {error &&

{error}

}
); } const shuffled = (list: T[]): T[] => { const a = [...list]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }; /** A deck you flip through: tap the card (or press Space) to flip, mark it known or to review, arrows to move. Used on * its own, in a modal, and inside a report. */ export function FlashcardDeck({ cards }: { cards: FlashcardData[] }) { const [order, setOrder] = useState(() => cards.map((_, i) => i)); const [pos, setPos] = useState(0); const [flipped, setFlipped] = useState(false); const [known, setKnown] = useState>(new Set()); const card = cards[order[pos]]; const go = useCallback((d: number) => { setFlipped(false); setPos((p) => Math.min(Math.max(p + d, 0), order.length - 1)); }, [order.length]); const onKey = useCallback((e: KeyboardEvent) => { const tag = (e.target as HTMLElement | null)?.tagName; if (tag === "INPUT" || tag === "TEXTAREA") return; if (e.key === "ArrowRight") go(1); else if (e.key === "ArrowLeft") go(-1); }, [go]); useEffect(() => { document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [onKey]); const left = useMemo(() => order.filter((i) => !known.has(i)).length, [order, known]); if (!card) return

No cards.

; const mark = (isKnown: boolean) => { setKnown((cur) => { const next = new Set(cur); if (isKnown) next.add(order[pos]); else next.delete(order[pos]); return next; }); if (pos < order.length - 1) go(1); else setFlipped(false); }; return (

Card {pos + 1} of {order.length} · {known.size} known · {left} to review

); } export function FlashcardsPlayer({ artifact, onClose }: { artifact: Artifact; onClose: () => void }) { return ( ); } /** What a finished flashcard set shows in a card: its size and a way in. */ export function FlashcardsBody({ artifact }: { artifact: Artifact }) { const [open, setOpen] = useState(false); const c = artifact.content; const from = sourceSummary(c.source); return (

{c.card_count ?? 0} cards · from {from}

{open && setOpen(false)} />}
); }