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>
This commit is contained in:
2026-02-15 22:40:20 -05:00
parent 8996b45e50
commit d7bc3f3222
22 changed files with 1158 additions and 837 deletions
+22 -8
View File
@@ -12,7 +12,7 @@
> Include file-level details in the commit body when the change is non-trivial.
## Last Updated
2026-02-14 — Phase 7: LLM tool calling (create tasks, create notes, search notes from chat)
2026-02-15 — Phase 8: CalDAV calendar integration, LLM-suggested tags, settings/model UI refinements
## Project Overview
Fabled Assistant is a self-hosted note-taking and task-tracking application with
@@ -212,7 +212,7 @@ for AI-assisted features.
```
fabledassistant/
├── summary.md # This file — canonical project context
├── pyproject.toml # Python project config
├── pyproject.toml # Python project config (deps include caldav, icalendar)
├── Dockerfile # Multi-stage build (Node → Python)
├── docker-compose.yml # Dev compose (app, PostgreSQL, Ollama)
├── docker-compose.prod.yml # Production stack (Docker Swarm, secrets, network isolation)
@@ -253,7 +253,7 @@ fabledassistant/
│ │ ├── auth.py # /api/auth blueprint: register, login, logout, me, status, password reset, invitation registration
│ │ ├── admin.py # /api/admin blueprint: backup, restore, user management, registration toggle, invitations, base URL, SMTP (admin only)
│ │ ├── chat.py # /api/chat blueprint: conversations CRUD, SSE message streaming (all @login_required)
│ │ ├── notes.py # /api/notes CRUD + wikilinks + backlinks (all @login_required)
│ │ ├── notes.py # /api/notes CRUD + wikilinks + backlinks + tag suggestions (all @login_required)
│ │ ├── tasks.py # /api/tasks CRUD + PATCH status (all @login_required)
│ │ └── settings.py # /api/settings GET/PUT — per-user settings (@login_required)
│ ├── services/
@@ -264,7 +264,9 @@ fabledassistant/
│ │ ├── chat.py # Conversation CRUD with user_id isolation, add_message, save/summarize as note (LLM-titled, chat-tagged)
│ │ ├── 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, tool loop) + run_assist_generation (lightweight, no DB)
│ │ ├── tools.py # LLM tool definitions (create_task, create_note, search_notes) + execute_tool dispatcher
│ │ ├── tools.py # LLM tool definitions (create_task, create_note, search_notes, CalDAV events) + 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 (per-user config from settings)
│ │ ├── 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
@@ -320,10 +322,10 @@ fabledassistant/
│ │ ├── HomeView.vue # Actionable dashboard: overdue, due today, due this week, high priority, in progress tasks; recent chats with inline chat input; recently edited notes; model warming on mount
│ │ ├── SettingsView.vue # Settings page: assistant name, model catalog, data export/restore (admin)
│ │ ├── NotesListView.vue # Note list: search, sort, tag filter pills, pagination
│ │ ├── NoteEditorView.vue # Create/edit: Tiptap editor, sticky toolbar, AI Assist panel (bottom 1/3), Ctrl+S, unsaved guard
│ │ ├── NoteEditorView.vue # Create/edit: Tiptap editor, sticky toolbar, AI Assist panel (bottom 1/3), LLM tag suggestions, Ctrl+S, unsaved guard
│ │ ├── NoteViewerView.vue # Markdown render, wikilink auto-create, convert-to-task, backlinks, table of contents sidebar
│ │ ├── TasksListView.vue # Task list: search, status/priority filters, sort, pagination
│ │ ├── TaskEditorView.vue # Create/edit task: Tiptap editor, sticky toolbar, AI Assist panel, Ctrl+S, dirty guard
│ │ ├── TaskEditorView.vue # Create/edit task: Tiptap editor, sticky toolbar, AI Assist panel, LLM tag suggestions, Ctrl+S, dirty guard
│ │ └── TaskViewerView.vue # Task detail: rendered markdown, badges, convert-to-note, backlinks, table of contents sidebar
│ ├── components/
│ │ ├── LogsView.vue # Admin log viewer: stats summary, category/search/date filters, paginated table with expandable detail rows
@@ -331,7 +333,7 @@ fabledassistant/
│ │ ├── ChatPanel.vue # Slide-out chat panel (right side overlay, receives contextNoteId prop), bubble-style messages, floating dark input, note picker, context pills with promote/exclude
│ │ ├── ModelSelector.vue # Model dropdown (v-model pattern): fetches installed + running models, hot/cold indicators
│ │ ├── DashboardChatInput.vue # Inline chat bar: ModelSelector + note picker + textarea + send button; emits submit event
│ │ ├── ToolCallCard.vue # Compact card for tool call results (created task/note link, search results, errors)
│ │ ├── ToolCallCard.vue # Compact card for tool call results (created task/note link, search results, errors) + suggested tag pills with apply-on-click
│ │ ├── ChatMessage.vue # Message bubble: markdown rendering, tool call cards, configurable assistant name label, "Save as Note" action on assistant messages
│ │ ├── NoteCard.vue # Card with rendered markdown preview (v-html), TagPill, tag-click emit, hover edit button
│ │ ├── TaskCard.vue # Card with rendered preview (body not description), StatusBadge (clickable), PriorityBadge, due date, tags, hover edit button
@@ -372,6 +374,8 @@ fabledassistant/
| GET | `/api/notes` | List notes (params: `q`, `tag`, `sort`, `order`, `limit`, `offset`; defaults to `is_task=false` — plain notes only; `?is_task=true` for tasks, `?all=true` for everything) |
| POST | `/api/notes` | Create note (body: `{title, body, status?, priority?, due_date?}` — tags auto-extracted) |
| GET | `/api/notes/tags` | List all tags from notes table (param: `q` for filter) |
| POST | `/api/notes/suggest-tags` | LLM-suggest tags for content (body: `{title, body}`, returns `{suggested_tags: [...]}`) |
| POST | `/api/notes/:id/append-tag` | Append `#tag` to note body (body: `{tag}`, returns updated note) |
| GET | `/api/notes/by-title?title=...` | Resolve note by exact title (case-insensitive) |
| POST | `/api/notes/resolve-title` | Get-or-create note by title (for wikilink clicks) |
| GET | `/api/notes/:id` | Get single note (response includes `is_task`, `status`, `priority`, `due_date`) |
@@ -542,6 +546,15 @@ When adding a new migration, follow these conventions:
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.
- **CalDAV calendar integration:** Per-user CalDAV settings (URL, username, password, calendar name).
LLM tools: `create_event`, `list_events`, `search_events`. Runs synchronous caldav library calls
in asyncio executor. Settings UI for CalDAV configuration.
- **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:
(1) Editor views — "Suggest tags" button shows clickable pills that insert `#tag` into body;
(2) Chat tool calls — `create_note`/`create_task` results include `suggested_tags`, rendered as
pills in ToolCallCard with one-click apply via append-tag API.
### Authentication & User Management
- Session cookie auth with bcrypt, first-user-is-admin, orphaned data claiming
@@ -594,9 +607,10 @@ When adding a new migration, follow these conventions:
- To reset database: `docker compose down -v && docker compose up --build`
## Backlog
- Tagging/labeling system with LLM-suggested tags
- Calendar/timeline view for tasks
- Import/export (Markdown files, JSON)
- Email integration (read/send emails from chat via IMAP/SMTP tools)
- Web search support (LLM tool to search the web and summarize results)
- Application-level rate limiting on auth endpoints
- Security headers middleware (CSP, X-Frame-Options, etc.) — currently handled at reverse proxy level
- Session invalidation on user deletion