import { schema } from '#triplit/schema'; import { type Entity } from '@triplit/client'; import { computed, watch } from 'vue'; // TODO: most of this code is generated by gemini 3 flash with fixups, // but its still bad code, so I'm going to clean it up later export const useUserSettings = async () => { const { user, loggedIn } = useAuth(); const triplit = useTriplitClient(); const { accent, neutral, hinting, colorScheme } = useTheme(); const start = Date.now(); const remoteSettings = useState | null>('user:settings', () => null); const { results } = await useQuery('settings', triplit, triplit.query('settings')) console.log("fetching settings took", Date.now() - start); watch(results, (val) => { if (!val) { remoteSettings.value = null; return; } remoteSettings.value = val[0] as any; }, { immediate: true, deep: true }) // Sync remote settings to local cookies // This bridges the gap between the server state and the local CSS variable application watch(remoteSettings, (newSettings) => { if (!newSettings?.appearance) return; const { appearance } = newSettings; if (appearance.colorScheme && appearance.colorScheme !== colorScheme.preference.value) { colorScheme.preference.value = appearance.colorScheme as 'light' | 'dark' | 'system'; } if (appearance.accent && appearance.accent !== accent.value) { accent.value = appearance.accent; } if (appearance.neutral && appearance.neutral !== neutral.value) { neutral.value = appearance.neutral; } if (appearance.hinting !== undefined && String(appearance.hinting) !== hinting.value) { hinting.value = String(appearance.hinting); } }, { deep: true }); // Effective settings with defaults for backwards compatibility const settings = computed(() => { const remote = remoteSettings.value; return { appearance: { colorScheme: remote?.appearance?.colorScheme ?? colorScheme.preference.value, accent: remote?.appearance?.accent ?? accent.value, neutral: remote?.appearance?.neutral ?? neutral.value, hinting: remote?.appearance?.hinting ?? Number(hinting.value), fontSize: remote?.appearance?.fontSize ?? 'medium', }, systemAssistants: { rename: { enabled: remote?.systemAssistants?.rename?.enabled ?? false, prompt: remote?.systemAssistants?.rename?.prompt ?? null, modelId: remote?.systemAssistants?.rename?.modelId ?? null, } } }; }); /** * Update settings both locally and on the server */ const updateSettings = async (updates: { appearance?: { colorScheme?: 'light' | 'dark' | 'system'; accent?: string; neutral?: string; hinting?: number; fontSize?: string; }; systemAssistants?: { rename?: { enabled?: boolean; prompt?: string | null; modelId?: string | null; }; }; }) => { if (updates.appearance) { if (updates.appearance.colorScheme) colorScheme.preference.value = updates.appearance.colorScheme; if (updates.appearance.accent) accent.value = updates.appearance.accent; if (updates.appearance.neutral) neutral.value = updates.appearance.neutral; if (updates.appearance.hinting !== undefined) hinting.value = String(updates.appearance.hinting); } if (!loggedIn.value || !user.value?.id) return; const current = remoteSettings.value; if (!current) { await triplit.insert('settings', { userId: user.value.id, appearance: { colorScheme: colorScheme.preference.value, accent: accent.value, neutral: neutral.value, hinting: Number(hinting.value), ...(updates.appearance || {}) }, systemAssistants: { rename: { enabled: updates.systemAssistants?.rename?.enabled ?? true, prompt: updates.systemAssistants?.rename?.prompt ?? null, modelId: updates.systemAssistants?.rename?.modelId ?? null, } } }); return; } await triplit.update('settings', current.id, (s) => { if (updates.appearance) { s.appearance = { ...(s.appearance || {}), ...updates.appearance }; } if (updates.systemAssistants) { // @ts-expect-error s.systemAssistants = { ...(s.systemAssistants || {}), ...updates.systemAssistants }; } }); }; return { settings, remoteSettings, updateSettings, }; };