Commit Graph

17 Commits

Author SHA1 Message Date
bvandeusen 9a851de624 fix(llm): normalize Ollama model tags to lowercase
Ollama's /api/tags returns whatever casing was used at pull time
(e.g. 'gemma3:12B' if the user ran 'ollama pull gemma3:12B'), but
/api/chat rejects mixed-case tags with a 400. The two code paths
are inconsistent, which surfaces the capitalized tag in the model
dropdown and then silently kills every chat request against it.

Lowercase on read (get_installed_models), on settings write
(update_settings_route), and on ensure_model() input so a legacy
mixed-case user setting can't trigger a spurious re-pull at
startup. The dropdown and stored settings are now always in the
form Ollama will actually accept.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 18:02:52 -04:00
bvandeusen 0becc1439b fix(llm): correct context sizing, honor think requests, broaden delete
Three related fixes uncovered while benchmarking qwen3:14b against 8b:

- pick_num_ctx was only counting message content, missing the ~15K
  tokens of tool schemas. num_ctx=8192 was being selected while actual
  prompt_tokens hit 14K+, causing silent prompt truncation on every
  tool-using request. Now includes json.dumps(tools) in the estimate.
  KV cache priming in app.py and routes/settings.py also fetches tools
  so the primed num_ctx matches what real chat requests will use.

- _should_think's heuristic classifier was overriding explicit
  think=true requests from the frontend toggle and MCP, gating on
  message length and regex patterns. Now a pass-through — the caller
  is the source of truth. quick_capture hardcodes think=False since
  it's a fast classification path that was relying on the old gating.

- delete_note description only mentioned "note or task", so the model
  refused to call it for entries created by save_person / save_place /
  create_list. Description now explicitly lists all five note_types it
  handles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-12 15:32:52 -04:00
Bryan Van Deusen 3f3156db07 feat(ollama): configurable per-model keep_alive durations
Replace the hardcoded "2h" keep_alive everywhere with a helper that
returns OLLAMA_KEEP_ALIVE_MAIN (default 30m) for the interactive model
and OLLAMA_KEEP_ALIVE_BACKGROUND (default 10m) for the background
model. Lets the main model release VRAM during long idle periods
while keeping it warm enough for bursty chat use, and stops the
sporadic background model from camping VRAM it rarely needs.

Seven call sites updated to route through llm.keep_alive_for(model):
the streaming helpers, generate_completion, the two startup warmers,
the settings KV-cache primer, and the chat warmer endpoint.

Override via env vars: OLLAMA_KEEP_ALIVE_MAIN, OLLAMA_KEEP_ALIVE_BACKGROUND.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:13:32 -04:00
bvandeusen 8ec91ceea7 feat(settings): propagate user_timezone to briefing scheduler on save
When user_timezone is saved via PUT /api/settings, immediately call
update_user_schedule if briefing is enabled so the scheduler picks up
the new timezone without requiring a restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:35:00 -04:00
bvandeusen 94d21c4512 fix(settings): audit pass — model auto-pull on startup, background_model empty-string bug, base URL validation
- Startup now pulls Config.OLLAMA_MODEL (system default chat model) — previously only
  embedding and background models were pulled; the primary chat model was skipped
- _warm_user_models expanded to also pull user-configured default_model and
  background_model overrides that are missing from Ollama, rather than logging and
  skipping them; pulls run before warm/KV-cache priming
- Add background_model to _MODEL_KEYS in settings route so clearing the dropdown
  deletes the DB row instead of saving "", which caused Ollama failures in tag
  suggestions, title generation, project summaries, and RSS classification
- Add http/https scheme validation to PUT /api/admin/base-url matching the CalDAV
  route pattern; a bad value no longer silently breaks invite/password-reset links
- Update admin voice config description: "Reload models" button exists to avoid
  a server restart, so the old "restart required" text was misleading

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:08:14 -04:00
bvandeusen ef55bcb560 feat(llm): adaptive num_ctx tiers + fix KV cache priming num_ctx mismatch
Adds pick_num_ctx() which selects the smallest context window tier
(8192, 16384, 32768) that fits the current messages with 25% headroom,
capped at OLLAMA_NUM_CTX. Threads num_ctx through generation_task.py so
every chat request uses the computed tier rather than a fixed 16384.

Fixes a critical cache miss bug: KV cache priming in app.py and
settings.py was sending requests without num_ctx, so Ollama sized the
cache at its model default (different from the 16384 real requests used),
forcing a full model reload on the first real user message. Both priming
sites now call pick_num_ctx() and pass the matching value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 11:47:39 -04:00
bvandeusen c9065c4481 perf(settings): prime KV cache when user changes chat model
When a user saves a new default_model in Settings, fire a background
cache-prime request so the first message with the new model is fast
rather than paying the full cold-start prefill cost.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:51:17 -04:00
bvandeusen 00643c778e security: fix 10 vulnerabilities from security audit
- SSRF: block private/internal URLs in image cache fetch
- SSRF: block private/internal URLs in RSS feed fetch (scheme guard)
- SSRF: block private/internal URLs in CalDAV URL setting
- Auth: require login for GET /api/images/<id> (was unauthenticated)
- Auth: restrict Ollama model pull/delete to admin users only
- Info disclosure: remove email from /api/users/search response
- OAuth: skip email-based account linking when email_verified is false
- Config: raise hard error on default SECRET_KEY when SECURE_COOKIES=true
- Rate limit: document proxy header requirement; add startup warning
- XSS: remove src/alt from global DOMPurify ADD_ATTR allowlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 00:37:13 -04:00
bvandeusen 3d7be5888e Remove intent model entirely; quick-capture uses primary model
The separate intent model (OLLAMA_INTENT_MODEL / qwen2.5:7b) is removed
from every part of the system. All classification now uses the primary model.

Changes:
- config.py: remove OLLAMA_INTENT_MODEL
- intent.py: remove classify_intent() and all supporting infrastructure
  (_SYSTEM_PROMPT_TEMPLATE, _RESEARCH_PREFIX, _PRIOR_WORK_REFS); file now
  only contains the quick-capture classifier
- quick_capture.py: classify_capture_intent() now called with Config.OLLAMA_MODEL
- generation_task.py: remove intent_model_setting DB lookup and get_setting import;
  history summarization and research pipeline use the primary model directly
- research.py: remove intent_model parameter from run_research_pipeline() and
  _generate_sub_queries(); both use the model param throughout
- routes/settings.py: remove intent_model from model-key validation and response
- app.py: remove intent model pre-warming at startup
- SettingsView.vue: remove Intent Model selector and related refs/state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 18:41:49 -05:00
bvandeusen fb3a6d2445 Fix 'default' model selection breaking readiness indicator
When a user selected the 'Default' option in Settings, the dropdown
sent an empty string "" to the backend. The route saved it as a DB row,
which caused get_setting() to return "" instead of falling back to
Config defaults. The chat status endpoint then tried to match "" against
installed model names — always failing — resulting in model: "not_found"
and a permanently failing readiness indicator.

services/settings.py:
- Add delete_setting() helper: removes a setting row so get_setting()
  correctly falls back to its hardcoded default argument

routes/settings.py:
- Import delete_setting
- When default_model or intent_model are saved as empty string, delete
  the DB row instead of storing "" — cleanly restores Config fallback

routes/chat.py:
- chat_status_route: add explicit `or Config.OLLAMA_MODEL` guard for
  any existing "" rows written before this fix (migration safety net)
- send_message and summarize routes: same guard on model resolution
  so empty settings never cause silent generation failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 16:06:02 -05:00
bvandeusen 590682a5d2 Phase 22: SearXNG web research pipeline + settings layout overhaul
Research pipeline (research_topic tool):
- New service: services/research.py — sub-query generation, SearXNG
  search, URL fetch, deduplication, and LLM synthesis into a note
- 5 sub-queries × 3 pages = up to 15 sources, capped at 12 for synthesis
- Synthesis uses num_ctx=16384 + max_tokens=8192 for long-form output
- Prompt demands 2500+ words, 6+ topic-appropriate sections, detailed prose
- 429 retry with backoff; 1s inter-query sleep; raw_decode JSON parsing

search_web tool (new):
- Lightweight single-query SearXNG search, results returned inline in chat
- LLM answers conversationally in round 1; no note created
- web_search result type with external links in ToolCallCard

Infrastructure:
- llm.py: generate_completion accepts num_ctx override
- config.py: SEARXNG_URL + Config.searxng_enabled()
- docker-compose: OLLAMA_NUM_PARALLEL=2, commented SEARXNG_URL example
- intent.py: search_web and research_topic routing rules

