Skip to content

feat(frontend): API client, KB UI, live views, chat routing, history panel#43

Closed
spencerwozniak wants to merge 8 commits into
zeapsu:mainfrom
spencerwozniak:split/03-frontend
Closed

feat(frontend): API client, KB UI, live views, chat routing, history panel#43
spencerwozniak wants to merge 8 commits into
zeapsu:mainfrom
spencerwozniak:split/03-frontend

Conversation

@spencerwozniak

@spencerwozniak spencerwozniak commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Split out of #37 — frontend wiring that consumes the backend endpoints. Includes the backend commits as dependencies so this PR is self-mergeable against main; once #42 (backend-only) merges first, those commits will drop out of this diff on rebase.

  • Sage API client (frontend/src/lib/sage-api.ts) — typed helpers for knowledge, generation, audio, history; resolveAudioUrl() joins relative audio paths against the active API base for both Next.js dev and Tauri sidecar.
  • Knowledge base UIUploadModal (paste / drag-drop / file-picker for .txt|.md|.markdown|.rst|.csv|.json|.log, backend dedupes by content hash) and KnowledgeBaseWidget (two-pane browser with chunk count / hash / linked tomes / preview / per-row delete).
  • Live viewsFlashcardsView, QuizView, AudioView, ReportView replace the SAMPLE_* placeholders and POST to /api/generate/*. Shared loading / error+retry / content / sources-footer shape. AudioPlayerWidget switches between <audio> element (when MP3 available) and SpeechSynthesis fallback.
  • Real chat round-trip + command routingChatWidget POSTs to /api/chat, tracks sessionId, surfaces errors inline. detectView() lifted to page.tsx and shared with ChatWidget so verbs ("make flashcards on attention", etc.) route from inside the chat view. CommandBar gets a circular KB button.
  • History panel — replaces SAMPLE_HISTORY with GET /api/chat/sessions; client-side search, Today/Yesterday/Earlier-this-week/Older bucketing, stub-session filtering.

Test plan

  • Upload a doc, see it in the KB browser, delete it
  • "make flashcards on X" from the command bar AND from inside the chat view both route to FlashcardsView with KB-grounded content
  • Quiz / Report views render and show grounding sources
  • Audio playback works with and without OPENAI_API_KEY (MP3 mode vs. browser TTS fallback)
  • History panel shows sessions with correct titles, tome names, and timestamp buckets
  • Chat sessionId persists across turns

spencerwozniak and others added 8 commits May 22, 2026 15:36
Adds POST /api/generate/{flashcards,quiz,report} on a new router. Each
endpoint pulls excerpts from the knowledge base (semantic search when a
topic is provided, random sample otherwise — optionally scoped to a
tome), prompts the configured LLM with response_format=json_object on
OpenAI-compatible providers, and validates the parsed payload before
responding. An empty KB returns 400 with a hint to upload documents.

Also exposes an llm_text(...) helper used by the audio endpoint for
plain-text completions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
POST /api/generate/audio retrieves KB context (or randomly samples it),
prompts the configured provider for a spoken-style narration script,
and segments it into sentence-level chunks with word-rate-estimated
timings for transcript scroll-lock.

When OPENAI_API_KEY is configured, the script is also synthesized to
MP3 via OpenAI's tts-1 voice (default: alloy) and cached to disk; the
response includes audio_url. Without a key, audio_url is null and the
frontend falls back to the browser's SpeechSynthesis voice.

GET /api/audio/{audio_id}.mp3 streams the cached file with a regex
guard against directory traversal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Adds KnowledgeStore.list_sessions(): one SQL pass joining sessions,
  messages, and tomes that returns derived title (first user message),
  tome name, message count, and last activity timestamp. Ordered by
  most-recent activity.
- Adds GET /api/chat/sessions?limit=<n> as a thin wrapper for the
  History panel.
- Extends the chat SYSTEM_PROMPT with an "App capabilities you should
  know about" section so the model can describe its own routed
  features (quiz / flashcards / audio / report / history / tomes /
  knowledge base / upload) accurately when the user asks "what can
  you do?", and skips replying when the frontend has already routed
  away.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds typed helpers covering every new backend surface:

- Knowledge base: listDocuments, getDocument, deleteDocument plus
  DocumentSummary / DocumentDetail.
- Generation: generateFlashcards, generateQuiz, generateReport,
  generateAudio with shared GenerateOpts and GeneratedSource.
- Audio: AudioGeneration / AudioSegment plus resolveAudioUrl() that
  joins a relative audio path against the active API base (so it
  works from both the Next.js dev server and the Tauri sidecar).
- History: listChatSessions and ChatSessionSummary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two new components reachable from the round button next to the
command-bar pill:

- UploadModal: paste / drag-drop / file-picker ingest of text-like
  files (.txt, .md, .markdown, .rst, .csv, .json, .log). Backend
  dedupes by content hash so re-uploads are safe.
- KnowledgeBaseWidget: two-pane list+detail browser over every
  ingested document. Lists titles with doc_type/source/timestamp,
  shows chunk count, content hash, linked tomes, and a 200-char
  preview. Per-row delete. An "Add document" button in the header
  opens UploadModal and refreshes the list on close.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the hard-coded SAMPLE_* placeholders that the original
FlashcardWidget / QuizWidget / AudioPlayerWidget / ReportViewWidget
rendered with real, KB-grounded views that POST to the new
/api/generate/* endpoints.

Each view shares the same shape: loading card, error card with retry,
content card, and a "Grounded in: ..." source footer listing the
distinct source documents.

AudioPlayerWidget gains two new props — audioUrl and script — and
switches between two playback modes:

- <audio> element when the backend synthesized an MP3 (OpenAI TTS).
  Timeupdate events drive currentTime; a Download link appears.
- SpeechSynthesis fallback when no audio_url. Seek bar restarts
  speech from the nearest segment; a "Browser TTS" badge appears
  in place of Download.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ChatWidget now POSTs to /api/chat via sendChat(), tracks sessionId
  across turns, surfaces backend errors inline as assistant-style
  messages, and accepts an initialQuery prop so the first command-bar
  prompt seeds the conversation.
- The verb router used to live only in the command bar. detectView()
  is now lifted to page.tsx and shared with ChatWidget through an
  onCommand callback, so typing "make flashcards on attention" from
  inside the chat view routes correctly instead of being answered as
  prose. Supported verbs: quiz / test / flashcards / audio /
  podcast / listen / report / study guide / summary / history /
  tome / collection / library / knowledge / kb / docs.
- CommandBar gets a circular knowledge-base button (icon: library_add)
  to the right of the pill that opens the KB browser; the model badge
  is now driven by getRuntimeConfig() instead of being hard-coded.
- page.tsx threads generationPrompt through the routed views so the
  user's full command text becomes the topic for /api/generate/*.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the SAMPLE_HISTORY array with a fetch against
GET /api/chat/sessions. Filters out stub sessions (no user message
or zero messages), derives a row title from the first user message
(first line, 90 char cap), and groups rows into Today / Yesterday /
Earlier this week / Older buckets using a shared formatTimestamp
helper that handles both `datetime('now')` and ISO timestamp formats.

Search input filters by title or tome name client-side. Loading,
error, and empty states are inline. The filter pills (chat / quiz /
flashcard / etc.) are removed for now — only chat rows exist until
the other generations gain server-side persistence.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@zeapsu

zeapsu commented May 25, 2026

Copy link
Copy Markdown
Owner

Thanks for breaking this out. There is a lot of useful UI/API work here, especially the API client and the feature views for KB, quiz, flashcards, report, audio, and history.

I reviewed this in an isolated checkout.

Checks:

  • backend pytest with isolated HOME: passed
    • 62 passed
  • frontend install: passed
  • frontend build: passed
  • TypeScript check: passed

I would hold this before merge for a few runtime/product issues.

Main blockers:

  1. Existing DB migration is not handled

Running against an older Sage DB can hit:

sqlite3.OperationalError: no such column: content_hash

Fresh DBs pass, but existing user DBs need a schema-hardening/migration path before this lands. I’d suggest checking existing columns with PRAGMA table_info(documents), adding content_hash when missing, tolerating nulls/backfilling where possible, then creating indexes after the column exists.

  1. Command-bar chat submissions can be dropped while Chat is already mounted

page.tsx updates chatQuery, but ChatWidget only auto-sends initialQuery once. So a later top-bar prompt while Chat is already open may update state without sending.

Suggested fixes:

  • track the last sent initial query and send when it changes, or
  • remount ChatWidget with a key based on the submitted query
  1. Tome routing currently exposes sample data as real UI

The PR routes tome, collection, and library commands to TomeSelector, but TomeSelector still uses sample data and does not call the real Tome API. Since Tome selection/home is core UX, I’d either wire this to real data or avoid routing users into mock Tome state.

Other follow-ups:

  • make keyword routing less broad so normal questions like “history of transformers” do not unexpectedly open the History panel
  • wire HistoryPanel.onSelect or make entries visibly read-only
  • add confirmation or undo for document deletion
  • add upload size guards before reading full files into memory
  • consider retry/repair for malformed JSON responses from local models

One product-direction note: Sage is moving toward a calm default Tome Home surface with capability chips, while the expanded dashboard remains secondary. A lot of this PR is still useful, but I think we should adapt the entry points into that Tome Home flow rather than relying on broad keyword hijacking from the command bar.

Recommended path:

  • keep/cherry-pick the valuable pieces like sage-api.ts, Upload Modal, KB widget, feature views, and History panel
  • fix existing-user DB migration first
  • rework routing around Tome Home and explicit capability actions

Great Sage appreciates how much implementation is here. I just want to make sure it lands in a way that is safe for existing local data and aligned with the calmer Tome Home direction.

@zeapsu

zeapsu commented May 25, 2026

Copy link
Copy Markdown
Owner

Heads up: we’re going to implement the redesigned Tome Home on our Tome Dashboard/Home branch, and that work will touch frontend/src/app/page.tsx. It should be mostly product/surface-level work separate from this PR’s backend/API-client/live-view wiring, but I wanted to flag the overlap early so we can avoid unnecessary merge-conflict pain. Great Sage housekeeping ✨

@zeapsu

zeapsu commented May 27, 2026

Copy link
Copy Markdown
Owner

Thanks for splitting out the frontend integration work. PR #46 has now been merged into main as 53b83e0 and cherry-picked/integrated the API client, KB UI, live views, chat routing, and history-panel work from this PR, with the Tome Home reconciliation and extra polish/tests on top. I'm closing this as superseded by #46 so the open PR list reflects the current mainline state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants