Add Projects, Milestones, RAG auto-inject, push notifications, PWA, tag normalisation
## Projects & Milestones (Phases A + G) - New models: Project, Milestone (Project → Milestone → Task hierarchy) - notes table: project_id + milestone_id FKs; parent_id FK constraint activated - Migrations: 0017 (projects), 0018 (push_subscriptions), 0019 (events), 0020 (milestones) - Services: projects.py, milestones.py (CRUD + progress tracking) - Routes: /api/projects + /api/projects/<id>/milestones - LLM tools: create/list/get/update project; create/list milestone; project + milestone + parent_task params on note/task tools - Frontend: ProjectListView (stacked milestone bars), ProjectView (milestone-grouped kanban), ProjectSelector, MilestoneSelector, NoteEditorView + TaskEditorView updated ## RAG Auto-injection (Phase B) - Notes ≥0.60 cosine similarity auto-injected into system prompt (max 3, 800 chars each) - excluded_note_ids param; ChatView "Auto-included" sidebar section ## Summarisation improvements (Phase C) - Threshold 20→30, keep-recent 6→8, max_tokens 200→400 - Two-pass summarisation for histories >50 messages ## Browser push notifications (Phase E) - PushSubscription model + migration; pywebpush dependency - /api/push routes; VAPID config; fire-and-forget on generation complete - Frontend: sw.js, push store, Settings toggle ## PWA manifest (Phase F) - manifest.json, Apple meta tags, service worker registration in main.ts ## Tag normalisation - All tags lowercased + deduplicated at backend (create_note/update_note) and frontend (TagInput sanitize) - Note/Task types gain project_id + milestone_id fields; store signatures updated ## CalDAV - Radicale embedded server reverted; back to user-configured external CalDAV Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,10 +4,12 @@ import { useSettingsStore } from "@/stores/settings";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useToastStore } from "@/stores/toast";
|
||||
import { apiGet, apiPost, apiPut } from "@/api/client";
|
||||
import { usePushStore } from "@/stores/push";
|
||||
|
||||
const store = useSettingsStore();
|
||||
const authStore = useAuthStore();
|
||||
const toastStore = useToastStore();
|
||||
const pushStore = usePushStore();
|
||||
const assistantName = ref("");
|
||||
const defaultModel = ref("");
|
||||
const installedModels = ref<string[]>([]);
|
||||
@@ -73,6 +75,7 @@ const searchError = ref("");
|
||||
|
||||
onMounted(async () => {
|
||||
await store.fetchSettings();
|
||||
pushStore.checkSubscription();
|
||||
assistantName.value = store.assistantName;
|
||||
newEmail.value = authStore.user?.email ?? "";
|
||||
|
||||
@@ -640,6 +643,59 @@ function hostname(url: string): string {
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<!-- ── Push Notifications ── half width ── -->
|
||||
<section class="settings-section">
|
||||
<h2>Push Notifications</h2>
|
||||
<p class="section-desc">
|
||||
Receive browser push notifications when a response is ready.
|
||||
</p>
|
||||
<template v-if="!pushStore.isSupported">
|
||||
<p class="push-unsupported">Push notifications are not supported in this browser.</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="push-status-row">
|
||||
<span class="push-status-label">Permission:</span>
|
||||
<span
|
||||
:class="['push-permission-badge', {
|
||||
'perm-granted': pushStore.permission === 'granted',
|
||||
'perm-denied': pushStore.permission === 'denied',
|
||||
'perm-default': pushStore.permission === 'default',
|
||||
}]"
|
||||
>
|
||||
{{ pushStore.permission === 'granted' ? 'Granted' : pushStore.permission === 'denied' ? 'Denied' : 'Not asked' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="push-status-row">
|
||||
<span class="push-status-label">Status:</span>
|
||||
<span :class="['push-sub-badge', { 'sub-active': pushStore.isSubscribed }]">
|
||||
{{ pushStore.isSubscribed ? 'Subscribed' : 'Not subscribed' }}
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="pushStore.error" class="push-error">{{ pushStore.error }}</p>
|
||||
<div class="actions" style="margin-top: 0.75rem;">
|
||||
<button
|
||||
v-if="!pushStore.isSubscribed"
|
||||
class="btn-save"
|
||||
@click="pushStore.subscribe()"
|
||||
:disabled="pushStore.loading || pushStore.permission === 'denied'"
|
||||
>
|
||||
{{ pushStore.loading ? 'Enabling...' : 'Enable Notifications' }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn-secondary"
|
||||
@click="pushStore.unsubscribe()"
|
||||
:disabled="pushStore.loading"
|
||||
>
|
||||
{{ pushStore.loading ? 'Disabling...' : 'Disable Notifications' }}
|
||||
</button>
|
||||
</div>
|
||||
<p v-if="pushStore.permission === 'denied'" class="field-hint" style="margin-top: 0.5rem;">
|
||||
Notifications are blocked. Allow them in your browser site settings to re-enable.
|
||||
</p>
|
||||
</template>
|
||||
</section>
|
||||
|
||||
<!-- ── Application URL (admin) ── full width ── -->
|
||||
<section v-if="authStore.isAdmin" class="settings-section full-width">
|
||||
<h2>Application URL</h2>
|
||||
@@ -1022,6 +1078,43 @@ function hostname(url: string): string {
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
/* Push notifications */
|
||||
.push-unsupported {
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
.push-status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.4rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.push-status-label {
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
.push-permission-badge,
|
||||
.push-sub-badge {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
padding: 0.1rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--color-text-muted) 15%, transparent);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.perm-granted { background: color-mix(in srgb, var(--color-success) 15%, transparent); color: var(--color-success); }
|
||||
.perm-denied { background: color-mix(in srgb, var(--color-danger, #e74c3c) 15%, transparent); color: var(--color-danger, #e74c3c); }
|
||||
.sub-active { background: color-mix(in srgb, var(--color-success) 15%, transparent); color: var(--color-success); }
|
||||
.push-error {
|
||||
font-size: 0.82rem;
|
||||
color: var(--color-danger, #e74c3c);
|
||||
margin: 0.25rem 0 0;
|
||||
}
|
||||
|
||||
/* Responsive — collapse to 1 column on narrow screens */
|
||||
@media (max-width: 640px) {
|
||||
.settings-grid {
|
||||
|
||||
Reference in New Issue
Block a user