Commit Graph

7 Commits

Author SHA1 Message Date
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 e119331645 Phase 21: Intent-first pipeline, visible ack, KV-stable system prompt
Pipeline changes (generation_task.py, intent.py):
- Remove optimistic streaming queue/race (_drain_queue deleted)
- Remove _generate_acknowledgment — ack now embedded in intent JSON
- Round 0: await intent (~400ms), stream ack immediately as TTFT,
  then execute tool sequentially; chat-only streams directly
- IntentResult.ack: one-sentence acknowledgment, intent max_tokens 200→350
- _parse_intent extracts and trims ack field

KV cache stability (llm.py, generation_buffer.py, generation_task.py):
- build_context: replace cached_note_ids with include_note_ids
- Auto-found notes populate context_meta["auto_notes"] for sidebar but
  are NOT injected into system prompt (--- Related Notes --- removed)
- Explicitly included notes injected as --- Included Notes ---
- _conv_note_cache dict + get/set/clear functions removed from generation_buffer.py
- All clear_conv_note_cache() calls removed

Cold model retry (llm.py):
- generate_completion (used by classify_intent) retries on HTTP 500:
  3 attempts with 3s/6s delays — prevents intent failure during cold load

API + frontend (routes/chat.py, stores/chat.ts, views/ChatView.vue, components/ChatPanel.vue):
- exclude_note_ids → include_note_ids throughout
- ChatView sidebar: Suggested (auto-found, + to include) + In Context (× to remove)
- ChatPanel: remove exclude button from context pills; no IDs passed to sendMessage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 22:34:54 -05:00
bvandeusen 32e4ee12f2 Add persistent context sidebar, note title fix, and expanded tool suite
Context sidebar + note title:
- ChatView: replace ephemeral context pills with a persistent right-panel sidebar;
  auto-found notes accumulate across turns; attached note shows with pin icon;
  × button excludes a note from future auto-search; hidden on mobile
- routes/chat.py: batch-fetch note titles via get_notes_by_ids() and inject
  context_note_title into each message dict at conversation load time
- notes.py: add get_notes_by_ids() batch fetch helper
- types/chat.ts: add context_note_title field to Message interface
- stores/chat.ts: sendMessage accepts optional 5th arg contextNoteTitle,
  included in optimistic user message
- ChatMessage.vue: context badge shows note title instead of 'Note #N'

Expanded LLM tool suite (all with intent router rules + ToolCallCard display):
- delete_note / delete_task: permanent delete with user confirmation (write tool),
  type-safe (refuse to delete wrong type), clears note context cache on success
- get_note: fetch full note body by query (search_notes returns only 200-char preview)
- list_notes: browse notes by recency/keyword/tags with limit; notes only
- update_note: add tags + tag_mode (replace/add/remove) parameters
- search_notes: add optional type filter ("note" | "task")
- search_todos (CalDAV): keyword-filter todos, companion to list_todos
- caldav.py: add search_todos() built on top of list_todos()
- generation_task.py: register new tools in _WRITE_TOOLS, _TOOL_LABELS, _TOOL_ACTIONS
- llm.py: update available actions list and guidance in system prompt
- intent.py: routing rules for all new tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 14:40:34 -05:00
bvandeusen 92bf2768b6 Reduce perceived latency: move context build into task, title fire-and-forget, think:False on aux calls
- build_context() moved from route handler into run_generation() background task.
  The 202 response now returns immediately; client connects to SSE before
  note search / URL fetch begins, so 'Building context...' status is visible.
- _generate_title() runs in a fire-and-forget asyncio.create_task() after the
  'done' SSE event fires. Users see their response complete 2–5s sooner on new
  conversations; title appears later in the sidebar without blocking the stream.
- generate_completion() now sets think:False and accepts a max_tokens limit.
  Intent classifier passes max_tokens=200 (JSON only), title generator passes
  max_tokens=30 (short title), eliminating qwen3 thinking-mode overhead on these
  auxiliary calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 18:50:37 -05:00
bvandeusen 4df5ec2d65 Add update_task fields, list_tasks, and update_todo tools
- update_note: extend with status/priority/due_date fields so task attributes
  can be changed via chat (mark done, set priority, move due date). body is now
  optional — task field updates work without touching content.
- list_tasks: new core tool with status/priority/due_before/due_after/limit
  filters backed by list_notes(is_task=True). Enables queries like
  "overdue tasks", "high priority tasks", "what's in progress".
- update_todo: new CalDAV tool to modify VTODO summary, due date, description,
  and priority — follows update_event pattern (modify component, rebuild ical,
  save). Completes the CalDAV todo CRUD suite.
- tools.py: add update_todo import + execute case (type: todo_updated)
- llm.py: add list_tasks and update_todo to available actions + guidance
- intent.py: routing rules for mark-done/priority/due-date → update_note,
  overdue/in-progress/high-priority queries → list_tasks, CalDAV todo updates
  → update_todo
- ToolCallCard.vue: tasks list block (linked titles + due + priority badges),
  todo_updated label, tool-task-priority CSS classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 23:22:02 -05:00
bvandeusen 70cba72a80 Phase 10: CalDAV full lifecycle, update_note, dashboard inline streaming, keyboard shortcuts
Backend:
- caldav.py: Full event lifecycle — update_event, delete_event; VTODO suite —
  create_todo, list_todos, complete_todo, delete_todo; list_calendars; timezone
  support via ZoneInfo; reminders via VALARM; attendees; multi-calendar search
  (_get_all_calendars scans all calendars when no specific one is configured)
- tools.py: New update_note tool (find by title + replace/append modes),
  7 new CalDAV tool definitions, corresponding execute_tool cases
- llm.py: Update system prompt — add update_note guidance, full CalDAV action list
- intent.py: Confidence scoring (high/medium/low) + should_execute property;
  conversation history support for anaphora resolution; routing rules for
  update/delete events, todos, update_note vs create_note disambiguation,
  time-period → list_events (not search_events), reminder_minutes conversion
- generation_task.py: Parallel fetch of tools + intent_model setting; dedicated
  intent model (OLLAMA_INTENT_MODEL env var or per-user intent_model setting)
- config.py: Add OLLAMA_INTENT_MODEL env var

Frontend:
- HomeView.vue: Inline streaming response (no navigation); quick action chips;
  isConversational computed — prominent "Continue this conversation" CTA when
  no tool calls; auto-focus chat input on mount via chatInputRef
- DashboardChatInput.vue: defineExpose({ focus }) for external focus control
- ChatView.vue: Escape key handler — close picker → close sidebar → clear
  textarea → navigate home; onUnmounted cleanup
- App.vue: Global ? key shortcut toggles keyboard shortcuts overlay; shared
  state via useShortcuts composable; Transition animation
- AppHeader.vue: ? button for shortcuts overlay discoverability
- useShortcuts.ts (new): Shared showShortcuts ref + open/close/toggle helpers
- ToolCallCard.vue: note_updated, event_updated, event_deleted, calendars,
  todo, todos, todo_completed, todo_deleted label cases + render blocks
- SettingsView.vue: Intent model field + caldav_timezone setting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-17 22:04:41 -05:00
bvandeusen 75560dee4e Switch default model to qwen3 and add intent routing for reliable tool calling
Mistral didn't reliably use Ollama's structured tool calling API — it wrote
tool calls as JSON text instead of invoking them. This adds an intent routing
layer that classifies user intent via a fast non-streaming LLM call before
streaming, executing detected tools directly and bypassing native tool calling.

- Change default OLLAMA_MODEL from mistral to qwen3
- Add intent.py: classify_intent() with JSON parsing and fallback regex
- Integrate intent routing into generation_task.py round 0
- Add all-day event support (iCalendar DATE values) to CalDAV service
- Add recurring event support (RRULE) to CalDAV service and tool definition
- Improve create_event tool description for descriptive titles
- Enhance system prompt with structured tool usage guidance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:24:01 -05:00