Download src/lib/api/flashcards.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 5.65 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/flashcards.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/api/flashcards.ts
-
curl -L -o flashcards.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/api/flashcards.ts
5.65 kB
| import { delay, demoFlashcardSet } from "@/lib/frontend-demo-data"; | |
| import { apiGet, apiPost } from "@/lib/api/client"; | |
| import { shouldUseDemoData, withDemoFallback } from "@/lib/api/mode"; | |
| import type { FlashcardGenerateRequest, FlashcardResponseItem, FlashcardSetResponse } from "@/lib/api/types"; | |
| type DemoCardWithReview = FlashcardResponseItem & { | |
| review: ReviewState; | |
| }; | |
| type ReviewResponse = { | |
| ok: boolean; | |
| set_id: string; | |
| card_index: number; | |
| new_due: string; | |
| new_ease?: number; | |
| new_interval?: number; | |
| quality: number; | |
| }; | |
| function getFlashcardItems(payload: FlashcardSetResponse["cards_json"]): FlashcardResponseItem[] { | |
| return Array.isArray(payload) ? payload : payload.cards; | |
| } | |
| export async function generateFlashcards(payload: FlashcardGenerateRequest) { | |
| if (shouldUseDemoData(payload.document_id)) { | |
| return delay({ | |
| ...demoFlashcardSet, | |
| document_id: payload.document_id, | |
| cards: getFlashcardItems(demoFlashcardSet.cards_json), | |
| }); | |
| } | |
| return withDemoFallback( | |
| () => apiPost<FlashcardSetResponse, FlashcardGenerateRequest>("/flashcards/generate", payload), | |
| () => delay({ | |
| ...demoFlashcardSet, | |
| document_id: payload.document_id, | |
| }), | |
| ); | |
| } | |
| // βββ Spaced Repetition client (SM-2 due + record) ββββββββββββββββββββββββββββββ | |
| // Works in live + local demo (augments demo cards with review state on the fly). | |
| export type ReviewState = { | |
| ease: number; | |
| interval: number; | |
| reps: number; | |
| due: string; | |
| last_reviewed?: string | null; | |
| }; | |
| export type DueCard = { | |
| set_id: string; | |
| card_index: number; | |
| front: string; | |
| back: string; | |
| hint?: string | null; | |
| due: string; | |
| ease: number; | |
| interval_days: number; | |
| source_title?: string | null; | |
| }; | |
| export type DueCardsResponse = { | |
| due_count: number; | |
| cards: DueCard[]; | |
| also_from_notes?: number; | |
| }; | |
| const demoReviewStore: Record<string, ReviewState> = {}; // key = `${setId}:${idx}` , session only for demo magic | |
| function _demoCardKey(setId: string, idx: number) { | |
| return `${setId}:${idx}`; | |
| } | |
| function _ensureDemoReview(card: FlashcardResponseItem, setId: string, idx: number): DemoCardWithReview { | |
| const key = _demoCardKey(setId, idx); | |
| if (!demoReviewStore[key]) { | |
| demoReviewStore[key] = { ease: 2.5, interval: 0, reps: 0, due: new Date().toISOString().slice(0, 10) }; | |
| } | |
| const rev = demoReviewStore[key]; | |
| return { | |
| ...card, | |
| review: rev, | |
| }; | |
| } | |
| export async function getDueFlashcards(): Promise<DueCardsResponse> { | |
| if (shouldUseDemoData("demo")) { | |
| // Build magical demo due set from the demoFlashcardSet + some note points | |
| const base = getFlashcardItems(demoFlashcardSet.cards_json); | |
| const today = new Date().toISOString().slice(0, 10); | |
| const demoDue: DueCard[] = base.map((c, i) => { | |
| const c2 = _ensureDemoReview(c, "flashcards-demo-1", i); | |
| const r = c2.review; | |
| return { | |
| set_id: "flashcards-demo-1", | |
| card_index: i, | |
| front: c.front || c2.front, | |
| back: c.back || c2.back, | |
| hint: c.hint || c.memory_hook, | |
| due: r.due || today, | |
| ease: r.ease, | |
| interval_days: r.interval || 0, | |
| source_title: "Demo Physics Notes", | |
| }; | |
| }).filter((d: DueCard) => d.due <= today); | |
| // Add 2 virtual "note points" always due for cramming demo | |
| demoDue.push({ | |
| set_id: "notes-demo", | |
| card_index: 0, | |
| front: "Key: Lenz's law opposes change in flux", | |
| back: "State direction of induced current opposes the change.", | |
| due: today, | |
| ease: 2.5, | |
| interval_days: 0, | |
| source_title: "Notes: Electromagnetic Induction", | |
| }); | |
| demoDue.push({ | |
| set_id: "notes-demo", | |
| card_index: 1, | |
| front: "Faraday's law: EMF = -dΦ/dt", | |
| back: "Magnitude of induced EMF equals rate of change of flux.", | |
| due: today, | |
| ease: 2.5, | |
| interval_days: 0, | |
| source_title: "Notes: Electromagnetic Induction", | |
| }); | |
| return delay({ due_count: demoDue.length, cards: demoDue, also_from_notes: 2 }); | |
| } | |
| return withDemoFallback( | |
| () => apiGet<DueCardsResponse>("/flashcards/due"), | |
| () => ({ due_count: 0, cards: [], also_from_notes: 0 }), | |
| ); | |
| } | |
| export async function recordCardReview(setId: string, cardIndex: number, quality: number): Promise<ReviewResponse> { | |
| if (shouldUseDemoData("demo")) { | |
| const key = _demoCardKey(setId, cardIndex); | |
| const prev = demoReviewStore[key] || { ease: 2.5, interval: 0, reps: 0, due: new Date().toISOString().slice(0, 10) }; | |
| // simple local SM-2 mirror of backend | |
| let { ease, interval, reps } = prev; | |
| if (quality < 3) { | |
| reps = 0; | |
| interval = quality === 2 ? 1 : 0; | |
| } else { | |
| reps += 1; | |
| if (reps === 1) interval = 1; | |
| else if (reps === 2) interval = 6; | |
| else interval = Math.max(1, Math.round(interval * ease)); | |
| } | |
| ease = Math.max(1.3, Math.min(2.5, ease + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)))); | |
| const nextDue = new Date(Date.now() + interval * 86400000).toISOString().slice(0, 10); | |
| demoReviewStore[key] = { ease: Math.round(ease * 100) / 100, interval, reps, due: nextDue, last_reviewed: new Date().toISOString() }; | |
| return delay({ ok: true, set_id: setId, card_index: cardIndex, new_due: nextDue, new_ease: ease, new_interval: interval, quality }); | |
| } | |
| return withDemoFallback( | |
| () => apiPost<ReviewResponse, { set_id: string; card_index: number; quality: number }>("/flashcards/review", { set_id: setId, card_index: cardIndex, quality }), | |
| () => ({ ok: true, set_id: setId, card_index: cardIndex, new_due: "demo", quality }), | |
| ); | |
| } | |