90d5698e76
Adds a config jsonb column to agents for per-agent search settings (enabled, rerank, maxResults). Types systemAssistants with rename and rerank sub-objects. Backfills missing systemAssistants keys on settings fetch. Includes generated drizzle migrations.
131 lines
4.9 KiB
TypeScript
131 lines
4.9 KiB
TypeScript
import * as schema from '~~/drizzle/schema';
|
|
|
|
type UserSettings = typeof schema.settings.$inferSelect;
|
|
type Appearance = UserSettings['appearance'];
|
|
type SystemAssistants = UserSettings['systemAssistants'];
|
|
|
|
const defaultAppearance: Appearance = {
|
|
colorScheme: 'system',
|
|
accent: 'violet',
|
|
neutral: 'zinc',
|
|
hinting: 0,
|
|
fontSize: 'md',
|
|
};
|
|
|
|
export const useUserSettings = async () => {
|
|
const settings = useState<UserSettings | null>('settings_state', () => null);
|
|
const loaded = useState('settings_loaded', () => false);
|
|
const syncing = useState('settings_syncing', () => false);
|
|
|
|
const localAppearance = useState<Appearance | null>('settings_local_appearance', () => null);
|
|
const localSystemAssistants = useState<SystemAssistants | null>('settings_local_sa', () => null);
|
|
|
|
const { refresh } = await useFetch<UserSettings>('/api/settings', {
|
|
key: 'settings_request',
|
|
immediate: !loaded.value,
|
|
onRequest() {
|
|
loaded.value = true;
|
|
},
|
|
onResponse({ response }) {
|
|
if (response.ok && response._data) {
|
|
settings.value = response._data;
|
|
localAppearance.value = { ...defaultAppearance, ...response._data.appearance };
|
|
localSystemAssistants.value = response._data.systemAssistants;
|
|
}
|
|
}
|
|
});
|
|
|
|
const applyOptimisticUpdates = (updates: { appearance?: Partial<Appearance>; systemAssistants?: Partial<SystemAssistants> }) => {
|
|
if (updates.appearance && localAppearance.value) {
|
|
localAppearance.value = { ...localAppearance.value, ...updates.appearance };
|
|
}
|
|
if (updates.systemAssistants && localSystemAssistants.value) {
|
|
localSystemAssistants.value = { ...localSystemAssistants.value, ...updates.systemAssistants };
|
|
}
|
|
if (updates.appearance && settings.value) {
|
|
settings.value = {
|
|
...settings.value,
|
|
appearance: { ...settings.value.appearance, ...updates.appearance }
|
|
};
|
|
}
|
|
if (updates.systemAssistants && settings.value) {
|
|
settings.value = {
|
|
...settings.value,
|
|
systemAssistants: { ...settings.value.systemAssistants, ...updates.systemAssistants }
|
|
};
|
|
}
|
|
};
|
|
|
|
const updateSettings = async (updates: { appearance?: Partial<Appearance>; systemAssistants?: Partial<SystemAssistants> }) => {
|
|
const original = settings.value;
|
|
const originalAppearance = localAppearance.value;
|
|
const originalSA = localSystemAssistants.value;
|
|
|
|
applyOptimisticUpdates(updates);
|
|
|
|
try {
|
|
syncing.value = true;
|
|
await $fetch(`/api/settings`, {
|
|
method: 'PATCH',
|
|
body: updates,
|
|
});
|
|
await refresh();
|
|
} catch (error) {
|
|
settings.value = original;
|
|
localAppearance.value = originalAppearance;
|
|
localSystemAssistants.value = originalSA;
|
|
throw error;
|
|
} finally {
|
|
syncing.value = false;
|
|
}
|
|
};
|
|
|
|
const effectiveAppearance = computed(() => {
|
|
return localAppearance.value || defaultAppearance;
|
|
});
|
|
|
|
const effectiveSystemAssistants = computed(() => {
|
|
return localSystemAssistants.value || {} as SystemAssistants;
|
|
});
|
|
|
|
const colorSchemeValue = computed(() => {
|
|
if (effectiveAppearance.value.colorScheme === 'system') {
|
|
if (import.meta.client) {
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
}
|
|
return 'dark';
|
|
}
|
|
return effectiveAppearance.value.colorScheme as 'dark' | 'light';
|
|
});
|
|
|
|
const effectiveSettings = computed(() => ({
|
|
appearance: effectiveAppearance.value,
|
|
systemAssistants: effectiveSystemAssistants.value,
|
|
}));
|
|
|
|
return {
|
|
settings: effectiveSettings,
|
|
rawSettings: readonly(settings),
|
|
updateSettings,
|
|
syncing: readonly(syncing),
|
|
loaded: readonly(loaded),
|
|
refresh,
|
|
accent: computed(() => effectiveAppearance.value.accent ?? 'violet'),
|
|
neutral: computed(() => effectiveAppearance.value.neutral ?? 'zinc'),
|
|
hinting: computed(() => effectiveAppearance.value.hinting ?? 0),
|
|
colorScheme: {
|
|
preference: computed(() => effectiveAppearance.value.colorScheme ?? 'system'),
|
|
value: colorSchemeValue,
|
|
class: computed(() => {
|
|
if (effectiveAppearance.value.colorScheme === 'system') {
|
|
if (import.meta.client) {
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
}
|
|
return 'dark';
|
|
}
|
|
return effectiveAppearance.value.colorScheme as 'dark' | 'light';
|
|
}),
|
|
}
|
|
};
|
|
};
|