diff --git a/frontend/public/sw.js b/frontend/public/sw.js index 5ce54d6..fd5182d 100644 --- a/frontend/public/sw.js +++ b/frontend/public/sw.js @@ -1,45 +1,11 @@ -// Service Worker for push notifications and PWA support -self.addEventListener('push', event => { - if (!event.data) return; - const data = event.data.json(); - const notifUrl = data.url || '/'; +// Service Worker — PWA installability shell only. +// +// The push and notificationclick listeners were removed alongside the chat +// generation pipeline (they only fired when the internal LLM finished a +// response). The empty fetch handler is preserved as the installability +// surface required for "Add to Home Screen" in some browsers; offline +// caching is intentionally not implemented yet. - event.waitUntil( - clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => { - // Suppress notification if the user already has the relevant page focused - for (const client of clientList) { - if (client.visibilityState === 'visible' && client.url.includes(notifUrl)) { - return; - } - } - return self.registration.showNotification(data.title || 'Scribe', { - body: data.body || '', - icon: '/favicon.ico', - badge: '/favicon.ico', - data: { url: notifUrl }, - }); - }) - ); -}); - -self.addEventListener('notificationclick', event => { - event.notification.close(); - event.waitUntil( - clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => { - const url = event.notification.data.url; - for (const client of clientList) { - if (client.url.includes(url) && 'focus' in client) { - return client.focus(); - } - } - if (clients.openWindow) { - return clients.openWindow(url); - } - }) - ); -}); - -// PWA: serve cached assets for offline support (minimal) -self.addEventListener('fetch', event => { +self.addEventListener('fetch', () => { // Pass-through — no offline caching in v1 }); diff --git a/frontend/src/App.vue b/frontend/src/App.vue index fd39b8e..4b0e8c5 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -5,43 +5,26 @@ import AppHeader from "@/components/AppHeader.vue"; import ToastNotification from "@/components/ToastNotification.vue"; import { useTheme } from "@/composables/useTheme"; import { useShortcuts } from "@/composables/useShortcuts"; -import { useOnnxPreloader } from "@/composables/useOnnxPreloader"; import { useAuthStore } from "@/stores/auth"; -import { useChatStore } from "@/stores/chat"; import { useSettingsStore } from "@/stores/settings"; import { apiGet, apiPut } from "@/api/client"; useTheme(); -const { schedulePreload: scheduleVadPreload } = useOnnxPreloader(); -scheduleVadPreload(); - const router = useRouter(); const appVersion = ref("dev"); const authStore = useAuthStore(); -const chatStore = useChatStore(); const settingsStore = useSettingsStore(); const { showShortcuts, toggleShortcuts, closeShortcuts } = useShortcuts(); function startAppServices() { - chatStore.startStatusPolling(); settingsStore.fetchSettings(); - settingsStore.checkVoiceStatus(); // Sync browser timezone to the server on every login/page load. apiPut("/api/settings", { user_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }).catch(() => {}); - // Re-check voice status when the tab becomes visible again (model may - // have finished loading while the user was away). - document.addEventListener("visibilitychange", onVisibilityChange); -} - -function onVisibilityChange() { - if (document.visibilityState === "visible" && authStore.isAuthenticated) { - settingsStore.checkVoiceStatus(); - } } function stopAppServices() { - chatStore.stopStatusPolling(); + // no-op for now; kept as a hook for future per-session teardown } function isInputActive(): boolean { @@ -96,7 +79,6 @@ function onGlobalKeydown(e: KeyboardEvent) { case "n": router.push("/notes"); break; case "t": router.push("/"); break; case "p": router.push("/projects"); break; - case "c": router.push("/chat"); break; case "g": router.push("/graph"); break; case "l": router.push("/calendar"); break; } @@ -126,13 +108,6 @@ function onGlobalKeydown(e: KeyboardEvent) { e.preventDefault(); document.dispatchEvent(new CustomEvent("shortcut:focus-search")); break; - case "c": - if (router.currentRoute.value.name === "home") { - document.dispatchEvent(new CustomEvent("shortcut:focus-chat")); - } else { - router.push("/chat"); - } - break; } } @@ -163,7 +138,6 @@ watch( onUnmounted(() => { document.removeEventListener("keydown", onGlobalKeydown); - document.removeEventListener("visibilitychange", onVisibilityChange); stopAppServices(); }); @@ -214,12 +188,6 @@ onUnmounted(() => { p Projects -
- g - + - c - Chat -
g + @@ -267,23 +235,6 @@ onUnmounted(() => { Edit current item (viewer)
-
-
Chat
-
- c - Focus chat (home) / go to chat -
-
- Enter - Send message -
-
- Shift - + - Enter - New line -
-
@@ -328,8 +279,6 @@ onUnmounted(() => { min-height: 0; overflow-y: auto; } -.app-content:has(.workspace-root), -.app-content:has(.chat-page), .app-content:has(.knowledge-root) { overflow: hidden; } diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue index f11789e..553b0db 100644 --- a/frontend/src/components/AppHeader.vue +++ b/frontend/src/components/AppHeader.vue @@ -4,7 +4,6 @@ import { useRouter, useRoute } from "vue-router"; import { useTheme } from "@/composables/useTheme"; import { useShortcuts } from "@/composables/useShortcuts"; import { useAuthStore } from "@/stores/auth"; -import { useChatStore } from "@/stores/chat"; import AppLogo from "@/components/AppLogo.vue"; import NotificationBell from "@/components/NotificationBell.vue"; import { Sun, Moon, Settings } from "lucide-vue-next"; @@ -12,42 +11,13 @@ import { Sun, Moon, Settings } from "lucide-vue-next"; const { theme, toggleTheme } = useTheme(); const { toggleShortcuts } = useShortcuts(); const authStore = useAuthStore(); -const chatStore = useChatStore(); const router = useRouter(); const route = useRoute(); -const isChatActive = computed(() => route.path.startsWith("/chat")) const isKnowledgeActive = computed(() => route.path === "/knowledge"); const mobileMenuOpen = ref(false); -const statusShortLabel = computed(() => { - if (chatStore.ollamaStatus === "unavailable") return "Offline"; - if (chatStore.ollamaStatus === "checking") return "Checking"; - if (chatStore.modelStatus === "not_found") return "No model"; - if (chatStore.modelStatus === "cold") return "Cold"; - if (chatStore.modelStatus === "loaded") return "Ready"; - return "Checking"; -}); - -const statusLabel = computed(() => { - if (chatStore.ollamaStatus === "unavailable") return "Ollama unavailable"; - if (chatStore.ollamaStatus === "checking") return "Checking..."; - if (chatStore.modelStatus === "not_found") return "Model not installed"; - if (chatStore.modelStatus === "cold") return "Model cold — first response may be slow"; - if (chatStore.modelStatus === "loaded") return "Model loaded · ready"; - return "Checking..."; -}); - -const statusClass = computed(() => { - if (chatStore.ollamaStatus === "unavailable") return "status-red"; - if (chatStore.ollamaStatus === "checking") return "status-gray"; - if (chatStore.modelStatus === "not_found") return "status-orange"; - if (chatStore.modelStatus === "cold") return "status-yellow"; - if (chatStore.modelStatus === "loaded") return "status-green"; - return "status-gray"; -}); - function toggleMobileMenu() { mobileMenuOpen.value = !mobileMenuOpen.value; } @@ -76,20 +46,13 @@ router.afterEach(() => { - + - -
- -
- Chat -
- - -
-
- - -
- -
- - -
- -
-
- @@ -1294,71 +1202,4 @@ onUnmounted(() => { height: 100%; } -/* ── Mini-chat ───────────────────────────────────────────── */ -.minichat { - position: absolute; - bottom: 0; - left: var(--sidebar-width); /* align with content area past filter panel */ - right: 0; - max-width: var(--page-max-width); - margin-inline: auto; - z-index: 20; - display: flex; - flex-direction: column; - background: var(--color-bg-secondary); - border-radius: 16px 16px 0 0; - box-shadow: 0 -8px 32px rgba(0,0,0,0.35), 0 -1px 0 rgba(255,255,255,0.05); - transition: height 0.2s ease; -} -.minichat--open { - height: 500px; -} -.knowledge-root.graph-open .minichat { - right: 500px; - transition: right 0.2s ease; -} -.knowledge-root.graph-open.graph-expanded .minichat { - right: min(960px, 60vw); -} - -/* Header row (collapse / close buttons) */ -.minichat-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 14px; - border-bottom: 1px solid rgba(255,255,255,0.06); - flex-shrink: 0; -} -.minichat-title { - font-size: 0.82rem; - font-weight: 500; - color: var(--color-muted); - text-transform: uppercase; - letter-spacing: 0.06em; -} -.minichat-header-actions { - display: flex; - gap: 4px; - align-items: center; -} - -/* ChatPanel body — fills remaining space */ -.minichat-chat-panel { - flex: 1; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -.minichat-chat-panel :deep(.chat-body) { - flex: 1; - min-height: 0; -} - -/* Standalone input row (closed / collapsed state) */ -.minichat-input-row { - padding: 10px 14px; - flex-shrink: 0; -} diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue index 95678bf..9f1a65c 100644 --- a/frontend/src/views/SettingsView.vue +++ b/frontend/src/views/SettingsView.vue @@ -82,7 +82,7 @@ const appVersion = ref('dev'); const restoreFileInput = ref(null); // Migrate stored "admin" → "config"; unknown tabs fall back to "general" -const VALID_TABS = new Set(["general", "account", "profile", "notifications", "integrations", "data", "voice", "apikeys", "config", "users", "logs", "groups"]); +const VALID_TABS = new Set(["general", "account", "profile", "notifications", "integrations", "data", "apikeys", "config", "users", "logs", "groups"]); const _stored = localStorage.getItem("settings_tab") ?? "general"; const activeTab = ref(VALID_TABS.has(_stored) ? (_stored === "admin" ? "config" : _stored) : "general"); @@ -92,7 +92,6 @@ function _loadTabContent(tab: string) { else if (tab === "logs") loadLogsPanel(); else if (tab === "groups") loadGroupsPanel(); } - if (tab === "voice") loadVoiceTab(); if (tab === "apikeys") { fetchApiKeys(); } } @@ -1482,7 +1481,7 @@ function formatUserDate(iso: string): string { - -

Tasks

@@ -1608,68 +1554,6 @@ function formatUserDate(iso: string): string {
- -
-
-
-

Model Management

-

Install and remove Ollama models without leaving the app. Search ollama.com/library for available models.

-
- -
- - -
-
-
- {{ m.name }} - in VRAM - default -
-
- {{ formatBytes(m.size) }} - -
-
-
-

No models installed, or Ollama is unreachable.

- - -
- - -
-
- Suggestions: - -
- - -
-
{{ pullProgress.status }}
-
-
-
-
-
-
@@ -1946,109 +1830,6 @@ function formatUserDate(iso: string): string { -
-

Journal

-

Controls when the daily journal prep generates and how the day is framed.

-
- -

When enabled, the journal generates a morning briefing at the time below. When off, the journal still works — just without the auto-generated daily prep.

-
-
-
- -
- - : - -
-

24-hour. Generates each morning at this local time.

-
-
- -
- - :00 -
-

Hour when the journal switches to a new day. Default 4am — late-night entries (1–3am) still count as the previous day.

-
-
-
- - Saved! -
-
- -
-

What the Assistant Has Learned

-

- The assistant observes patterns from your journal and chat conversations and builds a summary over time. The summary is included in the journal's system prompt so the daily prep can reference what it knows about you. - {{ profile.observations_count }} raw observation{{ profile.observations_count !== 1 ? 's' : '' }} stored. -

- - - -
{{ profile.learned_summary }}
-
No learned summary yet. Observations accumulate from journal and chat conversations.
- -
- -
-
Loading…
-
No observations yet.
-
-
-
{{ entry.date }}
-
{{ entry.bullets }}
-
-
-
-
- -
- - -
-
@@ -2080,103 +1861,6 @@ function formatUserDate(iso: string): string {
-
-

Push Notifications

-

- Receive browser push notifications when a response is ready. Requires HTTPS. -

- - - -
- -
-

Chat History

-

- Conversations older than this many days are automatically deleted when you load the chat. - Set to 0 to keep conversations forever. -

-
- -
- - -
-
-
- -
-

About

-

Fabled Scribe {{ appVersion }}

-
- -
@@ -2307,198 +1991,6 @@ function formatUserDate(iso: string): string {
-
- -
-

Voice

-

- Configure text-to-speech and speech-to-text for voice conversations. - Requires VOICE_ENABLED=true on the server. -

- - -
Loading voice status…
-
Voice status unavailable.
-
- - {{ voiceStatus.enabled ? 'Enabled' : 'Disabled' }} - - - STT {{ voiceStatus.stt ? 'ready' : 'loading…' }} - - - TTS {{ voiceStatus.tts ? 'ready' : 'loading…' }} - - - Model: {{ voiceStatus.stt_model }} - -
- -
- Voice is disabled. An administrator can enable it under - Admin → Config → Voice. -
-
- -
-

Speech Style

-

Controls how the assistant phrases spoken responses.

- -
- -
- -
-
-
- -
-

Voice & Speed

-

Choose the TTS voice and playback speed.

- -
- - -

Voice character and accent. Applies to all voice conversations.

-
- -
- - -
- 0.7× (slow) - 1.0× (normal) - 1.3× (fast) -
-
- -
- - -
-
- - - - -
-
-

Voice Library

- -
-

- Download additional piper voices from the - piper-voices catalog. - Installed voices appear in every user's voice picker after a page reload. -

- - -
- -
diff --git a/frontend/src/views/WorkspaceView.vue b/frontend/src/views/WorkspaceView.vue deleted file mode 100644 index ab33b27..0000000 --- a/frontend/src/views/WorkspaceView.vue +++ /dev/null @@ -1,240 +0,0 @@ - - - - -