Commit Graph

374 Commits

Author SHA1 Message Date
bvandeusen 76c3dbc4b7 fix: stop TTS playback when PTT is activated
Pressing push-to-talk now immediately stops any ongoing TTS audio before
opening the microphone, preventing the assistant from hearing itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:43:15 -04:00
bvandeusen 1460863e82 feat: Kokoro voice blending — blend builder UI + weighted tensor synthesis
Add voice blend support to TTS pipeline and settings UI. Users can mix
2–5 Kokoro voices with per-voice weight sliders; the blended style tensor
replaces the single voice when enabled. Settings persist as JSON and auto-
load on synthesis when no explicit voice is supplied in the request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:10:22 -04:00
bvandeusen 98b3cdb593 feat: add weather condition icons to WeatherCard
Maps WMO condition strings to emoji icons for current conditions and
forecast days. No external dependencies — pure emoji lookup by condition text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:08:18 -04:00
bvandeusen c2d81e04b9 fix: update tests for briefing rewrite and raised content cap
- Remove test_slot_greeting — slot_greeting() was removed in the
  conversational briefing rewrite
- Update test_extract_item_truncates_content to use CONTENT_MAX_CHARS
  rather than a hardcoded 2000 (cap raised to 50000 for full articles)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 17:42:48 -04:00
bvandeusen e613485474 feat: full article fetching with trafilatura + html2text cleanup
- Add trafilatura + html2text to dependencies
- Replace custom HTMLStripper with html2text for RSS feed content
- Fetch full article text via httpx + trafilatura after each new item is stored;
  falls back to RSS-provided content if fetch/extraction fails
- Raise CONTENT_MAX_CHARS from 2000 to 50000 (TEXT column, no migration needed)
- Re-embed items with full article content once enrichment completes
- Startup backfill enriches existing items with short content (<1000 chars)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 16:33:27 -04:00
bvandeusen 0b05b03987 fix: strip HTML from RSS item content during ingestion
feedparser returns HTML in content/summary fields for many feeds.
Raw tags were being stored in the DB and passed to the LLM/embeddings.
Added a stdlib HTMLParser-based stripper in extract_item() — block elements
become newlines, script/style content is dropped, plain text passes through.
No new dependencies required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 16:15:49 -04:00
bvandeusen a773c11aa0 feat: RSS embeddings, semantic news in chat, article-to-chat, richer briefings
- Embed RSS items at fetch time (nomic-embed-text); backfill at startup
- Semantic news search injected into chat system prompt ("Recent News You've Seen")
  when items match query above 0.55 cosine threshold (independent of note RAG)
- "Discuss in chat" button on news cards — creates a seeded conversation with
  the article title + full content, navigates directly to the new chat
- Briefing compilation now passes 500-char article excerpts (not just headlines)
  to the LLM and uses 8192 num_ctx to accommodate the larger prompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 15:12:38 -04:00
bvandeusen dba41879ed feat: structured user profile with LLM-learned preferences
Replaces the freeform briefing-profile note with a DB-backed user_profiles
table. Users can edit job/industry/expertise/response preferences/interests/
work schedule via a new Settings → Profile tab. The LLM appends nightly
observations; at 14+ entries they are auto-consolidated into a learned_summary.
Profile context is injected into both briefing and chat system prompts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:17:30 -04:00
bvandeusen 9f3b9e45c6 feat: rewrite briefing pipeline to conversational prose
Replace two-pass structured LLM synthesis (## Your Day / ## The World
sections with bullets and formatted news cards) with a single
conversational pass. The new prompt instructs the model to write 3-5
flowing sentences covering weather, today's tasks/events, and 1-2 news
highlights — no markdown, no headers, no lists. Full news detail stays
in the right panel; weather detail stays in the weather card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 13:37:47 -04:00
bvandeusen 2a8c0cfa56 fix: set keep_alive to 2h on all Ollama requests
Prevents models from sitting in VRAM indefinitely. Applies to both
streaming chat calls and the non-streaming generate_completion path,
as well as the startup warm-up request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 10:21:59 -04:00
bvandeusen dd304bb556 feat: unify voice PTT across briefing and chat views
- ChatView: add PTT mic button in input bar (hold to speak → transcribe → send)
- BriefingView: restyle input bar to match ChatView (floating pill, circle send)
- BriefingView: move listen/mic controls into input bar, remove from header
- BriefingView: consistent icon-button style for speaker/mic matching ChatView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 21:58:20 -04:00
bvandeusen f146485df3 feat: hot-reload voice models without server restart
Voice enabled/STT model are now DB-backed (admin settings), not env
vars. Added reload_stt_model()/reload_tts_model() that clear singletons
under lock and re-trigger loading. POST /api/admin/voice/reload triggers
both in background tasks. Settings UI polls /api/voice/status every 2.5s
until models are ready, with spinner feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 20:52:07 -04:00
bvandeusen eaf70500b8 feat: move voice enable/model config to admin UI
Replace VOICE_ENABLED env var gate with DB-backed admin setting.

