40858e0025
Deterministic, on-device, no model: - src/lib/learn pure modules (tokenize, summary [TextRank-ish], glossary [definition-pattern + frequency], flashcards [cloze/Q-A], srs [SM-2], quiz [MCQ with distractors]) — 37 unit tests. - Flashcard persistence: Dexie v4 + native v4 `flashcards` table; create/list/ listDue/updateSrs/delete/counts; cascades (transcript delete, course->Unsorted). - UI: transcript "Study aids" (generate summary+glossary, click-to-seek; create flashcards), Study screen (SM-2 review + Anki CSV export), per-lecture Quiz, library Study link with due-count badge. 215 tests green, 0 tsc errors, web export builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
52 lines
1.7 KiB
TypeScript
52 lines
1.7 KiB
TypeScript
// Turn glossary entries into flashcard seeds. Deterministic, no model.
|
|
// A seed is a UI-friendly front/back pair carrying its source provenance; the
|
|
// repo later wraps it into a FlashcardDraft + SRS state.
|
|
//
|
|
// IMPORTANT: relative imports only inside src/lib (vitest has no '@/*' alias).
|
|
|
|
import type { GlossaryEntry } from './glossary';
|
|
|
|
/** A front/back pair for a flashcard, with optional source anchor. */
|
|
export interface CardSeed {
|
|
front: string;
|
|
back: string;
|
|
/** Source segment id, for click-to-seek. */
|
|
segmentId?: string;
|
|
/** Source start time (seconds). */
|
|
start?: number;
|
|
}
|
|
|
|
/**
|
|
* Build one {@link CardSeed} per glossary entry.
|
|
*
|
|
* Front: if the term appears verbatim (case-insensitively) inside the
|
|
* definition, we make a cloze deletion — blanking the term in the definition so
|
|
* the learner recalls it in context. Otherwise we fall back to the plain
|
|
* "What is {term}?" prompt. Back is always the full definition.
|
|
*/
|
|
export function cardsFromGlossary(entries: GlossaryEntry[]): CardSeed[] {
|
|
return entries.map((entry) => {
|
|
const front = makeFront(entry.term, entry.definition);
|
|
return {
|
|
front,
|
|
back: entry.definition,
|
|
segmentId: entry.segmentId,
|
|
start: entry.start,
|
|
};
|
|
});
|
|
}
|
|
|
|
/** "_____" cloze if the term occurs in the definition, else a Q prompt. */
|
|
function makeFront(term: string, definition: string): string {
|
|
const re = new RegExp(escapeRegExp(term), 'i');
|
|
if (re.test(definition)) {
|
|
return definition.replace(re, '_____');
|
|
}
|
|
return `What is ${term}?`;
|
|
}
|
|
|
|
/** Escape a string for safe use inside a RegExp. */
|
|
function escapeRegExp(s: string): string {
|
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
}
|