diff --git a/TODO.md b/TODO.md index 022be44..d1d14ca 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,7 @@ - [x] Standardize on one icon set rather than 4 (lmao) - [-] Make dropdowns a singleton component (work in progress) - [ ] Make the sidebar better :kekdoggo:. It's annoying to manage across routes -- [ ] Make topics manually renameable +- [x] Make topics manually renameable - [ ] Improve latex rendering, make single line equations work _well_, and likely make it configurable - [x] Implement appearence settings - [x] Accent colors diff --git a/app/components/Dialog/Settings.vue b/app/components/Dialog/Settings.vue index 62b1fcf..00b47e1 100644 --- a/app/components/Dialog/Settings.vue +++ b/app/components/Dialog/Settings.vue @@ -32,19 +32,24 @@ const PAGES_CONFIG = { }, } as const; -const { currentPage, pageParams, setPage, close } = useSettings(); +const props = defineProps<{ + page: keyof typeof PAGES_CONFIG; + params?: string; +}>(); + +const { close, setOptions } = useDialog(); const runtimePage = computed(() => { // 1. Get the base config (e.g., 'providers' or 'general') - const config = PAGES_CONFIG[currentPage.value as keyof typeof PAGES_CONFIG] || PAGES_CONFIG.general; + const config = PAGES_CONFIG[props.page as keyof typeof PAGES_CONFIG] || PAGES_CONFIG.general; // 2. Determine the actual component to show let component = config.component; let label = config.label as string; - if (currentPage.value === 'providers' && pageParams.value.length > 0) { + if (props.page === 'providers' && props.params) { component = AIServiceProvider; - const providerId = pageParams.value[0]; + const providerId = props.params; const provider = providers.value!.find(p => p.id === providerId); label = provider ? provider.name : 'Unknown Provider'; } @@ -53,13 +58,13 @@ const runtimePage = computed(() => { ...config, label, component, - params: pageParams.value, + params: props.params, } as { label: string; icon: string; component: Component; sidebar?: Component; - params: string[]; + params: string; }; }); @@ -68,10 +73,11 @@ const runtimePage = computed(() => {
diff --git a/app/components/Dialog/index.vue b/app/components/Dialog/index.vue index 1166b18..ac65a3c 100644 --- a/app/components/Dialog/index.vue +++ b/app/components/Dialog/index.vue @@ -41,7 +41,7 @@ onUnmounted(() => {
- + diff --git a/app/components/ModelSelector.vue b/app/components/ModelSelector.vue index 0128b18..b7c364b 100644 --- a/app/components/ModelSelector.vue +++ b/app/components/ModelSelector.vue @@ -5,25 +5,51 @@ import type schema from '#triplit/schema'; import type { ModelWithProvider, ProviderWithModels } from '~/composables/useModels'; import { sortByReleaseDate } from '~/utils/sort'; -const { setPage } = useSettings(); +const { openDialog } = useDialog(); const { allModels } = await useModels(); const props = defineProps<{ providers: ProviderWithModels[]; }>(); -const isOpen = ref(false); +const modelSelectorState = reactive({ + open: false, + direction: 'up', + x: 0, + y: 0, +}); const selectedModel = defineModel(); -const dropdownRef = ref(null); +const dropdownContentRef = ref(null); const dropdownButton = ref(null); const searchInputRef = ref(null); -const dropdownDirection = ref<'up' | 'down'>('up'); const dropdownMaxHeight = ref(undefined); const navigatingWithKeyboard = ref(false); const focusedOptionId = ref(null); +const toggleDropdown = () => { + if (modelSelectorState.open) { + closeDropdown(); + return; + } + + const buttonRect = dropdownButton.value?.getBoundingClientRect(); + if (!buttonRect) return; + modelSelectorState.x = buttonRect.left; + if (modelSelectorState.direction === 'up') { + const pageHeight = document.body.scrollHeight; + modelSelectorState.y = (pageHeight - buttonRect.top) + 6; + } else { + modelSelectorState.y = buttonRect.bottom - 6; + } + modelSelectorState.open = true; + + setTimeout(() => { + document.body.addEventListener('click', closeDropdown); + }); +} + const findContainer = (startingElement: HTMLElement): HTMLElement | null => { let container: HTMLElement | null = startingElement; while (container) { @@ -48,10 +74,10 @@ const calculateDropdownPosition = () => { const spaceBelow = containerRect.bottom - buttonRect.bottom; if (spaceAbove > spaceBelow) { - dropdownDirection.value = 'up'; + modelSelectorState.direction = 'up'; dropdownMaxHeight.value = Math.min(spaceAbove - 20, 460); } else { - dropdownDirection.value = 'down'; + modelSelectorState.direction = 'down'; dropdownMaxHeight.value = Math.min(spaceBelow - 20, 460); } }; @@ -119,7 +145,7 @@ watch(() => props.providers, () => { } }) -watch(isOpen, (open) => { +watch(() => modelSelectorState.open, (open) => { if (open) { calculateDropdownPosition(); nextTick(() => { @@ -132,7 +158,9 @@ watch(isOpen, (open) => { }); const closeDropdown = () => { - isOpen.value = false; + document.body.removeEventListener('click', closeDropdown); + + modelSelectorState.open = false; navigatingWithKeyboard.value = false; }; @@ -186,88 +214,96 @@ const handleSearchKeyDown = (event: KeyboardEvent) => { break; } }; - -useClickOutside(dropdownRef, closeDropdown); diff --git a/app/components/Settings/AIServiceProvider.vue b/app/components/Settings/AIServiceProvider.vue index 53feb7b..d93dac9 100644 --- a/app/components/Settings/AIServiceProvider.vue +++ b/app/components/Settings/AIServiceProvider.vue @@ -7,14 +7,17 @@ import ModelItem from './ModelItem.vue'; import RowVirtualizerDynamic from '../RowVirtualizerDynamic.vue'; const triplit = useTriplitClient(); -const { pageParams } = useSettings(); +const props = defineProps<{ + params?: string; +}>(); + const { providers } = useModels(); const scrollContainerRef = ref(null); const provider = computed(() => { - if (pageParams.value.length === 0) return null; - return providers.value!.find(p => p.id === pageParams.value[0]); + if (props.params === undefined) return null; + return providers.value!.find(p => p.id === props.params); }); watch(provider, async () => { @@ -28,8 +31,8 @@ const apiKey = ref(''); const apiProxyUrl = ref(provider.value?.config.apiProxyUrl ?? ''); const modelSearch = ref(''); -watch(pageParams, () => { - if (pageParams.value.length === 0) return; +watch(() => props.params, () => { + if (props.params === undefined) return; console.log(scrollContainerRef.value); apiKey.value = provider.value?.config.apiKey ?? ''; apiProxyUrl.value = provider.value?.config.apiProxyUrl ?? ''; diff --git a/app/components/Settings/ProviderSidebar.vue b/app/components/Settings/ProviderSidebar.vue index 166c3f9..36508b8 100644 --- a/app/components/Settings/ProviderSidebar.vue +++ b/app/components/Settings/ProviderSidebar.vue @@ -1,8 +1,10 @@ @@ -22,7 +24,7 @@ defineEmits(['navigate']);
Enabled Providers