- services/voice_config.py: reads voice_enabled + voice_stt_model from
  admin user's settings row (falls back to env var defaults)
- routes/admin.py: GET/PUT /api/admin/voice for admin configuration
- routes/voice.py, services/stt.py, services/tts.py: read enabled/model
  from DB via voice_config instead of Config directly
- app.py: always schedule model loaders at startup; they self-gate on
  the DB setting so no conditional needed at the call site
- SettingsView.vue: Voice section in Admin → Config tab (enable toggle +
  STT model dropdown); user Voice tab now points to admin panel when disabled

No env var required to test — enable via Settings → Admin → Config → Voice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 20:22:18 -04:00
bvandeusen 6f84d90dff feat: voice S2S — faster-whisper STT, Kokoro TTS, PTT overlay
Implements full speech-to-speech pipeline (all 4 phases):

Backend (Phase 1):
- services/stt.py: lazy WhisperModel singleton, run_in_executor transcription
- services/tts.py: lazy KPipeline singleton, WAV synthesis at 24kHz/16-bit
- routes/voice.py: /api/voice/status, /voices, /transcribe, /synthesise
- config.py: VOICE_ENABLED, STT_BACKEND, STT_MODEL, TTS_BACKEND env vars
- app.py: load STT/TTS models at startup when VOICE_ENABLED=true
- llm.py: voice_mode + voice_speech_style params inject speak-naturally prefix
- generation_task.py: voice_mode passed through from chat route
- chat.py: "voice" conversation type allowed + excluded from retention cleanup
- pyproject.toml + Dockerfile: faster-whisper, kokoro, soundfile dependencies

Frontend (Phases 2–4):
- composables/useVoiceRecorder.ts: MediaRecorder PTT wrapper
- composables/useVoiceAudio.ts: AudioContext WAV playback wrapper
- BriefingView.vue: Listen button (TTS read-aloud), auto-TTS mode, mic PTT
- VoiceOverlay.vue: global floating PTT button; creates/reuses voice conv;
  full record→transcribe→stream→TTS flow; Space bar hold-to-talk via App.vue
- SettingsView.vue: Voice tab (status badge, speech style, voice/speed)
- App.vue: mounts VoiceOverlay; Space keydown/keyup fires voice:ptt-toggle
- api/client.ts: getVoiceStatus, getVoiceList, transcribeAudio, synthesiseSpeech

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 20:03:38 -04:00
bvandeusen 3581cc1582 docs: add voice S2S design spec
Covers STT (faster-whisper), TTS (Kokoro), per-user settings,
all new/modified files, audio format decisions, and 4-phase
implementation plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 18:17:58 -04:00
bvandeusen 3dd879640a chore(fable-mcp): bump version to 0.2.0
Reflects all changes since initial 0.1.0 release: RSS tools, task log
content/body fix, project and milestone status on create, and various
other fixes. Auto-bump hook will handle patch increments from here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 16:01:12 -04:00
bvandeusen 0cbeb6b7ac fix: honour status param on project and milestone creation
create_project and create_milestone hardcoded status="active" and
ignored any value passed by the MCP or API callers. Route, service,
and model construction now all thread the status field through.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 15:19:50 -04:00
bvandeusen 218f946e48 fix: fable_add_task_log sends content not body to match API
The MCP tool was sending {"body": ...} but the task logs API route
expects {"content": ...}, causing 400 errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 15:16:13 -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 024075329d feat: add add_rss_feed LLM tool so users can add feeds via chat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 13:50:13 -04:00
bvandeusen 697d99cc4d fix: pass temp_unit from API to WeatherCard so F/C label matches converted values 2026-03-28 13:21:30 -04:00
bvandeusen 164c55e845 fix: increase contrast on app version display in settings 2026-03-28 13:16:15 -04:00
bvandeusen c77c13684f fix: cast feed_id parameter to integer to resolve asyncpg AmbiguousParameterError 2026-03-28 12:56:33 -04:00
bvandeusen 7b95150101 fix: read temp_unit from briefing_config not a nonexistent temperature_unit setting 2026-03-28 12:49:36 -04:00
bvandeusen 96a07690a8 fix: use COALESCE(published_at, fetched_at) in news query — articles with no publish date were silently dropped 2026-03-28 12:32:19 -04:00
bvandeusen 6da4b098e3 fix: /api/briefing/weather returns card-format data for WeatherCard component 2026-03-28 12:30:11 -04:00
bvandeusen 7fdb2ee39d chore: ignore docs/superpowers, docs/plans, docs/specs — keep only root docs 2026-03-28 12:16:23 -04:00
bvandeusen 83cee46078 feat: auto-bump fable-mcp patch version on commit via Claude Code hook
- scripts/bump_fable_mcp_version.sh: increments patch in pyproject.toml and stages it
- scripts/pre_commit_fable_mcp.sh: PreToolUse Bash hook — fires before git commits,
  bumps version if fable-mcp files (other than pyproject.toml itself) are staged
