Teach it back: the AI becomes the curious student
A new mode where the user explains a topic in their own words and the AI plays a curious classmate: short reactions, exactly one follow-up question at a time, never "correct"/"wrong", never lecturing, and everything it knows comes from the user's own decks and notes. - Entry points: a sidebar row next to Exam mode and a "Teach it back" action in every deck's menu (which prefills the topic and scope). - Start screen: deck or typed topic, friendly/skeptical student, the same context and model chips as Chat (/teach?deck=<id> pre-selects the deck). - Session: the student prompt is a mode of the existing /api/chat route, so model resolution, allowlists, rate limits and the streaming client are the same code; the personality lives in lib/prompts.ts (TEACH_*). The deck's cards travel as a numbered checklist and the student is told to ask about the ideas that were skipped; it must say so rather than guess when the notes cover nothing. One constant (TEACH_MAX_EXCHANGES = 10) ends the session, or the Finish button does. - Recap: a teach_summary call (JSON, one repair retry) yields well / gaps with card references / next steps; each gap links to the card it means. "Add missed cards to review" schedules those activities due now; "Turn gaps into cards" generates one flashcard per gap from the deck's notes and shows an editable review list before saving. - Sessions are chat conversations with a mode:"teach" marker + session record: same IndexedDB history, backup and sync, filtered out of the Chat screens; the recent list reopens a recap or continues a session. - Reused pieces: MessageRow, Composer (now optional attach/scope chips and a mic), Markdown/KaTeX, AiDisclaimer, ContextPicker/ModelPicker, plus a shared useChatModels hook and useKeyboardInset extracted from Chat. - Dictation: Web Speech API recognition (lib/speech-input.ts); the mic only renders where the browser supports it, and dictated phrases land in the draft. - All strings in en + cs. teach-test: 44 checks (API prompts and guards, both entry points, the session, recap actions, history, exchange limit, 360/390px layouts, mic on/off). Version 0.8.1, SW v103.
This commit is contained in:
parent
3fb491c54d
commit
6036238bf0
25 changed files with 2821 additions and 181 deletions
|
|
@ -226,6 +226,53 @@ export function recordAnswer(input: AnswerInput, at = Date.now()): void {
|
|||
}
|
||||
|
||||
/** Used by backup import and sync pulls. */
|
||||
/**
|
||||
* Puts specific cards on the review schedule, due right now — Teach it back's
|
||||
* "add the missed cards to review". A card that was already scheduled keeps
|
||||
* its ease and lapse count; only the due date is pulled forward. Returns how
|
||||
* many cards were scheduled.
|
||||
*/
|
||||
export function scheduleForReview(deckId: string, activityIds: string[], at = Date.now()): number {
|
||||
const changed: SrsRecord[] = [];
|
||||
for (const activityId of activityIds) {
|
||||
const key = `${deckId}:${activityId}`;
|
||||
const previous = schedules.get(key);
|
||||
changed.push({
|
||||
key,
|
||||
deckId,
|
||||
activityId,
|
||||
ease: previous?.ease ?? 2.5,
|
||||
reps: previous?.reps ?? 0,
|
||||
intervalDays: 0,
|
||||
lapses: previous?.lapses ?? 0,
|
||||
dueAt: at,
|
||||
lastAt: at,
|
||||
lastCorrect: previous?.lastCorrect ?? false,
|
||||
});
|
||||
}
|
||||
if (changed.length === 0) return 0;
|
||||
|
||||
schedules = new Map(schedules);
|
||||
for (const schedule of changed) schedules.set(schedule.key, schedule);
|
||||
notify();
|
||||
markSyncDirty();
|
||||
|
||||
void (async () => {
|
||||
try {
|
||||
await ensureLearningHydrated();
|
||||
await withDb("readwrite", (db) => {
|
||||
const transaction = db.transaction(SRS, "readwrite");
|
||||
const store = transaction.objectStore(SRS);
|
||||
for (const schedule of changed) store.put(schedule);
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn("[freezmo] could not schedule the review cards", error);
|
||||
}
|
||||
})();
|
||||
|
||||
return changed.length;
|
||||
}
|
||||
|
||||
export async function replaceLearning(nextAnswers: AnswerRecord[], nextSrs: SrsRecord[]): Promise<void> {
|
||||
const sorted = [...nextAnswers].sort((a, b) => b.at - a.at).slice(0, MAX_ANSWERS);
|
||||
answers = sorted;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue