feat(profile): Settings panel with recent journal observations
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { ref, computed, watch, onMounted } from "vue";
|
|||||||
import { useSettingsStore } from "@/stores/settings";
|
import { useSettingsStore } from "@/stores/settings";
|
||||||
import { useAuthStore } from "@/stores/auth";
|
import { useAuthStore } from "@/stores/auth";
|
||||||
import { useToastStore } from "@/stores/toast";
|
import { useToastStore } from "@/stores/toast";
|
||||||
import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGroup, listGroupMembers, addGroupMember, removeGroupMember, searchUsers, getFableMcpInfo, listApiKeys, createApiKey as apiCreateApiKey, revokeApiKey as apiRevokeApiKey, getVoiceStatus, getVoiceList, synthesiseSpeech, getProfile, updateProfile, consolidateProfile, clearProfileObservations, getJournalConfig, saveJournalConfig, geocodeAddress, type ApiKeyEntry, type GroupEntry, type GroupMember, type UserSearchResult, type VoiceStatusResult, type VoiceEntry, type VoiceBlendEntry, type UserProfile, type JournalConfig } from "@/api/client";
|
import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGroup, listGroupMembers, addGroupMember, removeGroupMember, searchUsers, getFableMcpInfo, listApiKeys, createApiKey as apiCreateApiKey, revokeApiKey as apiRevokeApiKey, getVoiceStatus, getVoiceList, synthesiseSpeech, getProfile, updateProfile, consolidateProfile, clearProfileObservations, listProfileObservations, getJournalConfig, saveJournalConfig, geocodeAddress, type ApiKeyEntry, type GroupEntry, type GroupMember, type UserSearchResult, type VoiceStatusResult, type VoiceEntry, type VoiceBlendEntry, type UserProfile, type JournalConfig, type ProfileObservationEntry } from "@/api/client";
|
||||||
import { usePushStore } from "@/stores/push";
|
import { usePushStore } from "@/stores/push";
|
||||||
import type { User } from "@/types/auth";
|
import type { User } from "@/types/auth";
|
||||||
import PaginationBar from "@/components/PaginationBar.vue";
|
import PaginationBar from "@/components/PaginationBar.vue";
|
||||||
@@ -549,6 +549,26 @@ const profileSaving = ref(false)
|
|||||||
const profileSaved = ref(false)
|
const profileSaved = ref(false)
|
||||||
const consolidating = ref(false)
|
const consolidating = ref(false)
|
||||||
const clearingObs = ref(false)
|
const clearingObs = ref(false)
|
||||||
|
const observations = ref<ProfileObservationEntry[]>([])
|
||||||
|
const observationsExpanded = ref(false)
|
||||||
|
const observationsLoading = ref(false)
|
||||||
|
const observationsLoaded = ref(false)
|
||||||
|
|
||||||
|
async function toggleObservations() {
|
||||||
|
observationsExpanded.value = !observationsExpanded.value
|
||||||
|
if (observationsExpanded.value && !observationsLoaded.value) {
|
||||||
|
observationsLoading.value = true
|
||||||
|
try {
|
||||||
|
const res = await listProfileObservations()
|
||||||
|
observations.value = res.observations
|
||||||
|
observationsLoaded.value = true
|
||||||
|
} catch {
|
||||||
|
toastStore.show('Failed to load observations', 'error')
|
||||||
|
} finally {
|
||||||
|
observationsLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadProfile() {
|
async function loadProfile() {
|
||||||
try { profile.value = await getProfile() } catch { /* non-critical */ }
|
try { profile.value = await getProfile() } catch { /* non-critical */ }
|
||||||
@@ -702,6 +722,8 @@ async function clearObservations() {
|
|||||||
profile.value.learned_summary = ''
|
profile.value.learned_summary = ''
|
||||||
profile.value.observations_count = 0
|
profile.value.observations_count = 0
|
||||||
profile.value.observations_updated_at = null
|
profile.value.observations_updated_at = null
|
||||||
|
observations.value = []
|
||||||
|
observationsLoaded.value = false
|
||||||
toastStore.show('Learned data cleared')
|
toastStore.show('Learned data cleared')
|
||||||
} catch { toastStore.show('Failed to clear observations', 'error') }
|
} catch { toastStore.show('Failed to clear observations', 'error') }
|
||||||
finally { clearingObs.value = false }
|
finally { clearingObs.value = false }
|
||||||
@@ -1929,6 +1951,23 @@ function formatUserDate(iso: string): string {
|
|||||||
|
|
||||||
<div v-if="profile.learned_summary" class="learned-summary">{{ profile.learned_summary }}</div>
|
<div v-if="profile.learned_summary" class="learned-summary">{{ profile.learned_summary }}</div>
|
||||||
<div v-else class="learned-empty">No learned summary yet. Observations accumulate from journal and chat conversations.</div>
|
<div v-else class="learned-empty">No learned summary yet. Observations accumulate from journal and chat conversations.</div>
|
||||||
|
|
||||||
|
<div class="observations-panel" style="margin-top:0.75rem">
|
||||||
|
<button class="btn-secondary" @click="toggleObservations" :disabled="profile.observations_count === 0">
|
||||||
|
{{ observationsExpanded ? '▾' : '▸' }} Recent observations ({{ profile.observations_count }})
|
||||||
|
</button>
|
||||||
|
<div v-if="observationsExpanded" class="observations-list" style="margin-top:0.5rem;padding:0.5rem 0.75rem;border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-bg-elev-1)">
|
||||||
|
<div v-if="observationsLoading">Loading…</div>
|
||||||
|
<div v-else-if="observations.length === 0">No observations yet.</div>
|
||||||
|
<div v-else>
|
||||||
|
<div v-for="entry in observations" :key="entry.date" style="margin-bottom:0.75rem">
|
||||||
|
<div style="font-weight:600;font-size:0.875rem;color:var(--color-text-muted)">{{ entry.date }}</div>
|
||||||
|
<div style="white-space:pre-wrap;font-size:0.9rem">{{ entry.bullets }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="actions" style="gap:0.5rem;flex-wrap:wrap">
|
<div class="actions" style="gap:0.5rem;flex-wrap:wrap">
|
||||||
<button class="btn-secondary" @click="runConsolidate" :disabled="consolidating || profile.observations_count === 0">
|
<button class="btn-secondary" @click="runConsolidate" :disabled="consolidating || profile.observations_count === 0">
|
||||||
{{ consolidating ? 'Consolidating…' : 'Consolidate Now' }}
|
{{ consolidating ? 'Consolidating…' : 'Consolidate Now' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user