refactor: useUserSettings uses global state to reduce db calls
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
import '~/assets/css/reset.css';
|
import '~/assets/css/reset.css';
|
||||||
import '~/assets/css/base.css';
|
import '~/assets/css/base.css';
|
||||||
|
|
||||||
const { accent, neutral, hinting } = await useUserSettings();
|
const { accent, neutral, hinting } = useUserSettings();
|
||||||
|
|
||||||
// by default, disable hinting
|
// by default, disable hinting
|
||||||
if (Number.isNaN(Number(hinting.value))) hinting.value = '0';
|
if (Number.isNaN(Number(hinting.value))) hinting.value = '0';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const props = defineProps<{
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
mimeType: string;
|
mimeType: string;
|
||||||
status: 'pending' | 'uploaded';
|
status?: 'pending' | 'uploaded';
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
}>();
|
}>();
|
||||||
|
|||||||
@@ -93,8 +93,6 @@ const handleSubmit = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(inputValue.value);
|
|
||||||
|
|
||||||
if (inputValue.value.content.trim() || inputValue.value.fileIds.length > 0) {
|
if (inputValue.value.content.trim() || inputValue.value.fileIds.length > 0) {
|
||||||
emit('submit', inputValue.value, selectedModel.value);
|
emit('submit', inputValue.value, selectedModel.value);
|
||||||
inputValue.value = { content: '', fileIds: [] };
|
inputValue.value = { content: '', fileIds: [] };
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ const handleKeyDown = (e: KeyboardEvent) => {
|
|||||||
|
|
||||||
// 60% of the window height aka 60vh
|
// 60% of the window height aka 60vh
|
||||||
const itemsPerPage = Math.floor(((window.innerHeight * 0.6) - (inputRef.value?.parentElement?.clientHeight || 0)) / 42);
|
const itemsPerPage = Math.floor(((window.innerHeight * 0.6) - (inputRef.value?.parentElement?.clientHeight || 0)) / 42);
|
||||||
console.log(itemsPerPage);
|
|
||||||
|
|
||||||
switch (e.key) {
|
switch (e.key) {
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
|
|||||||
@@ -29,10 +29,6 @@ const toggle = () => (isOpen.value = !isOpen.value);
|
|||||||
const close = () => (isOpen.value = false);
|
const close = () => (isOpen.value = false);
|
||||||
|
|
||||||
defineExpose({ close });
|
defineExpose({ close });
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
console.log(triggerRef.value);
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -75,8 +75,6 @@ const handleFileChange = (e: Event) => {
|
|||||||
const inputFiles = (e.target! as HTMLInputElement).files;
|
const inputFiles = (e.target! as HTMLInputElement).files;
|
||||||
if (inputFiles === null) return;
|
if (inputFiles === null) return;
|
||||||
|
|
||||||
console.log(inputFiles);
|
|
||||||
|
|
||||||
rawFiles.value = [...inputFiles].map(file => ({
|
rawFiles.value = [...inputFiles].map(file => ({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { sortByReleaseDate } from '~/utils/sort';
|
import { sortByReleaseDate } from '~/utils/sort';
|
||||||
import { encryptData, decrypt, uint8ArrayToBase64, base64ToUint8Array } from '~/utils/crypto';
|
import { encryptData, decrypt, uint8ArrayToBase64, base64ToUint8Array } from '~/utils/crypto';
|
||||||
import { providerBaseUrls, type Model } from '~/types/model';
|
import { providerBaseUrls, type Model } from '~/types/model';
|
||||||
import { useSettings } from '~/composables/useSettings';
|
|
||||||
import ModelItem from './ModelItem.vue';
|
import ModelItem from './ModelItem.vue';
|
||||||
import RowVirtualizerDynamic from '../RowVirtualizerDynamic.vue';
|
import RowVirtualizerDynamic from '../RowVirtualizerDynamic.vue';
|
||||||
const triplit = useTriplitClient();
|
const triplit = useTriplitClient();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { colorScheme, settings, updateSettings } = await useUserSettings();
|
const { colorScheme, settings, updateSettings } = useUserSettings();
|
||||||
|
|
||||||
const accents = ['violet', 'volcano', 'lime', 'sky', 'coral', 'emerald', 'amber', 'rose', 'cyan', 'indigo', 'magenta'];
|
const accents = ['violet', 'volcano', 'lime', 'sky', 'coral', 'emerald', 'amber', 'rose', 'cyan', 'indigo', 'magenta'];
|
||||||
const neutrals = ['zinc', 'slate', 'obsidian'];
|
const neutrals = ['zinc', 'slate', 'obsidian'];
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
params?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
defineEmits(['navigate']);
|
defineEmits(['navigate']);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { providerIcons } from '~/utils/model-mapping';
|
|||||||
const triplit = useTriplitClient();
|
const triplit = useTriplitClient();
|
||||||
const { providers } = useModels();
|
const { providers } = useModels();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
params?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
if (providers.value === undefined) throw new Error('Providers not loaded');
|
if (providers.value === undefined) throw new Error('Providers not loaded');
|
||||||
|
|
||||||
// TODO: sometimes this code can create duplicate providers
|
// TODO: sometimes this code can create duplicate providers
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ModelWithProvider } from '~/composables/useModels';
|
import type { ModelWithProvider } from '~/composables/useModels';
|
||||||
|
|
||||||
const { providers, allModels } = await useModels();
|
const props = defineProps<{
|
||||||
const { settings, updateSettings } = await useUserSettings();
|
params?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { providers, allModels } = useModels();
|
||||||
|
const { settings, updateSettings } = useUserSettings();
|
||||||
|
|
||||||
const toggle = async (key: string) => {
|
const toggle = async (key: string) => {
|
||||||
const current = (settings.value.systemAssistants as any)[key];
|
const current = (settings.value.systemAssistants as any)[key];
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
type Theme = 'light' | 'dark' | 'system';
|
type Theme = 'light' | 'dark' | 'system';
|
||||||
|
|
||||||
const { updateSettings, settings } = await useUserSettings();
|
const { updateSettings, settings } = useUserSettings();
|
||||||
|
|
||||||
const selectTheme = (colorScheme: Theme) => {
|
const selectTheme = (colorScheme: Theme) => {
|
||||||
updateSettings({ appearance: { colorScheme } });
|
updateSettings({ appearance: { colorScheme } });
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ export const useChat = (agentId: string) => {
|
|||||||
FailedToGenerate,
|
FailedToGenerate,
|
||||||
}
|
}
|
||||||
const autoRename = async (topicId: string, prompt: string): Promise<Result<string, AutoRenameError>> => {
|
const autoRename = async (topicId: string, prompt: string): Promise<Result<string, AutoRenameError>> => {
|
||||||
const { settings } = await useUserSettings();
|
const { settings } = useUserSettings();
|
||||||
|
|
||||||
console.log(settings.value);
|
console.log(settings.value);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// // composables/useModels.ts
|
|
||||||
import type { Entity } from '@triplit/client';
|
import type { Entity } from '@triplit/client';
|
||||||
import type schema from '#triplit/schema';
|
import type schema from '#triplit/schema';
|
||||||
|
|
||||||
|
|||||||
@@ -1,112 +1,109 @@
|
|||||||
import { schema } from '#triplit/schema';
|
import { schema } from '#triplit/schema';
|
||||||
import { type Entity } from '@triplit/client';
|
import { type Entity } from '@triplit/client';
|
||||||
import { computed, watch } from 'vue';
|
import { computed, watch, ref, type Ref } from 'vue';
|
||||||
|
|
||||||
// TODO: most of this code is generated by gemini 3 flash with fixups,
|
type UserSettings = Entity<typeof schema, 'settings'>;
|
||||||
// but its still bad code, so I'm going to clean it up later
|
|
||||||
export const useUserSettings = async () => {
|
interface UserSettingsState {
|
||||||
const { user, loggedIn } = useAuth();
|
accent: Ref<string>;
|
||||||
|
neutral: Ref<string>;
|
||||||
|
hinting: Ref<string>;
|
||||||
|
colorSchemePreference: Ref<'light' | 'dark' | 'system'>;
|
||||||
|
colorSchemeClass: Ref<'light' | 'dark' | undefined>;
|
||||||
|
remoteSettings: Ref<UserSettings | null>;
|
||||||
|
initPromise: Promise<void> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserSettings = () => {
|
||||||
|
const nuxtApp = useNuxtApp();
|
||||||
const triplit = useTriplitClient();
|
const triplit = useTriplitClient();
|
||||||
|
const { user, loggedIn } = useAuth();
|
||||||
|
|
||||||
const accent = useState('theme:accent', () => 'violet');
|
if (!nuxtApp._userSettingsState) {
|
||||||
const neutral = useState('theme:neutral', () => 'zinc');
|
nuxtApp._userSettingsState = {
|
||||||
const hinting = useState('theme:hinting', () => '0');
|
accent: ref('violet'),
|
||||||
|
neutral: ref('zinc'),
|
||||||
const colorScheme: { preference: Ref<'light' | 'dark' | 'system'>; value: Ref<'light' | 'dark'>; class: Ref<'light' | 'dark' | undefined> } = {
|
hinting: ref('0'),
|
||||||
preference: useState('theme:colorScheme', () => 'system' as 'light' | 'dark' | 'system'),
|
colorSchemePreference: ref<'light' | 'dark' | 'system'>('system'),
|
||||||
value: computed(() => {
|
colorSchemeClass: ref<undefined | 'light' | 'dark'>(undefined),
|
||||||
if (colorScheme.preference.value === 'system') {
|
remoteSettings: ref<UserSettings | null>(null),
|
||||||
if (import.meta.client) {
|
initPromise: null,
|
||||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
} as UserSettingsState;
|
||||||
|
|
||||||
return prefersDark ? 'dark' : 'light';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'dark';
|
const state = nuxtApp._userSettingsState as UserSettingsState;
|
||||||
}
|
|
||||||
|
|
||||||
return colorScheme.preference.value as 'light' | 'dark';
|
const init = () => {
|
||||||
}),
|
if (state.initPromise) return state.initPromise;
|
||||||
class: ref<undefined | 'light' | 'dark'>(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
let listeningToColorScheme = false;
|
state.initPromise = (async () => {
|
||||||
const changeSystemColorScheme = (e: MediaQueryListEvent) => {
|
const { results } = await useQuery('settings', triplit, triplit.query('settings'));
|
||||||
if (colorScheme.preference.value !== 'system') return;
|
|
||||||
|
|
||||||
colorScheme.class.value = e.matches ? 'dark' : 'light';
|
|
||||||
}
|
|
||||||
watch(colorScheme.preference, () => {
|
|
||||||
if (colorScheme.preference.value === 'system') {
|
|
||||||
if (import.meta.client) {
|
|
||||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
||||||
|
|
||||||
if (!listeningToColorScheme) {
|
|
||||||
listeningToColorScheme = true;
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', changeSystemColorScheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
colorScheme.class.value = prefersDark ? 'dark' : 'light';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listeningToColorScheme) {
|
|
||||||
listeningToColorScheme = false;
|
|
||||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', changeSystemColorScheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
// fallback to dark on the server
|
|
||||||
colorScheme.class.value = 'dark';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
colorScheme.class.value = colorScheme.preference.value as 'light' | 'dark';
|
|
||||||
}, { immediate: true });
|
|
||||||
|
|
||||||
const start = Date.now();
|
|
||||||
const remoteSettings = useState<Entity<typeof schema, 'settings'> | null>('user:settings', () => null);
|
|
||||||
const { results } = await useQuery('settings', triplit, triplit.query('settings'))
|
|
||||||
console.log("fetching settings took", Date.now() - start);
|
|
||||||
|
|
||||||
watch(results, (val) => {
|
watch(results, (val) => {
|
||||||
if (!val) {
|
if (val && val.length > 0) {
|
||||||
remoteSettings.value = null;
|
state.remoteSettings.value = val[0] as UserSettings;
|
||||||
return;
|
} else {
|
||||||
|
state.remoteSettings.value = null;
|
||||||
}
|
}
|
||||||
|
}, { immediate: true, deep: true });
|
||||||
|
|
||||||
remoteSettings.value = val[0] as any;
|
watch(state.remoteSettings, (newSettings) => {
|
||||||
}, { immediate: true, deep: true })
|
|
||||||
|
|
||||||
watch(remoteSettings, (newSettings) => {
|
|
||||||
if (!newSettings?.appearance) return;
|
if (!newSettings?.appearance) return;
|
||||||
const { appearance } = newSettings;
|
const { appearance } = newSettings;
|
||||||
|
|
||||||
if (appearance.colorScheme) {
|
if (appearance.colorScheme) {
|
||||||
colorScheme.preference.value = appearance.colorScheme as 'light' | 'dark' | 'system';
|
state.colorSchemePreference.value = appearance.colorScheme as 'light' | 'dark' | 'system';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appearance.accent) {
|
if (appearance.accent) {
|
||||||
accent.value = appearance.accent;
|
state.accent.value = appearance.accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appearance.neutral) {
|
if (appearance.neutral) {
|
||||||
neutral.value = appearance.neutral;
|
state.neutral.value = appearance.neutral;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appearance.hinting !== undefined) {
|
if (appearance.hinting !== undefined) {
|
||||||
hinting.value = String(appearance.hinting);
|
state.hinting.value = String(appearance.hinting);
|
||||||
}
|
}
|
||||||
}, { immediate: true, deep: true });
|
}, { immediate: true, deep: true });
|
||||||
|
|
||||||
// Effective settings with defaults for backwards compatibility
|
if (import.meta.client) {
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const update = () => {
|
||||||
|
if (state.colorSchemePreference.value === 'system') {
|
||||||
|
state.colorSchemeClass.value = mediaQuery.matches ? 'dark' : 'light';
|
||||||
|
} else {
|
||||||
|
state.colorSchemeClass.value = state.colorSchemePreference.value as 'dark' | 'light';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mediaQuery.addEventListener('change', update);
|
||||||
|
watch(state.colorSchemePreference, update, { immediate: true });
|
||||||
|
} else {
|
||||||
|
watch(state.colorSchemePreference, (pref) => {
|
||||||
|
state.colorSchemeClass.value = pref === 'system' ? 'dark' : (pref as 'dark' | 'light');
|
||||||
|
}, { immediate: true });
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return state.initPromise;
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorSchemeValue = computed(() => {
|
||||||
|
if (state.colorSchemePreference.value === 'system') {
|
||||||
|
if (import.meta.client) {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
return 'dark';
|
||||||
|
}
|
||||||
|
return state.colorSchemePreference.value as 'dark' | 'light';
|
||||||
|
});
|
||||||
|
|
||||||
const settings = computed(() => {
|
const settings = computed(() => {
|
||||||
const remote = remoteSettings.value;
|
const remote = state.remoteSettings.value;
|
||||||
return {
|
return {
|
||||||
appearance: {
|
appearance: {
|
||||||
colorScheme: remote?.appearance?.colorScheme ?? colorScheme.preference.value,
|
colorScheme: remote?.appearance?.colorScheme ?? state.colorSchemePreference.value,
|
||||||
accent: remote?.appearance?.accent ?? accent.value,
|
accent: remote?.appearance?.accent ?? state.accent.value,
|
||||||
neutral: remote?.appearance?.neutral ?? neutral.value,
|
neutral: remote?.appearance?.neutral ?? state.neutral.value,
|
||||||
hinting: remote?.appearance?.hinting ?? Number(hinting.value),
|
hinting: remote?.appearance?.hinting ?? Number(state.hinting.value),
|
||||||
fontSize: remote?.appearance?.fontSize ?? 'medium',
|
fontSize: remote?.appearance?.fontSize ?? 'medium',
|
||||||
},
|
},
|
||||||
systemAssistants: {
|
systemAssistants: {
|
||||||
@@ -119,9 +116,6 @@ export const useUserSettings = async () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Update settings both locally and on the server
|
|
||||||
*/
|
|
||||||
const updateSettings = async (updates: {
|
const updateSettings = async (updates: {
|
||||||
appearance?: {
|
appearance?: {
|
||||||
colorScheme?: 'light' | 'dark' | 'system';
|
colorScheme?: 'light' | 'dark' | 'system';
|
||||||
@@ -139,23 +133,23 @@ export const useUserSettings = async () => {
|
|||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
if (updates.appearance) {
|
if (updates.appearance) {
|
||||||
if (updates.appearance.colorScheme) colorScheme.preference.value = updates.appearance.colorScheme;
|
if (updates.appearance.colorScheme) state.colorSchemePreference.value = updates.appearance.colorScheme;
|
||||||
if (updates.appearance.accent) accent.value = updates.appearance.accent;
|
if (updates.appearance.accent) state.accent.value = updates.appearance.accent;
|
||||||
if (updates.appearance.neutral) neutral.value = updates.appearance.neutral;
|
if (updates.appearance.neutral) state.neutral.value = updates.appearance.neutral;
|
||||||
if (updates.appearance.hinting !== undefined) hinting.value = String(updates.appearance.hinting);
|
if (updates.appearance.hinting !== undefined) state.hinting.value = String(updates.appearance.hinting);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!loggedIn.value || !user.value?.id) return;
|
if (!loggedIn.value || !user.value?.id) return;
|
||||||
|
|
||||||
const current = remoteSettings.value;
|
const current = state.remoteSettings.value;
|
||||||
if (!current) {
|
if (!current) {
|
||||||
await triplit.insert('settings', {
|
await triplit.insert('settings', {
|
||||||
userId: user.value.id,
|
userId: user.value.id,
|
||||||
appearance: {
|
appearance: {
|
||||||
colorScheme: colorScheme.preference.value,
|
colorScheme: state.colorSchemePreference.value,
|
||||||
accent: accent.value,
|
accent: state.accent.value,
|
||||||
neutral: neutral.value,
|
neutral: state.neutral.value,
|
||||||
hinting: Number(hinting.value),
|
hinting: Number(state.hinting.value),
|
||||||
...(updates.appearance || {})
|
...(updates.appearance || {})
|
||||||
},
|
},
|
||||||
systemAssistants: {
|
systemAssistants: {
|
||||||
@@ -187,12 +181,17 @@ export const useUserSettings = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
init,
|
||||||
settings,
|
settings,
|
||||||
remoteSettings,
|
remoteSettings: state.remoteSettings,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
accent,
|
accent: state.accent,
|
||||||
neutral,
|
neutral: state.neutral,
|
||||||
hinting,
|
hinting: state.hinting,
|
||||||
colorScheme
|
colorScheme: {
|
||||||
|
preference: state.colorSchemePreference,
|
||||||
|
value: colorSchemeValue,
|
||||||
|
class: state.colorSchemeClass
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// TODO: when a user changes the color scheme and then signs in/up, the color
|
// TODO: when a user changes the color scheme and then signs in/up, the color
|
||||||
// scheme will not be updated in the user's settings.
|
// scheme will not be updated in the user's settings.
|
||||||
const { colorScheme } = await useUserSettings();
|
const { colorScheme } = useUserSettings();
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Dialog from '~/components/Dialog/index.vue';
|
import Dialog from '~/components/Dialog/index.vue';
|
||||||
|
|
||||||
const { colorScheme } = await useUserSettings();
|
const { colorScheme } = useUserSettings();
|
||||||
const { toggle: toggleSidebar } = useSidebar();
|
const { toggle: toggleSidebar } = useSidebar();
|
||||||
const { addShortcut } = useKeyboardShortcuts();
|
const { addShortcut } = useKeyboardShortcuts();
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export default defineNuxtPlugin(async () => {
|
|||||||
// Warm up the caches
|
// Warm up the caches
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
useAgents().init(),
|
useAgents().init(),
|
||||||
useModels().init()
|
useModels().init(),
|
||||||
|
useUserSettings().init()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log('Global data ready');
|
console.log('Global data ready');
|
||||||
|
|||||||
Reference in New Issue
Block a user