From 70cba72a800ac690c7d6747b5cd689a2f813a953 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 17 Feb 2026 22:04:41 -0500 Subject: [PATCH] Phase 10: CalDAV full lifecycle, update_note, dashboard inline streaming, keyboard shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/App.vue | 165 +++++++ frontend/src/components/AppHeader.vue | 20 + .../src/components/DashboardChatInput.vue | 6 + frontend/src/components/ToolCallCard.vue | 107 +++++ frontend/src/composables/useShortcuts.ts | 12 + frontend/src/views/ChatView.vue | 30 +- frontend/src/views/HomeView.vue | 269 ++++++++++- frontend/src/views/SettingsView.vue | 23 +- src/fabledassistant/config.py | 3 + src/fabledassistant/services/caldav.py | 447 +++++++++++++++++- .../services/generation_task.py | 37 +- src/fabledassistant/services/intent.py | 65 ++- src/fabledassistant/services/llm.py | 15 +- src/fabledassistant/services/tools.py | 388 ++++++++++++++- summary.md | 53 ++- 15 files changed, 1569 insertions(+), 71 deletions(-) create mode 100644 frontend/src/composables/useShortcuts.ts diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 80d99b1..786dd3b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -3,6 +3,7 @@ import { onMounted, onUnmounted, watch } from "vue"; import AppHeader from "@/components/AppHeader.vue"; import ToastNotification from "@/components/ToastNotification.vue"; import { useTheme } from "@/composables/useTheme"; +import { useShortcuts } from "@/composables/useShortcuts"; import { useAuthStore } from "@/stores/auth"; import { useChatStore } from "@/stores/chat"; import { useSettingsStore } from "@/stores/settings"; @@ -12,6 +13,7 @@ useTheme(); const authStore = useAuthStore(); const chatStore = useChatStore(); const settingsStore = useSettingsStore(); +const { showShortcuts, toggleShortcuts, closeShortcuts } = useShortcuts(); function startAppServices() { chatStore.startStatusPolling(); @@ -22,7 +24,28 @@ function stopAppServices() { chatStore.stopStatusPolling(); } +function isInputActive(): boolean { + const el = document.activeElement; + if (!el) return false; + const tag = (el as HTMLElement).tagName; + return tag === "INPUT" || tag === "TEXTAREA" || (el as HTMLElement).isContentEditable; +} + +function onGlobalKeydown(e: KeyboardEvent) { + if (!authStore.isAuthenticated) return; + // ? — toggle shortcuts overlay (only when not typing) + if (e.key === "?" && !isInputActive() && !e.ctrlKey && !e.metaKey) { + toggleShortcuts(); + return; + } + // Escape — close shortcuts overlay + if (e.key === "Escape" && showShortcuts.value) { + closeShortcuts(); + } +} + onMounted(async () => { + document.addEventListener("keydown", onGlobalKeydown); await authStore.checkAuth(); if (authStore.isAuthenticated) { startAppServices(); @@ -41,6 +64,7 @@ watch( ); onUnmounted(() => { + document.removeEventListener("keydown", onGlobalKeydown); stopAppServices(); }); @@ -53,6 +77,48 @@ onUnmounted(() => { + + + +
+
+
+

Keyboard Shortcuts

+ +
+
+
+
Navigation
+
+ Esc + Go to home / close dialog +
+
+ ? + Toggle this shortcuts panel +
+
+
+
Chat
+
+ Enter + Send message +
+
+ Shift + + + Enter + New line in message +
+
+ Esc + Clear input / go home +
+
+
+
+
+