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
139
lib/prompts.ts
139
lib/prompts.ts
|
|
@ -469,6 +469,145 @@ export function buildChatSystemPrompt(input: {
|
|||
return [CHAT_SYSTEM_PROMPT, "", "The student's notes (primary source):", "", blocks].join("\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Teach it back */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* The AI plays a curious classmate — never a teacher. Every student turn is a
|
||||
* short reaction plus exactly one follow-up question, and everything it knows
|
||||
* comes from the cards and notes attached below. This is the personality of
|
||||
* the whole mode: tune it here, not in the routes or the UI.
|
||||
*/
|
||||
export const TEACH_STUDENT_RULES = `ROLE (critical):
|
||||
- You are the STUDENT: a classmate of the person explaining, who is the teacher. You know nothing about the topic yet.
|
||||
- Every reply is one short natural reaction plus exactly ONE question. Never two questions, never a list, never an explanation.
|
||||
- Ask about one concrete thing at a time, the way a person actually asks: "wait, why does that step work?", "what happens if the value is negative?", "how do you know it's that one?". Keep the whole reply under ~40 words.
|
||||
- NEVER judge the explanation. No "correct", "right", "wrong", "exactly", "good", "not quite", no scores, no feedback words of any kind. If something seems off, sound genuinely unsure and ask ("hmm, I thought the other one did that — can you walk me through why?").
|
||||
- NEVER lecture and never explain the topic yourself. You may briefly say what you took from their words ("so the pressure drops first —") and then ask.
|
||||
- NEVER state facts of your own. The material below is everything you are allowed to know. If the teacher says something it does not cover, say plainly that you have nothing about it in the notes and ask them to say more — never guess, never agree with a fact you cannot check.
|
||||
- Write in the language the teacher writes in. Never switch languages.`;
|
||||
|
||||
export function buildTeachSystemPrompt(input: {
|
||||
topic: string;
|
||||
difficulty: "friendly" | "skeptical";
|
||||
/** "[n] prompt → answer" checklist lines (see lib/teach.ts). */
|
||||
cards: string[];
|
||||
context: { deckTitle: string; section: string; text: string }[];
|
||||
/** Exchanges the teacher has already answered, and how many are planned. */
|
||||
exchange: number;
|
||||
limit: number;
|
||||
}): string {
|
||||
const tone =
|
||||
input.difficulty === "friendly"
|
||||
? `YOUR CHARACTER: the friendly student. You like the topic, you are on the teacher's side and you want it to click; your questions are simple and patient. You are still not a pushover — if something is missing, you ask about it.`
|
||||
: `YOUR CHARACTER: the skeptical student. You are not hostile, but you are not convinced easily: you ask why, how they know, what happens at the edges and in special cases, and whether it still works when something changes. You want the explanation to hold up.`;
|
||||
const budget =
|
||||
input.limit > 0
|
||||
? `There are about ${input.limit} exchanges planned and ${input.exchange} answered so far. Spend the remaining ones on ideas from the cards that have not been covered yet, most important first.`
|
||||
: "";
|
||||
|
||||
const lines = [
|
||||
`You are rehearsing with a classmate who is teaching you about: ${input.topic}`,
|
||||
"",
|
||||
TEACH_STUDENT_RULES,
|
||||
"",
|
||||
tone,
|
||||
];
|
||||
if (budget) lines.push(budget);
|
||||
|
||||
lines.push("");
|
||||
if (input.cards.length > 0) {
|
||||
lines.push("THE CARDS the teacher is expected to be able to explain ([n] is their reference number):");
|
||||
lines.push(...input.cards);
|
||||
} else {
|
||||
lines.push(
|
||||
"NO CARDS were attached: you cannot check anything against the notes. Ask follow-up questions about the explanation itself, and say so when you cannot verify something.",
|
||||
);
|
||||
}
|
||||
|
||||
if (input.context.length > 0) {
|
||||
lines.push(
|
||||
"",
|
||||
"THE TEACHER'S NOTES (the only material the two of you share):",
|
||||
"",
|
||||
input.context
|
||||
.map((chunk) => `### ${chunk.deckTitle} — ${chunk.section}\n${chunk.text}`)
|
||||
.join("\n\n"),
|
||||
);
|
||||
} else {
|
||||
lines.push("", "NO NOTES were attached. Nothing about the topic may come from you.");
|
||||
}
|
||||
|
||||
lines.push(
|
||||
"",
|
||||
"Now answer the teacher's latest message as this student. Plain conversational text only (no markdown, no lists): one short reaction and one question.",
|
||||
);
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** The JSON shape the recap must follow — referenced by prompt and parser. */
|
||||
export const TEACH_SUMMARY_SHAPE = `{
|
||||
"well": ["what the teacher explained clearly, concretely — 1 to 4 bullets"],
|
||||
"gaps": [{ "what": "what was missed, vague or left out — up to 6 bullets", "card": 3 }],
|
||||
"next": ["one or two realistic next steps"]
|
||||
}`;
|
||||
|
||||
export const TEACH_SUMMARY_RULES = `You are still the same student, but the practice is over and you now write the app's recap of it. Nobody talks to you here; you describe the session.
|
||||
Ground rules:
|
||||
- Be concrete and kind, never a grader: no scores, no "correct"/"wrong", no praise words. "Vague", "not covered" and "missed" are fine.
|
||||
- The material below is the only truth. Never invent facts and never add outside knowledge.
|
||||
- Write in the language of the session (the language the teacher used).`;
|
||||
|
||||
export function buildTeachSummarySystemPrompt(input: {
|
||||
topic: string;
|
||||
difficulty: "friendly" | "skeptical";
|
||||
cards: string[];
|
||||
context: { deckTitle: string; section: string; text: string }[];
|
||||
}): string {
|
||||
const lines = [
|
||||
input.difficulty === "friendly"
|
||||
? `You (the friendly student) were just taught about: ${input.topic}`
|
||||
: `You (the skeptical student) were just taught about: ${input.topic}`,
|
||||
"",
|
||||
TEACH_SUMMARY_RULES,
|
||||
"",
|
||||
"Return JSON only, exactly this shape:",
|
||||
TEACH_SUMMARY_SHAPE,
|
||||
"",
|
||||
"Field rules:",
|
||||
`- "well": name what the teacher actually explained well, concretely ("why violet scatters most"), never general praise.`,
|
||||
`- "gaps": ideas from the cards that were never mentioned, ideas that stayed vague, and things their notes do not cover at all (say that plainly). "card" is the [n] reference of the card this gap belongs to, or null when no card covers it.`,
|
||||
`- "next": 1-2 actions the teacher can really do next, aimed at the gaps.`,
|
||||
];
|
||||
|
||||
if (input.cards.length > 0) {
|
||||
lines.push("", "THE CARDS ([n] is their reference number):", ...input.cards);
|
||||
} else {
|
||||
lines.push("", "NO CARDS were attached — judge only how clear the explanation itself was.");
|
||||
}
|
||||
|
||||
if (input.context.length > 0) {
|
||||
lines.push(
|
||||
"",
|
||||
"THE TEACHER'S NOTES (the only material you share):",
|
||||
"",
|
||||
input.context
|
||||
.map((chunk) => `### ${chunk.deckTitle} — ${chunk.section}\n${chunk.text}`)
|
||||
.join("\n\n"),
|
||||
);
|
||||
}
|
||||
|
||||
lines.push("", "Reply with the JSON object only. No code fences, no commentary.");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
/** Sent as the final user turn when the session ends. */
|
||||
export const TEACH_SUMMARY_USER_INSTRUCTION = `The practice is finished. Write the recap now — JSON only, in the language of the session.`;
|
||||
|
||||
/** One repair attempt when the recap did not parse as JSON. */
|
||||
export const TEACH_SUMMARY_REPAIR_INSTRUCTION = `Your previous reply was not valid JSON matching the required shape. Reply with the JSON object only — no code fences, no commentary, same content and language.`;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Retry helper */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue