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.
28 lines
1 KiB
TypeScript
28 lines
1 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect } from "react";
|
|
|
|
/**
|
|
* Keeps a chat-style composer above the on-screen keyboard. The visual
|
|
* viewport tells us how much of the window the keyboard covers; the screen
|
|
* reads it as bottom padding via `--chat-keyboard` (WebKit does not resize the
|
|
* layout viewport, so `bottom: 0` alone slides under the keyboard there).
|
|
*/
|
|
export function useKeyboardInset(): void {
|
|
useEffect(() => {
|
|
const viewport = window.visualViewport;
|
|
if (!viewport) return;
|
|
const apply = () => {
|
|
const inset = Math.max(0, window.innerHeight - viewport.height - viewport.offsetTop);
|
|
document.documentElement.style.setProperty("--chat-keyboard", `${Math.round(inset)}px`);
|
|
};
|
|
apply();
|
|
viewport.addEventListener("resize", apply);
|
|
viewport.addEventListener("scroll", apply);
|
|
return () => {
|
|
viewport.removeEventListener("resize", apply);
|
|
viewport.removeEventListener("scroll", apply);
|
|
document.documentElement.style.removeProperty("--chat-keyboard");
|
|
};
|
|
}, []);
|
|
}
|