refactor: Phase 8 — backend deletion (chat / voice / push / journal / curator)

Mega-commit. Strips all server-side LLM machinery now that Phase 7 has
removed the corresponding UI surfaces and the MCP HTTP endpoint is the
sole assistant interface.

Deleted (services/):
  chat, generation_buffer, generation_log, generation_task, llm, tools/
  (entire package), stt, tts, voice_config, voice_library, push,
  journal_closeout, journal_pipeline, journal_prep, journal_scheduler,
  journal_search, curator, curator_scheduler, consolidation,
  tag_suggestions, research, weather, article_fetcher, pending_actions,
  moments, assist, wikipedia.

Deleted (routes/):
  chat, voice, push, journal, quick_capture, fable_mcp_dist.

Deleted (models/):
  conversation, generation_tool_log, push_subscription,
  pending_curator_action, moment, weather_cache.

Deleted (tests/):
  test_generation_log, test_journal_*, test_consolidation, test_lookup_tool,
  test_notes_consolidation_trigger, test_record_moment_guards,
  test_research_pipeline, test_tools_*, test_tool_use_fixes,
  test_voice_library, test_weather_service, test_calendar_tool_tz,
  test_wikipedia.

Deleted (top-level):
  fable-mcp/ (legacy standalone stdio package — wheel-build pipeline
  also removed from Dockerfile).

app.py:
  - blueprint registrations for the 6 deleted routes
  - startup hook trimmed: no more Ollama warmup, KV-cache priming,
    journal/curator schedulers, voice model loading
  - shutdown hook simplified
  - httpx import dropped (was for Ollama calls)

pyproject.toml:
  - removed deps: pywebpush, feedparser, html2text, trafilatura
  - removed [voice] extras entirely
  - description updated for the MCP-first architecture

Dockerfile:
  - removed faster-whisper / piper-tts install steps
  - removed bundled piper voice download stage
  - removed fable-mcp wheel build stage

Surviving-file edits:
  - services/auth.py: drop Conversation table claim on first-user setup
  - services/backup.py: drop conversation / push-subscription export+restore;
    v1/v2 restore now silently skip pre-pivot conversation data
  - services/notes.py: drop maybe_consolidate trigger on task done/cancelled;
    drop _maybe_trigger_project_summary (LLM auto-summary)
  - services/projects.py: drop generate_project_summary + backfill_project_summaries
    (both LLM-driven)
  - services/user_profile.py: drop append_observations / consolidate /
    clear_learned_data (curator-tied) and build_profile_context
    (was LLM system-prompt builder)
  - services/notifications.py: stub out _fire_push_notif (was send_push_notification)
  - services/event_scheduler.py: drop event-reminder push + chat-retention
    cleanup job; keep CalDAV pull-sync + reminders job (in-app)
  - services/diagnostics.py: _curator_busy() always False
  - routes/notes.py: drop /assist, /assist/stream, /suggest-tags endpoints
  - routes/tasks.py: drop /<id>/consolidate endpoint
  - routes/settings.py: drop /models, KV-cache-prime-on-save, journal-schedule
    timezone hook, and the SearXNG search-test endpoint; inline _is_private_url
    (was in services/llm.py)
  - routes/admin.py: drop /voice, /voice/reload endpoints
  - routes/profile.py: drop /consolidate, /observations (GET, DELETE)
  - models/__init__.py: drop the 6 dead model imports

Frontend cascade:
  - stores/push.ts: deleted entirely (no callers after Phase 7)
  - stores/settings.ts: drop checkVoiceStatus + voice-status state
  - views/SettingsView.vue: drop Locations section + journalConfig state
    (was tied to /api/journal/config); drop JournalConfig + journal/voice
    api/client imports
  - frontend/api/client.ts: orphaned voice/journal/profile-observation/
    fable-mcp-dist exports are left as dead but harmless (call them and
    they 404; type-check is clean).

