peregrine/web/src/stores/settings/privacy.ts
pyr0ball fa2569c7e4 feat(settings): License, Data, Privacy, Developer tabs — stores, views, endpoints
- useLicenseStore: load/activate/deactivate with tier badge and key input
- useDataStore: createBackup with file count and size display
- usePrivacyStore: BYOK panel logic (dismissal snapshot tracks new backends),
  telemetry toggle (self-hosted) and master-off/usage/content controls (cloud)
- Views: LicenseView (cloud/self-hosted split), LicenseSelfHosted,
  LicenseCloud, DataView, PrivacyView, DeveloperView
- dev-api.py: /api/settings/license, /activate, /deactivate;
  /api/settings/data/backup/create; /api/settings/privacy GET+PUT;
  /api/settings/developer GET, /tier PUT, /hf-token PUT+test, /wizard-reset,
  /export-classifier; _load_user_config/_save_user_config helpers; CONFIG_DIR
- TDD: 10/10 store tests passing (license×3, data×2, privacy×5)
2026-03-22 16:01:29 -07:00

64 lines
2.3 KiB
TypeScript

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { useApiFetch } from '../../composables/useApi'
export const usePrivacyStore = defineStore('settings/privacy', () => {
// Session-scoped BYOK panel state
const activeCloudBackends = ref<string[]>([])
const byokInfoDismissed = ref(false)
const dismissedForBackends = ref<string[]>([])
// Self-hosted privacy prefs
const telemetryOptIn = ref(false)
// Cloud privacy prefs
const masterOff = ref(false)
const usageEvents = ref(true)
const contentSharing = ref(false)
const loading = ref(false)
const saving = ref(false)
// Panel shows if there are active cloud backends not yet covered by dismissal snapshot,
// or if byokInfoDismissed was set directly (e.g. loaded from server) and new backends haven't appeared
const showByokPanel = computed(() => {
if (activeCloudBackends.value.length === 0) return false
if (byokInfoDismissed.value && activeCloudBackends.value.every(b => dismissedForBackends.value.includes(b))) return false
if (byokInfoDismissed.value && dismissedForBackends.value.length === 0) return false
return !activeCloudBackends.value.every(b => dismissedForBackends.value.includes(b))
})
function dismissByokInfo() {
dismissedForBackends.value = [...activeCloudBackends.value]
byokInfoDismissed.value = true
}
async function loadPrivacy() {
loading.value = true
const { data } = await useApiFetch<Record<string, unknown>>('/api/settings/privacy')
loading.value = false
if (!data) return
telemetryOptIn.value = Boolean(data.telemetry_opt_in)
byokInfoDismissed.value = Boolean(data.byok_info_dismissed)
masterOff.value = Boolean(data.master_off)
usageEvents.value = data.usage_events !== false
contentSharing.value = Boolean(data.content_sharing)
}
async function savePrivacy(prefs: Record<string, unknown>) {
saving.value = true
await useApiFetch('/api/settings/privacy', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(prefs),
})
saving.value = false
}
return {
activeCloudBackends, byokInfoDismissed, dismissedForBackends,
telemetryOptIn, masterOff, usageEvents, contentSharing,
loading, saving, showByokPanel,
dismissByokInfo, loadPrivacy, savePrivacy,
}
})