Files
zoeissleeping 90d5698e76 feat: add agent search config and system assistant defaults
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.
2026-04-27 12:05:27 -05:00

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';
}),
}
};
};