Pre-existing v1 backups that contained conversations/messages still
restore — those tables are silently dropped from the import path.
Anyone pulling the new image with a populated database will need the
Phase 9 migration to drop the dead tables (coming next).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 17:47:18 -04:00
parent 8bec68abc0
commit 91bafb641f
123 changed files with 161 additions and 19312 deletions
-109
View File
@@ -1,109 +0,0 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
function urlBase64ToUint8Array(base64String: string): Uint8Array<ArrayBuffer> {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
const rawData = window.atob(base64)
const buf = new ArrayBuffer(rawData.length)
const outputArray = new Uint8Array<ArrayBuffer>(buf)
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i)
}
return outputArray
}
export const usePushStore = defineStore('push', () => {
const isSupported = ref(
'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window
)
const permission = ref<NotificationPermission>(
'Notification' in window ? Notification.permission : 'denied'
)
const isSubscribed = ref(false)
const loading = ref(false)
const error = ref<string | null>(null)
async function checkSubscription(): Promise<void> {
if (!isSupported.value) return
try {
const reg = await navigator.serviceWorker.ready
const sub = await reg.pushManager.getSubscription()
isSubscribed.value = !!sub
} catch {
isSubscribed.value = false
}
}
async function subscribe(): Promise<void> {
if (!isSupported.value) {
error.value = 'Push notifications not supported in this browser'
return
}
loading.value = true
error.value = null
try {
// Request permission
permission.value = await Notification.requestPermission()
if (permission.value !== 'granted') {
error.value = 'Notification permission denied'
return
}
// Fetch VAPID public key
const resp = await fetch('/api/push/vapid-public-key')
if (!resp.ok) {
error.value = 'Push notifications not configured on server'
return
}
const { publicKey } = await resp.json()
// Register service worker
const reg = await navigator.serviceWorker.register('/sw.js')
await navigator.serviceWorker.ready
// Subscribe
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicKey),
})
// Save to server
const saveResp = await fetch('/api/push/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sub.toJSON()),
})
if (!saveResp.ok) throw new Error('Failed to save subscription')
isSubscribed.value = true
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Failed to subscribe'
} finally {
loading.value = false
}
}
async function unsubscribe(): Promise<void> {
loading.value = true
error.value = null
try {
const reg = await navigator.serviceWorker.ready
const sub = await reg.pushManager.getSubscription()
if (sub) {
await fetch('/api/push/subscribe', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ endpoint: sub.endpoint }),
})
await sub.unsubscribe()
}
isSubscribed.value = false
} catch (e: unknown) {
error.value = e instanceof Error ? e.message : 'Failed to unsubscribe'
} finally {
loading.value = false
}
}
return { isSupported, permission, isSubscribed, loading, error, checkSubscription, subscribe, unsubscribe }
})
+2 -34
View File
@@ -1,6 +1,6 @@
import { ref, computed } from "vue";
import { ref } from "vue";
import { defineStore } from "pinia";
import { apiGet, apiPut, getVoiceStatus } from "@/api/client";
import { apiGet, apiPut } from "@/api/client";
import { useToastStore } from "@/stores/toast";
import type { AppSettings } from "@/types/settings";
@@ -8,32 +8,6 @@ export const useSettingsStore = defineStore("settings", () => {
const settings = ref<AppSettings>({});
const loading = ref(false);
const assistantName = computed(
() => settings.value.assistant_name || "Fable"
);
const defaultModel = computed(
() => settings.value.default_model || ""
);
// Voice status — checked once on login, refreshable from Settings
const voiceEnabled = ref(false);
const voiceSttReady = ref(false);
const voiceTtsReady = ref(false);
async function checkVoiceStatus() {
try {
const s = await getVoiceStatus();
voiceEnabled.value = s.enabled;
voiceSttReady.value = s.enabled && s.stt;
voiceTtsReady.value = s.enabled && s.tts;
} catch {
voiceEnabled.value = false;
voiceSttReady.value = false;
voiceTtsReady.value = false;
}
}
async function fetchSettings() {
loading.value = true;
try {
@@ -60,12 +34,6 @@ export const useSettingsStore = defineStore("settings", () => {
return {
settings,
loading,
assistantName,
defaultModel,
voiceEnabled,
voiceSttReady,
voiceTtsReady,
checkVoiceStatus,
fetchSettings,
updateSettings,
};