mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 15:46:22 +00:00
fix: ensure custom model entry is always visible in model selector (#662)
The merged PR (#661) injected custom entries into filteredModels, but cmdk auto-scrolls to its first selected CommandItem, pushing the custom entry out of view. Fix by using forceMount on a separate CommandGroup and resetting scroll to top on every keystroke via requestAnimationFrame.
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
XCircle,
|
XCircle,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { type FC, useEffect, useMemo, useState } from 'react'
|
import { type FC, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { z } from 'zod/v3'
|
import { z } from 'zod/v3'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
@@ -223,6 +223,7 @@ export const NewProviderDialog: FC<NewProviderDialogProps> = ({
|
|||||||
const [testResult, setTestResult] = useState<TestResult | null>(null)
|
const [testResult, setTestResult] = useState<TestResult | null>(null)
|
||||||
const [modelPickerOpen, setModelPickerOpen] = useState(false)
|
const [modelPickerOpen, setModelPickerOpen] = useState(false)
|
||||||
const [modelSearch, setModelSearch] = useState('')
|
const [modelSearch, setModelSearch] = useState('')
|
||||||
|
const modelListRef = useRef<HTMLDivElement>(null)
|
||||||
const { supports } = useCapabilities()
|
const { supports } = useCapabilities()
|
||||||
const { baseUrl: agentServerUrl } = useAgentServerUrl()
|
const { baseUrl: agentServerUrl } = useAgentServerUrl()
|
||||||
const kimiLaunch = useKimiLaunch()
|
const kimiLaunch = useKimiLaunch()
|
||||||
@@ -305,14 +306,12 @@ export const NewProviderDialog: FC<NewProviderDialogProps> = ({
|
|||||||
[modelInfoList],
|
[modelInfoList],
|
||||||
)
|
)
|
||||||
|
|
||||||
const filteredModels = useMemo(() => {
|
const filteredModels = modelSearch
|
||||||
if (!modelSearch) return modelInfoList
|
? modelFuse.search(modelSearch).map((r) => r.item)
|
||||||
const fuzzyResults = modelFuse.search(modelSearch).map((r) => r.item)
|
: modelInfoList
|
||||||
const hasExactMatch = fuzzyResults.some((m) => m.modelId === modelSearch)
|
|
||||||
if (hasExactMatch) return fuzzyResults
|
const showCustomEntry =
|
||||||
const customEntry = { modelId: modelSearch, contextLength: 0 }
|
modelSearch && !filteredModels.some((m) => m.modelId === modelSearch)
|
||||||
return [customEntry, ...fuzzyResults]
|
|
||||||
}, [modelSearch, modelFuse, modelInfoList])
|
|
||||||
|
|
||||||
// Handle provider type change (user-initiated via Select)
|
// Handle provider type change (user-initiated via Select)
|
||||||
const handleTypeChange = (newType: ProviderType) => {
|
const handleTypeChange = (newType: ProviderType) => {
|
||||||
@@ -899,7 +898,12 @@ export const NewProviderDialog: FC<NewProviderDialogProps> = ({
|
|||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder="Search models..."
|
placeholder="Search models..."
|
||||||
value={modelSearch}
|
value={modelSearch}
|
||||||
onValueChange={setModelSearch}
|
onValueChange={(v) => {
|
||||||
|
setModelSearch(v)
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
modelListRef.current?.scrollTo(0, 0)
|
||||||
|
})
|
||||||
|
}}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' && modelSearch) {
|
if (e.key === 'Enter' && modelSearch) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -917,11 +921,36 @@ export const NewProviderDialog: FC<NewProviderDialogProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<CommandList>
|
<CommandList ref={modelListRef}>
|
||||||
<CommandEmpty>
|
<CommandEmpty>
|
||||||
No models found. Press Enter to use "
|
No models found. Press Enter to use "
|
||||||
{modelSearch}"
|
{modelSearch}"
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
|
{showCustomEntry && (
|
||||||
|
<CommandGroup forceMount>
|
||||||
|
<CommandItem
|
||||||
|
forceMount
|
||||||
|
value={`custom:${modelSearch}`}
|
||||||
|
onSelect={() => {
|
||||||
|
form.setValue('modelId', modelSearch)
|
||||||
|
track(MODEL_SELECTED_EVENT, {
|
||||||
|
provider_type: watchedType,
|
||||||
|
model_id: modelSearch,
|
||||||
|
is_custom_model: true,
|
||||||
|
})
|
||||||
|
setModelPickerOpen(false)
|
||||||
|
setModelSearch('')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="flex-1 truncate">
|
||||||
|
{modelSearch}
|
||||||
|
</span>
|
||||||
|
{field.value === modelSearch && (
|
||||||
|
<Check className="ml-2 h-4 w-4 shrink-0" />
|
||||||
|
)}
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
)}
|
||||||
{filteredModels.length > 0 && (
|
{filteredModels.length > 0 && (
|
||||||
<CommandGroup>
|
<CommandGroup>
|
||||||
{filteredModels.map((model) => (
|
{filteredModels.map((model) => (
|
||||||
|
|||||||
Reference in New Issue
Block a user