- .claude/settings.json: registers the PreToolUse hook

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 02:14:20 -04:00
bvandeusen 534e0a3a34 v26.03.28.1 — News/briefing redesign, task enhancements, recurrence v26.03.28.1 2026-03-28 05:38:26 +00:00
bvandeusen 51d2fd9d0a fix: migration 0031 no-op — status column is TEXT not a PG enum 2026-03-28 01:17:00 -04:00
bvandeusen ac90548823 feat: task management enhancements (cancelled status, recurrence, timestamps)
- Add 'cancelled' status to TaskStatus type, StatusBadge, TaskCard,
  TaskEditorView, TaskViewerView, TasksListView
- Add RecurrenceEditor component (none / interval / calendar rules)
- TaskEditorView: wire RecurrenceEditor, show started_at/completed_at
  timestamps read-only, include recurrence_rule in save payload
- TaskViewerView: show recurrence summary, timestamps in meta row
- tasks.ts: statusFilter/priorityFilter as arrays, add recurrence_rule
  to updateTask and createTask payloads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:51:36 -04:00
bvandeusen 37e2420192 feat: add /news archive view with feed filter and pagination
- NewsView.vue: 90-day article archive, feed filter dropdown,
  load-more pagination, topic pills, thumbs up/down reactions
- Router: add /news route
- AppHeader: add News nav link (desktop + mobile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:51:24 -04:00
bvandeusen f81c38df84 feat: redesign BriefingView into 3-column layout (weather · chat · news)
- Left column: weather loaded independently via /api/briefing/weather
- Center column: chat messages with input bar (unchanged behavior)
- Right column: news panel loaded from /api/briefing/news (last 2 days)
- Auto-scroll to bottom on mount and after streaming
- Background refresh also refreshes news panel
- Responsive: stacks to single column on narrow screens
- Fix TaskCard.vue to include 'cancelled' in status records

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:38:04 -04:00
bvandeusen 0a6e57e698 feat: add NewsItem type and getNewsItems() API client function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:34:34 -04:00
bvandeusen 260103d533 feat: inject briefing article content for deep article Q&A
Add _build_briefing_article_context() helper to llm.py that reads
rss_item_ids from briefing message metadata and injects article content
into the system prompt. Pass conv_id through build_context() and
generation_task.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:31:43 -04:00
bvandeusen 35f57e0d3e feat: add GET /api/briefing/news unified news endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:29:59 -04:00
bvandeusen 57bf63e576 feat: extend RSS item retention from 14 to 90 days
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:29:32 -04:00
bvandeusen aa18f4f527 docs: add news briefing redesign spec and plan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:13:40 -04:00
bvandeusen 3391825550 docs: add news feed & briefing redesign spec
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 23:59:34 -04:00
bvandeusen 3d7c953627 feat: add recurrence_rule to LLM tools; list_tasks status accepts list + cancelled 2026-03-27 23:02:30 -04:00
bvandeusen 6557fef6a2 feat: support multi-value status and priority filters in list_notes 2026-03-27 23:01:24 -04:00
bvandeusen 0dc3dfa539 feat: add recurrence_rule validation and recurrence-preview endpoint in tasks routes 2026-03-27 22:54:11 -04:00
bvandeusen 3179b60eac feat: wire recurrence into create_note/update_note and add daily APScheduler job 2026-03-27 22:53:20 -04:00
bvandeusen a24257aeed feat: add recurrence service (validate, calculate_next_due, spawn_recurring_tasks) 2026-03-27 22:52:18 -04:00
bvandeusen c271f1b41f feat: add recurrence_rule and recurrence_next_spawn_at columns to notes 2026-03-27 22:49:59 -04:00
bvandeusen c92f4944cc feat: auto-set started_at/completed_at on task status transitions 2026-03-27 22:49:21 -04:00
bvandeusen 1125c8e107 feat: add started_at/completed_at columns to notes 2026-03-27 22:37:55 -04:00
bvandeusen 7888788d42 Merge pull request 'Release v26.03.27.2' (#15) from dev into main
Release v26.03.27.2
v26.03.27.2
2026-03-27 21:23:53 +00:00
bvandeusen 7a12cba4d5 feat: add 'cancelled' task status; fix 500 on invalid status/priority
TaskStatus enum was missing 'cancelled' — the LLM tried to use it and
hit TaskStatus("cancelled") raising ValueError → 500. Added the value,
a migration to extend the task_status Postgres enum, and proper 400
validation guards on both create and update task routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 09:18:16 -04:00
bvandeusen 78791175a1 Merge pull request 'Release v26.03.27.1' (#14) from dev into main
Release v26.03.27.1
v26.03.27.1
2026-03-27 04:12:27 +00:00