Settings UI:
- 2-column grid layout (small sections pair up, complex span full width)
- Search Test section: live SearXNG query with result preview
- GET /api/settings/search?q= proxy endpoint
- Research button (magnifier) in ChatView input toolbar → popover modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:21:38 -05:00
bvandeusen f45b7cf9c5 Add model dropdowns to settings and set qwen2.5:1.5b as intent default
- Default OLLAMA_INTENT_MODEL to qwen2.5:1.5b in code instead of empty
- Add GET /api/settings/models endpoint returning installed models and defaults
- Validate intent_model against installed models on save (same as default_model)
- Replace intent model text input with a dropdown of installed models
- Add chat model dropdown to Assistant settings section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 22:17:45 -05:00
bvandeusen d7bc3f3222 Add CalDAV calendar integration, LLM-suggested tags, and settings refinements
- CalDAV integration: per-user calendar config, create/list/search events
  via caldav library, LLM tools for calendar operations from chat
- LLM-suggested tags: new tag_suggestions service prompts LLM with existing
  tags and note content to suggest 3-5 relevant tags; exposed via API
  endpoints (suggest-tags, append-tag); integrated into editor views
  (suggest button + clickable pills) and chat tool calls (pills in
  ToolCallCard with one-click apply)
- Settings/model UI refinements, generation task improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 22:40:20 -05:00
bvandeusen cbfdf5289e Add multi-user auth, background generation, and chat UX improvements
Phase 5: Multi-user authentication with session cookies, bcrypt passwords,
first-user-is-admin pattern, per-user data isolation, backup/restore,
Docker Swarm production stack with secrets and network isolation.

Phase 5.1: Chat UX improvements:
- Background generation architecture (GenerationBuffer + asyncio task)
  with SSE fan-out, reconnect support, and periodic DB flushes
- LLM-generated conversation titles (first exchange + every 10th message)
- Stop generation button with cancel_event and partial content preservation
- Relative timestamps in sidebar (5m ago, 3h ago, then dates)
- Empty chat auto-cleanup on navigation away
- Save-as-note uses LLM for title generation, tags notes with "chat"
- Summarize-as-note also tags with "chat"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 14:36:30 -05:00
bvandeusen db01106714 Backend efficiency & consistency pass
- Rewrite get_all_tags() with SQL unnest instead of loading all notes
- Consolidate convert_note_to_task/convert_task_to_note to single-session ops
- Add search_notes_for_context() with single OR-keyword query for build_context()
- Drop selectinload from list_conversations(), use correlated subquery for message_count
- Add set_settings_batch() for single-transaction multi-setting upserts
- Extract get_installed_models() shared helper into services/llm.py
- Delete services/tasks.py pass-through wrapper; routes/tasks.py imports from services.notes
- Add B-tree indexes on notes.title and conversations.updated_at (migration 0007)
- Add logging to services/notes.py, services/chat.py, services/settings.py
- Safe Conversation.to_dict() when messages relationship is not loaded

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 08:27:11 -05:00
bvandeusen fb18d2c41d Add note context visibility in chat and standardize UI design tokens
Improve chat context: build_context() now returns metadata about auto-found
notes, emitted as an SSE event so the frontend can display context pills
showing which notes influenced the response. Users can promote notes for
deeper context (+) or exclude irrelevant ones (x). A note picker lets users
manually attach notes. Multi-word search uses per-term AND matching, and
auto-search iterates keywords individually for broader OR-style coverage.

Standardize styling: introduce CSS design tokens (--radius-sm/md/lg/pill,
--color-success/warning/overlay, --focus-ring) and migrate all components
to use them. Fix header alignment to full-width, add active nav link state,
replace hardcoded colors with CSS variables, and normalize button padding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 06:49:12 -05:00
bvandeusen 38b1ac933e Add settings page, model management, and chat UX improvements
- Settings infrastructure: key-value settings table, GET/PUT API, Pinia store
- Configurable assistant name (default "Fable") in settings and LLM system prompt
- Model catalog with 18 models in 3 categories (General Purpose, Coding,
  Uncensored / Creative Writing) with download/select/remove functionality
- Move Ollama status indicator from chat views to global nav bar
- Chat bubble layout: user messages right-aligned, assistant left-aligned
- Floating dark input bar with auto-focus and circular send button
- Fix HTML entity rendering (&#39; apostrophe issue in marked/DOMPurify pipeline)
- Fix new chat button navigation (fetchConversation before router.push)
- Recent chats section on home page with "New Chat" button
- Update summary.md with Phase 4.5 changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 21:32:02 -05:00