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>
This commit is contained in:
+40
-13
@@ -12,7 +12,7 @@
|
||||
> Include file-level details in the commit body when the change is non-trivial.
|
||||
|
||||
## Last Updated
|
||||
2026-02-16 — Phase 9: Switch to qwen3, intent routing for reliable tool calling, all-day/recurring events
|
||||
2026-02-17 — Phase 10: CalDAV full lifecycle, update_note tool, dashboard inline streaming, keyboard shortcuts, intent router upgrades
|
||||
|
||||
## Project Overview
|
||||
Fabled Assistant is a self-hosted note-taking and task-tracking application with
|
||||
@@ -265,9 +265,9 @@ fabledassistant/
|
||||
│ │ ├── generation_buffer.py # In-memory SSE event buffer with cancel_event, reconnect support, auto-cleanup; supports chat (int keys) and assist (string keys)
|
||||
│ │ ├── generation_task.py # Background asyncio tasks: run_generation (chat, DB flush, titles, intent routing + tool loop) + run_assist_generation (lightweight, no DB)
|
||||
│ │ ├── intent.py # Intent routing: classify_intent() makes fast non-streaming LLM call to detect tool intent before streaming
|
||||
│ │ ├── tools.py # LLM tool definitions (create_task, create_note, search_notes, CalDAV events with all-day/recurrence) + execute_tool dispatcher
|
||||
│ │ ├── tools.py # LLM tool definitions (create_task, create_note, update_note, search_notes, full CalDAV suite) + execute_tool dispatcher
|
||||
│ │ ├── tag_suggestions.py # LLM-powered tag suggestions: suggest_tags() builds prompt with existing tags, calls generate_completion, parses JSON response
|
||||
│ │ ├── caldav.py # CalDAV integration: create/list/search calendar events via caldav library, all-day + recurring event support (per-user config from settings)
|
||||
│ │ ├── caldav.py # CalDAV integration: full event lifecycle (create/list/search/update/delete), todos (create/list/complete/delete), list_calendars, timezone (ZoneInfo), reminders (VALARM), attendees, multi-calendar search
|
||||
│ │ ├── settings.py # Settings CRUD with user_id isolation: get_setting, set_setting, set_settings_batch, get_all_settings
|
||||
│ │ ├── logging.py # App logging: log_audit, log_usage, log_error, get_logs, get_log_stats, delete_old_logs, start_log_retention_loop
|
||||
│ │ ├── email.py # SMTP email service: get_smtp_config, is_smtp_configured, send_email, send_test_email
|
||||
@@ -289,6 +289,7 @@ fabledassistant/
|
||||
│ │ └── client.ts # ApiError class, apiGet/apiPost/apiPut/apiPatch/apiDelete + apiSSEStream (fetch+ReadableStream), apiStreamPost (legacy), auto 401→login redirect
|
||||
│ ├── composables/
|
||||
│ │ ├── useTheme.ts # Theme toggle, localStorage, prefers-color-scheme
|
||||
│ │ ├── useShortcuts.ts # Shared showShortcuts ref + open/close/toggle helpers (used by App.vue + AppHeader.vue)
|
||||
│ │ ├── useAutocomplete.ts # Legacy textarea autocomplete (replaced by Tiptap suggestion extensions)
|
||||
│ │ └── useAssist.ts # AI Assist composable: section parsing, target selection, two-step POST+SSE streaming (apiPost → apiSSEStream), accept/reject; watches body ref for auto-sync
|
||||
│ ├── stores/
|
||||
@@ -522,6 +523,19 @@ When adding a new migration, follow these conventions:
|
||||
- `due_before`/`due_after` query params on `/api/tasks` for date-range filtering
|
||||
- Recent chats and recently edited notes sections
|
||||
- All task sections hidden when empty; marking done removes from all lists
|
||||
- **Inline chat widget:** Submitting a message streams the response inline on the dashboard — no
|
||||
navigation to `/chat`. Streaming tool calls shown live; final response captured from store after SSE.
|
||||
- **Quick action chips:** Pre-defined prompts above the chat input for common queries.
|
||||
- **Conversational detection (Option A):** When the response has no tool calls (pure text), the
|
||||
"Continue in Chat" link becomes a prominent filled button labeled "Continue this conversation →",
|
||||
signalling that the inline response is just a snippet of a longer exchange.
|
||||
- **Auto-focus:** Dashboard chat input gains focus on page mount (via `defineExpose({ focus })` on
|
||||
`DashboardChatInput` and a template ref in `HomeView`).
|
||||
- **Keyboard shortcut — Escape in ChatView:** Cascade close: note picker → mobile sidebar → clear
|
||||
textarea if non-empty → navigate to `/` home.
|
||||
- **Keyboard shortcuts overlay:** Press `?` (when not in a text input) or click the `?` button in
|
||||
the nav bar to open a panel listing all shortcuts. Escape or click-outside closes it. State shared
|
||||
via `useShortcuts` composable (`frontend/src/composables/useShortcuts.ts`).
|
||||
|
||||
### LLM Chat
|
||||
- Ollama integration via async HTTP (httpx), default model qwen3 (better tool support than mistral), auto-pull on startup
|
||||
@@ -540,21 +554,34 @@ When adding a new migration, follow these conventions:
|
||||
- Ollama configured with `OLLAMA_MAX_LOADED_MODELS=2` and `OLLAMA_KEEP_ALIVE=30m`
|
||||
- Timeout tuning: connect timeout 30s (cold model loading), warm timeout 300s, pull timeout 1800s
|
||||
- SSE reconnection failure shows error toast instead of silently recovering
|
||||
- **LLM tool calling:** Models with tool support can create tasks, create notes, and search notes
|
||||
on behalf of the user during chat. Multi-round tool loop (max 5 rounds) allows the LLM to
|
||||
execute tools and then produce a natural language response incorporating results. Tool call
|
||||
results are persisted in message `tool_calls` JSONB column and rendered as compact ToolCallCard
|
||||
components (linked titles for created items, search result lists). SSE emits `tool_call` events
|
||||
for real-time rendering during streaming. System prompt includes today's date for relative date
|
||||
resolution. Graceful degradation: models without tool support respond normally.
|
||||
- **LLM tool calling:** Models with tool support can create tasks, create/update notes, search notes,
|
||||
and manage CalDAV events/todos on behalf of the user during chat. Multi-round tool loop (max 5 rounds)
|
||||
allows the LLM to execute tools and then produce a natural language response incorporating results.
|
||||
Tool call results are persisted in message `tool_calls` JSONB column and rendered as compact
|
||||
ToolCallCard components (linked titles for created items, search result lists, event cards, todo cards).
|
||||
SSE emits `tool_call` events for real-time rendering during streaming. System prompt includes today's
|
||||
date for relative date resolution. Graceful degradation: models without tool support respond normally.
|
||||
`update_note` tool finds existing notes by exact title (falling back to fuzzy search) and supports
|
||||
`replace` (overwrite body) or `append` (add to existing body) modes — prevents duplicate note creation
|
||||
when the user provides follow-up content for an already-created note.
|
||||
- **Intent routing:** Before streaming, a fast non-streaming LLM call classifies user intent and
|
||||
extracts tool parameters (`services/intent.py`). If a tool call is detected, it executes directly
|
||||
— bypassing the model's native (sometimes unreliable) tool calling API. Falls through to normal
|
||||
streaming when no tool is detected or classification fails. Only runs on first round of tool loop.
|
||||
- **CalDAV calendar integration:** Per-user CalDAV settings (URL, username, password, calendar name).
|
||||
LLM tools: `create_event` (with `all_day` and `recurrence` support), `list_events`, `search_events`.
|
||||
Supports confidence levels ("high"/"medium"/"low") — low-confidence intents fall through to streaming.
|
||||
Passes last 6 user/assistant turns as history for anaphora resolution ("move it", "cancel that").
|
||||
Dedicated intent model configurable via `OLLAMA_INTENT_MODEL` env var or per-user `intent_model`
|
||||
setting — allows a smaller/faster model for routing while the main model handles responses.
|
||||
Intent router rules cover: update/delete events, CalDAV todos, time-period → list_events (not
|
||||
search_events), update_note vs create_note disambiguation, reminder_minutes conversion.
|
||||
- **CalDAV calendar integration:** Per-user CalDAV settings (URL, username, password, calendar name, timezone).
|
||||
LLM tools: `create_event` (all_day, recurrence, timezone, reminder_minutes, attendees, calendar_name),
|
||||
`list_events`, `search_events`, `update_event`, `delete_event`, `list_calendars`,
|
||||
`create_todo`, `list_todos`, `complete_todo`, `delete_todo`.
|
||||
All-day events use iCalendar DATE values; recurrence uses RRULE (e.g. `FREQ=YEARLY`).
|
||||
Runs synchronous caldav library calls in asyncio executor. Settings UI for CalDAV configuration.
|
||||
VALARM components added for reminders. Attendees via mailto: vCalAddress.
|
||||
Multi-calendar search: when no specific calendar configured, all calendars are scanned.
|
||||
Runs synchronous caldav library calls in asyncio executor. Settings UI for CalDAV config including timezone.
|
||||
- **LLM-suggested tags:** Backend service (`tag_suggestions.py`) prompts LLM with existing user tags
|
||||
and note content, returns 3-5 relevant tag suggestions. Tags already in body are filtered out.
|
||||
Exposed via `POST /api/notes/suggest-tags` and `POST /api/notes/:id/append-tag`. Integrated in:
|
||||
|
||||
Reference in New Issue
Block a user