- Accent colour picker: 8 presets + custom colour picker, live CSS var update - Glass intensity slider: Subtle/Default/Strong blur and opacity presets - Font size settings: Compact 13px / Default 15px / Large 17px - Content type visibility: toggle each of 11 content tabs - Keyboard shortcut map: view and rebind all shortcuts, conflict detection - Browser push notifications: opt-in for generation complete events - Auto-archive: 7/30/90/never day threshold for old conversations - Full data export: JSON archive of all localStorage aiui-* keys - Data wipe: two-step confirm, optional API key vault clearing - Default conversation settings: model, web search, token counts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
161 lines
4.3 KiB
TypeScript
161 lines
4.3 KiB
TypeScript
import { ref, computed, watch } from 'vue'
|
|
import { defineStore } from 'pinia'
|
|
import type { ContentTab } from '@/composables/contentFiltering'
|
|
|
|
const STORAGE_KEY = 'aiui-settings'
|
|
|
|
export interface AppSettings {
|
|
// M15.1 — Accent colour
|
|
accentColor: string
|
|
// M15.2 — Glass intensity
|
|
glassIntensity: 'subtle' | 'default' | 'strong'
|
|
// M15.3 — Font size
|
|
fontSize: 'compact' | 'default' | 'large'
|
|
// M15.4 — Content type visibility
|
|
hiddenContentTabs: ContentTab[]
|
|
// M15.5 — Keyboard shortcuts
|
|
shortcuts: Record<string, string>
|
|
// M15.6 — Push notifications
|
|
notificationsEnabled: boolean
|
|
// M15.7 — Auto-archive
|
|
autoArchiveDays: number // 0 = never, 7/30/90
|
|
// M15.10 — Default conversation settings
|
|
defaultModel: string
|
|
defaultPersonaId: string
|
|
defaultWebSearch: boolean
|
|
defaultShowTokens: boolean
|
|
}
|
|
|
|
const DEFAULT_SETTINGS: AppSettings = {
|
|
accentColor: '#F7931A',
|
|
glassIntensity: 'default',
|
|
fontSize: 'default',
|
|
hiddenContentTabs: [],
|
|
shortcuts: {
|
|
'send-message': 'Enter',
|
|
'new-line': 'Shift+Enter',
|
|
'search': 'Cmd+F',
|
|
'new-chat': 'Cmd+N',
|
|
'settings': 'Cmd+,',
|
|
'close-panel': 'Escape',
|
|
},
|
|
notificationsEnabled: false,
|
|
autoArchiveDays: 0,
|
|
defaultModel: '',
|
|
defaultPersonaId: '',
|
|
defaultWebSearch: false,
|
|
defaultShowTokens: false,
|
|
}
|
|
|
|
export const useSettingsStore = defineStore('settings', () => {
|
|
const settings = ref<AppSettings>(loadSettings())
|
|
|
|
function loadSettings(): AppSettings {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY)
|
|
if (stored) return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) }
|
|
} catch { /* ignore */ }
|
|
return { ...DEFAULT_SETTINGS }
|
|
}
|
|
|
|
function save() {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings.value))
|
|
}
|
|
|
|
// Apply CSS variables when settings change
|
|
function applyCssVars() {
|
|
const root = document.documentElement
|
|
|
|
// Accent colour
|
|
root.style.setProperty('--color-accent', settings.value.accentColor)
|
|
|
|
// Glass intensity
|
|
const glass = {
|
|
subtle: { blur: '12px', opacity: '0.25' },
|
|
default: { blur: '18px', opacity: '0.35' },
|
|
strong: { blur: '28px', opacity: '0.50' },
|
|
}[settings.value.glassIntensity]
|
|
root.style.setProperty('--glass-blur', glass.blur)
|
|
root.style.setProperty('--glass-opacity', glass.opacity)
|
|
|
|
// Font size
|
|
const sizes = { compact: '13px', default: '15px', large: '17px' }
|
|
root.style.setProperty('--font-size-base', sizes[settings.value.fontSize])
|
|
}
|
|
|
|
// Watch for changes and persist + apply
|
|
watch(settings, () => {
|
|
save()
|
|
applyCssVars()
|
|
}, { deep: true })
|
|
|
|
// Apply on init
|
|
applyCssVars()
|
|
|
|
const accentColor = computed({
|
|
get: () => settings.value.accentColor,
|
|
set: (v) => { settings.value.accentColor = v },
|
|
})
|
|
|
|
const glassIntensity = computed({
|
|
get: () => settings.value.glassIntensity,
|
|
set: (v) => { settings.value.glassIntensity = v },
|
|
})
|
|
|
|
const fontSize = computed({
|
|
get: () => settings.value.fontSize,
|
|
set: (v) => { settings.value.fontSize = v },
|
|
})
|
|
|
|
const hiddenContentTabs = computed({
|
|
get: () => settings.value.hiddenContentTabs,
|
|
set: (v) => { settings.value.hiddenContentTabs = v },
|
|
})
|
|
|
|
const notificationsEnabled = computed({
|
|
get: () => settings.value.notificationsEnabled,
|
|
set: (v) => { settings.value.notificationsEnabled = v },
|
|
})
|
|
|
|
const autoArchiveDays = computed({
|
|
get: () => settings.value.autoArchiveDays,
|
|
set: (v) => { settings.value.autoArchiveDays = v },
|
|
})
|
|
|
|
function isTabVisible(tab: ContentTab): boolean {
|
|
return !settings.value.hiddenContentTabs.includes(tab)
|
|
}
|
|
|
|
function toggleTabVisibility(tab: ContentTab) {
|
|
const idx = settings.value.hiddenContentTabs.indexOf(tab)
|
|
if (idx >= 0) {
|
|
settings.value.hiddenContentTabs.splice(idx, 1)
|
|
} else {
|
|
settings.value.hiddenContentTabs.push(tab)
|
|
}
|
|
}
|
|
|
|
function setShortcut(action: string, binding: string) {
|
|
settings.value.shortcuts[action] = binding
|
|
}
|
|
|
|
function resetSettings() {
|
|
settings.value = { ...DEFAULT_SETTINGS }
|
|
}
|
|
|
|
return {
|
|
settings,
|
|
accentColor,
|
|
glassIntensity,
|
|
fontSize,
|
|
hiddenContentTabs,
|
|
notificationsEnabled,
|
|
autoArchiveDays,
|
|
isTabVisible,
|
|
toggleTabVisibility,
|
|
setShortcut,
|
|
resetSettings,
|
|
applyCssVars,
|
|
}
|
|
})
|