Chat mode, phase 1: grounded streaming chat over your notes
Entry points (no sixth tab): Ask in the Library header (desktop button, phone icon), Ask about this deck in the deck menu, Ask a question on study/homework results (compact result travels as context), and a Chat row in the desktop sidebar. Phones get a full-screen route with back + New chat; the tab bar steps aside. - Client-side retrieval: chunk scoped decks (~800 tokens), BM25 against the question + recent turns, top chunks under ~7k tokens, each labelled with deck + section. Notes come from the deck's stored sourceText (already kept for Regenerate); decks without it fall back to generated content and the UI says so. - Stateless server: /api/chat validates against the CHAT_MODELS allowlist, trims the history to ~6k tokens, caps context at ~9k and streams token/thinking/done/error NDJSON. /api/chat/extract reduces attachments to text through the existing vision step. - UI: user bubbles + full-width assistant text, streaming with stop, auto-scroll that pauses with a jump-to-bottom button, safe markdown (React nodes, no HTML injection; KaTeX dynamically imported only when a message has math), Thinking row for reasoning, copy/regenerate/ edit-and-resend, model + context pickers (last model remembered), composer with visualViewport keyboard handling, safe-area, 16px textarea, Enter-to-send on desktop. - Usage-limit failures keep the draft and show the shared message with Settings/Ollama actions; stopping maps the reader abort to a clean stopped state; offline disables sending with a reason. - Settings: Keep notes text for chat toggle; deck menu: Delete notes text. RATE_LIMIT_CHAT + CHAT_MODELS in .env.example and README. - Tests: chat suite (63 checks, stub Ollama) wired into run-all. Full set: 12/12 suites, 487 checks. SW -> freezmo-v32. Also: tests/model-compare.mjs (diagnostic) benchmarks two models on the same notes (latency, repair rounds, activity types) so the gpt-oss vs gemma default can be decided on numbers; defaults unchanged.
This commit is contained in:
parent
6b6a6ea158
commit
756a6257a2
46 changed files with 3840 additions and 56 deletions
162
lib/i18n-dict.ts
162
lib/i18n-dict.ts
|
|
@ -898,6 +898,87 @@ const en: Record<string, string> = {
|
|||
"doc.readingSlides": "Reading the slides…",
|
||||
"doc.readingOpen": "Reading the OpenDocument file…",
|
||||
"doc.readingText": "Reading the text file…",
|
||||
|
||||
/* chat ------------------------------------------------------------ */
|
||||
"chat.nav": "Chat",
|
||||
"chat.title": "Chat",
|
||||
"chat.ask": "Ask",
|
||||
"chat.askAboutDeck": "Ask about this deck",
|
||||
"chat.askQuestion": "Ask a question",
|
||||
"chat.back": "Back",
|
||||
"chat.new": "New chat",
|
||||
"chat.placeholder": "Ask about your notes…",
|
||||
"chat.send": "Send",
|
||||
"chat.stop": "Stop",
|
||||
"chat.copy": "Copy",
|
||||
"chat.copied": "Copied",
|
||||
"chat.copyFailed": "Copy failed",
|
||||
"chat.edit": "Edit and resend",
|
||||
"chat.regenerate": "Regenerate",
|
||||
"chat.retry": "Retry",
|
||||
"chat.thinking": "Thinking…",
|
||||
"chat.thought": "Thinking",
|
||||
"chat.sources": "Sources",
|
||||
"chat.jumpToBottom": "Jump to the newest",
|
||||
"chat.offlineNote": "Offline — sending is paused",
|
||||
"chat.emptyTitle": "Ask your notes anything",
|
||||
"chat.emptyHint": "Answers come from your own decks. Pick what the chat may read with the context chip below.",
|
||||
"chat.oldDecksNote": "Older decks only have the generated study content. Re-add the notes for better answers.",
|
||||
"chat.fromLabel": "From",
|
||||
"chat.notesOffNote": "Chat is set to ignore notes text.",
|
||||
"chat.notesOffLink": "Turn it back on in Settings",
|
||||
"chat.attach": "Attach photos or documents",
|
||||
"chat.attachPhoto": "photo",
|
||||
"chat.attachDoc": "document",
|
||||
"chat.attachRemove": "Remove attachment",
|
||||
"chat.attachFailed": "That attachment could not be read.",
|
||||
"chat.attachNoText": "No text found in that file — try a photo instead.",
|
||||
"chat.attachEmpty": "Nothing readable was found in that attachment.",
|
||||
"chat.attachmentLabel": "Attachment",
|
||||
"chat.modelTitle": "Chat model",
|
||||
"chat.modelText": "Text",
|
||||
"chat.modelVision": "Vision",
|
||||
"chat.modelBoth": "Text + vision",
|
||||
"chat.modelAvailable": "available",
|
||||
"chat.modelUnavailable": "not found",
|
||||
"chat.modelNote": "Photos and documents are transcribed first, so any of these works.",
|
||||
"chat.scope.title": "What may chat read?",
|
||||
"chat.scope.all": "All notes",
|
||||
"chat.scope.allNote": "Every deck's notes can ground the answers.",
|
||||
"chat.scope.pick": "Selected decks",
|
||||
"chat.scope.pickNote": "Choose folders or single decks below.",
|
||||
"chat.scope.none": "No notes",
|
||||
"chat.scope.noneNote": "General chat — the model answers from general knowledge only.",
|
||||
"chat.scope.decks.one": "{n} deck",
|
||||
"chat.scope.decks.few": "{n} decks",
|
||||
"chat.scope.decks.many": "{n} decks",
|
||||
"chat.scope.deckCount.one": "{n} deck",
|
||||
"chat.scope.deckCount.few": "{n} decks",
|
||||
"chat.scope.deckCount.many": "{n} decks",
|
||||
"chat.scope.hasNotes": "notes text kept",
|
||||
"chat.scope.generatedOnly": "generated content only",
|
||||
"chat.scope.note": "Only what you pick here is sent as context — the API key never leaves this browser.",
|
||||
"chat.suggest.explain": "Explain the main idea of {deck}",
|
||||
"chat.suggest.quiz": "Quiz me on {deck}",
|
||||
"chat.suggest.summarize": "Summarize my notes",
|
||||
"chat.suggest.quizAll": "Quiz me on my decks",
|
||||
"chat.suggest.studyFirst": "What should I study first?",
|
||||
"chat.suggest.general": "Help me understand a topic",
|
||||
"chat.suggest.explainTopic": "Explain a topic step by step",
|
||||
"chat.seedResults": "Study results",
|
||||
"chat.seedAssignment": "Assignment",
|
||||
"chat.seedAnswer": "Answer",
|
||||
"chat.seedAttempt": "My attempt",
|
||||
"chat.seedVerdict": "Verdict",
|
||||
"chat.seedToFix": "To fix",
|
||||
"chat.seedCorrected": "Corrected version",
|
||||
"chat.seedSteps": "Steps",
|
||||
"chat.notesDeleted": "Notes text deleted",
|
||||
"chat.deleteNotes": "Delete notes text",
|
||||
"settings.chatNotes": "Keep notes text for chat",
|
||||
"settings.chatNotesNote": "Chat answers from the notes you added (stored with each deck). Turn off to use only the generated study content.",
|
||||
"server.chatModelNotAllowed": "That chat model is not on the server's allowlist.",
|
||||
"server.chatNeedsQuestion": "The conversation must end with a user message.",
|
||||
};
|
||||
|
||||
const cs: Record<string, string> = {
|
||||
|
|
@ -1737,6 +1818,87 @@ const cs: Record<string, string> = {
|
|||
"doc.readingSlides": "Čtu snímky…",
|
||||
"doc.readingOpen": "Čtu soubor OpenDocument…",
|
||||
"doc.readingText": "Čtu textový soubor…",
|
||||
|
||||
/* chat ------------------------------------------------------------ */
|
||||
"chat.nav": "Chat",
|
||||
"chat.title": "Chat",
|
||||
"chat.ask": "Zeptat se",
|
||||
"chat.askAboutDeck": "Zeptat se na tuto sadu",
|
||||
"chat.askQuestion": "Zeptat se na něco",
|
||||
"chat.back": "Zpět",
|
||||
"chat.new": "Nový chat",
|
||||
"chat.placeholder": "Zeptej se na svoje poznámky…",
|
||||
"chat.send": "Odeslat",
|
||||
"chat.stop": "Zastavit",
|
||||
"chat.copy": "Kopírovat",
|
||||
"chat.copied": "Zkopírováno",
|
||||
"chat.copyFailed": "Kopírování se nepovedlo",
|
||||
"chat.edit": "Upravit a poslat znovu",
|
||||
"chat.regenerate": "Vygenerovat znovu",
|
||||
"chat.retry": "Zkusit znovu",
|
||||
"chat.thinking": "Přemýšlím…",
|
||||
"chat.thought": "Myšlenkový postup",
|
||||
"chat.sources": "Zdroje",
|
||||
"chat.jumpToBottom": "Skočit na nejnovější",
|
||||
"chat.offlineNote": "Offline — posílání je pozastavené",
|
||||
"chat.emptyTitle": "Zeptej se svých poznámek na cokoli",
|
||||
"chat.emptyHint": "Odpovědi vycházejí z tvých sad. Pomocí chipu s kontextem níže vyber, co smí chat číst.",
|
||||
"chat.oldDecksNote": "Starší sady mají jen vygenerovaný obsah. Přidej znovu poznámky a odpovědi budou lepší.",
|
||||
"chat.fromLabel": "Z",
|
||||
"chat.notesOffNote": "Chat má vypnuté používání textu poznámek.",
|
||||
"chat.notesOffLink": "Zapnout v nastavení",
|
||||
"chat.attach": "Přiložit fotky nebo dokumenty",
|
||||
"chat.attachPhoto": "fotka",
|
||||
"chat.attachDoc": "dokument",
|
||||
"chat.attachRemove": "Odebrat přílohu",
|
||||
"chat.attachFailed": "Tuto přílohu se nepovedlo přečíst.",
|
||||
"chat.attachNoText": "V souboru nebyl žádný text — zkus místo toho fotku.",
|
||||
"chat.attachEmpty": "V příloze nebylo nic čitelného.",
|
||||
"chat.attachmentLabel": "Příloha",
|
||||
"chat.modelTitle": "Model chatu",
|
||||
"chat.modelText": "Text",
|
||||
"chat.modelVision": "Obrázky",
|
||||
"chat.modelBoth": "Text + obrázky",
|
||||
"chat.modelAvailable": "dostupný",
|
||||
"chat.modelUnavailable": "nenalezen",
|
||||
"chat.modelNote": "Fotky a dokumenty se nejdřív přepíšou na text, takže funguje kterýkoli z nich.",
|
||||
"chat.scope.title": "Co smí chat číst?",
|
||||
"chat.scope.all": "Všechny poznámky",
|
||||
"chat.scope.allNote": "Odpovědi mohou vycházet z poznámek všech sad.",
|
||||
"chat.scope.pick": "Vybrané sady",
|
||||
"chat.scope.pickNote": "Níže vyber složky nebo jednotlivé sady.",
|
||||
"chat.scope.none": "Bez poznámek",
|
||||
"chat.scope.noneNote": "Obecný chat — model odpovídá jen z obecných znalostí.",
|
||||
"chat.scope.decks.one": "{n} sada",
|
||||
"chat.scope.decks.few": "{n} sady",
|
||||
"chat.scope.decks.many": "{n} sad",
|
||||
"chat.scope.deckCount.one": "{n} sada",
|
||||
"chat.scope.deckCount.few": "{n} sady",
|
||||
"chat.scope.deckCount.many": "{n} sad",
|
||||
"chat.scope.hasNotes": "text poznámek uložen",
|
||||
"chat.scope.generatedOnly": "jen vygenerovaný obsah",
|
||||
"chat.scope.note": "Jako kontext se posílá jen to, co tady vybereš — API klíč nikdy neopustí tento prohlížeč.",
|
||||
"chat.suggest.explain": "Vysvětli hlavní myšlenku sady {deck}",
|
||||
"chat.suggest.quiz": "Vyzkoušej mě ze sady {deck}",
|
||||
"chat.suggest.summarize": "Shrň moje poznámky",
|
||||
"chat.suggest.quizAll": "Vyzkoušej mě ze všech sad",
|
||||
"chat.suggest.studyFirst": "Co mám studovat jako první?",
|
||||
"chat.suggest.general": "Pomoz mi pochopit nějaké téma",
|
||||
"chat.suggest.explainTopic": "Vysvětli téma krok za krokem",
|
||||
"chat.seedResults": "Výsledky studia",
|
||||
"chat.seedAssignment": "Zadání",
|
||||
"chat.seedAnswer": "Odpověď",
|
||||
"chat.seedAttempt": "Můj postup",
|
||||
"chat.seedVerdict": "Hodnocení",
|
||||
"chat.seedToFix": "Co opravit",
|
||||
"chat.seedCorrected": "Opravená verze",
|
||||
"chat.seedSteps": "Postup",
|
||||
"chat.notesDeleted": "Text poznámek smazán",
|
||||
"chat.deleteNotes": "Smazat text poznámek",
|
||||
"settings.chatNotes": "Uchovávat text poznámek pro chat",
|
||||
"settings.chatNotesNote": "Chat odpovídá z poznámek, které jsi přidal (jsou uložené u každé sady). Vypnutím bude používat jen vygenerovaný obsah.",
|
||||
"server.chatModelNotAllowed": "Tento model chatu není v povoleném seznamu serveru.",
|
||||
"server.chatNeedsQuestion": "Konverzace musí končit zprávou uživatele.",
|
||||
};
|
||||
|
||||
/** Used by tests/tools that want the raw tables. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue