project-nomad/admin/inertia/components
Chris Sherwood ffa70a54bc feat(chat): confirm-on-switch + one-chat-model-at-a-time enforcement
Surfaces NOMAD's previously-silent model-stacking behavior and enforces a
"one chat model in VRAM at a time" invariant (the embedding model is
always exempt). Addresses Chris's NOMAD3 testing observation that
switching the dropdown in the chat header was invisibly slow on low-VRAM
hardware because the prior model was never unloaded — Ollama would
either evict it under memory pressure or load the new one on CPU after
the runner choked.

Three integration points all funnel through one new helper:

- **User changes the model dropdown** in an active chat session →
  confirm modal "Switch to {newModel}? Switching to {newModel} will
  start a new chat. Your current conversation stays available in the
  sidebar." On confirm, fire `keep_alive: 0` against the previous chat
  model, clear active session, set the new selection. Cancel snaps the
  visible dropdown back to the previous value (no popup state leaks
  into `selectedModel`).

- **User clicks a session in the sidebar** → no popup (system-initiated).
  Restore the session's stored model into the dropdown and fire
  `unloadChatModels(targetModel)` so anything that isn't the target
  gets the unload hint.

- **Chat page first mount** → page-load normalization. Anything stacked
  from a prior session gets the unload hint with the current selected
  model as the target-to-preserve. Guarded by a ref so it only fires
  once per page lifetime; gated on `selectedModel` being populated.

Backend surface is a single new helper and a single new route:

  `OllamaService.unloadAllChatModelsExcept(targetModel: string | null)`
  → queries `/api/ps`, filters out (a) the embedding model name
  (hardcoded `nomic-embed-text:v1.5` to avoid the RagService circular
  import) and (b) `targetModel`, fires `POST /api/generate` with empty
  prompt + `keep_alive: 0` in parallel against everything else.
  Returns the names that were hinted. Best-effort: network or Ollama
  errors are logged and swallowed so callers don't fail on housekeeping.

  `POST /api/ollama/unload-chat-models` → thin wrapper validating
  `{ targetModel?: string | null }`.

Why `keep_alive: 0` is safe against in-flight inference: per Ollama's
scheduler semantics, the hint sets the post-completion eviction timer
to zero — the runner is not terminated. If Session A is mid-response
on gemma when Session B fires the unload, gemma stays resident until
A's request completes, then evicts. The user-visible worst case is the
race where A's longer-running request re-extends the timer back to the
default and the unload is no-op'd; the next transition (or page reload)
gets another chance, and Ollama's own LRU catches up under memory
pressure regardless. Robust in-flight tracking deferred to a follow-up
if we see stale-state in the wild.

Base `rc`: v1.40.0 will inherit everything from rc.6 via the backmerge.
Frontend tests deferred to a follow-up PR; existing inertia tsconfig
errors are pre-existing and unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:16:00 -07:00
..
chat feat(chat): confirm-on-switch + one-chat-model-at-a-time enforcement 2026-05-20 10:16:00 -07:00
file-uploader feat(KnowledgeBase): support up to 5 files upload of 100mb each per req 2026-04-03 14:26:50 -07:00
inputs feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
layout feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
maps fix(Maps): render notes in marker popup when populated 2026-05-20 10:16:00 -07:00
markdoc feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
systeminfo feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
ActiveDownloads.tsx feat(downloads): rich progress, friendly names, cancel, and live status (#554) 2026-04-03 14:26:50 -07:00
ActiveEmbedJobs.tsx fix(KB): remove redundant Refresh button from Processing Queue 2026-05-20 10:16:00 -07:00
ActiveModelDownloads.tsx fix(AI): allow cancelling in-progress model downloads and ensure consistent progress UI (#701) 2026-04-21 14:26:28 -07:00
Alert.tsx fix(UI): manual import map for DynamicIcon to avoid huge bundle of Tabler icons 2026-04-03 14:26:50 -07:00
BouncingDots.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
BouncingLogo.tsx feat: switch all PNG images to WEBP (#575) 2026-04-03 14:26:50 -07:00
BuilderTagSelector.tsx fix(UI): switch to tabler icons only for consistency 2026-01-31 20:39:49 -08:00
CategoryCard.tsx feat: curated content system overhaul 2026-02-11 15:44:46 -08:00
CountryPickerModal.tsx fix(UI): Country Picker UX polish + auto-refresh stored files (#817) 2026-05-20 10:16:00 -07:00
CuratedCollectionCard.tsx feat: curated content system overhaul 2026-02-11 15:44:46 -08:00
DebugInfoModal.tsx fix(UI): minor styling fixes for Night Ops 2026-03-20 11:46:10 -07:00
DownloadURLModal.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
DynamicIcon.tsx fix(UI): manual import map for DynamicIcon to avoid huge bundle of Tabler icons 2026-04-03 14:26:50 -07:00
Footer.tsx feat(UI): add Debug Info modal for bug reporting 2026-03-20 11:46:10 -07:00
HorizontalBarChart.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
InfoTooltip.tsx fix(UI): switch to tabler icons only for consistency 2026-01-31 20:39:49 -08:00
InstallActivityFeed.tsx fix(UI): constrain install activity feed height with auto-scroll (#611) 2026-04-03 14:26:50 -07:00
KbGuardrailModal.tsx feat(KB): guardrail modal at 50GB / 10%-free thresholds (RFC #883 §7) 2026-05-20 10:16:00 -07:00
LoadingSpinner.tsx fix(ui): support proper size override of LoadingSpinner 2026-03-20 11:46:10 -07:00
MarkdocRenderer.tsx feat(docs): polish docs rendering with desert-themed components 2026-02-06 14:41:30 -08:00
ProgressBar.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
StorageProjectionBar.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
StyledButton.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
StyledModal.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
StyledSectionHeader.tsx feat: curated zim collections 2025-12-05 15:47:22 -08:00
StyledSidebar.tsx fix: cache docker list requests, aiAssistantName fetching, and ensure inertia used properly 2026-04-03 14:26:50 -07:00
StyledTable.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
ThemeToggle.tsx fix(UI): improve version display in Settings sidebar (#547) 2026-03-25 16:30:35 -07:00
TierSelectionModal.tsx fix(KB): guardrail bypass during estimate load + Transition sibling (PR #901 review) 2026-05-20 10:16:00 -07:00
UpdateServiceModal.tsx feat(UI): add Night Ops dark mode with theme toggle 2026-03-20 11:46:10 -07:00
WikipediaSelector.tsx fix(ui): support proper size override of LoadingSpinner 2026-03-20 11:46:10 -07:00