@@ -7,14 +7,14 @@ import React, {
77 forwardRef ,
88} from "react" ;
99import { cn } from "@/common/lib/utils" ;
10- import { Star } from "lucide-react" ;
10+ import { Settings , Star } from "lucide-react" ;
1111import { TooltipWrapper , Tooltip } from "./Tooltip" ;
12+ import { useSettings } from "@/browser/contexts/SettingsContext" ;
1213
1314interface ModelSelectorProps {
1415 value : string ;
1516 onChange : ( value : string ) => void ;
1617 recentModels : string [ ] ;
17- onRemoveModel ?: ( model : string ) => void ;
1818 onComplete ?: ( ) => void ;
1919 defaultModel ?: string | null ;
2020 onSetDefaultModel ?: ( model : string ) => void ;
@@ -25,10 +25,8 @@ export interface ModelSelectorRef {
2525}
2626
2727export const ModelSelector = forwardRef < ModelSelectorRef , ModelSelectorProps > (
28- (
29- { value, onChange, recentModels, onRemoveModel, onComplete, defaultModel, onSetDefaultModel } ,
30- ref
31- ) => {
28+ ( { value, onChange, recentModels, onComplete, defaultModel, onSetDefaultModel } , ref ) => {
29+ const { open : openSettings } = useSettings ( ) ;
3230 const [ isEditing , setIsEditing ] = useState ( false ) ;
3331 const [ inputValue , setInputValue ] = useState ( value ) ;
3432 const [ error , setError ] = useState < string | null > ( null ) ;
@@ -83,22 +81,14 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
8381 ) . sort ( ) ;
8482
8583 const handleSave = ( ) => {
86- // If an item is highlighted, use that instead of inputValue
87- const valueToSave =
88- highlightedIndex >= 0 && highlightedIndex < filteredModels . length
89- ? filteredModels [ highlightedIndex ]
90- : inputValue . trim ( ) ;
91-
92- if ( ! valueToSave ) {
93- setError ( "Model cannot be empty" ) ;
84+ // No matches - do nothing, let user keep typing or cancel
85+ if ( filteredModels . length === 0 ) {
9486 return ;
9587 }
9688
97- // Basic validation: should have format "provider:model" or be an abbreviation
98- if ( ! valueToSave . includes ( ":" ) && valueToSave . length < 3 ) {
99- setError ( "Invalid model format" ) ;
100- return ;
101- }
89+ // Use highlighted item, or first item if none highlighted
90+ const selectedIndex = highlightedIndex >= 0 ? highlightedIndex : 0 ;
91+ const valueToSave = filteredModels [ selectedIndex ] ;
10292
10393 onChange ( valueToSave ) ;
10494 setIsEditing ( false ) ;
@@ -113,9 +103,11 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
113103 handleCancel ( ) ;
114104 } else if ( e . key === "Enter" ) {
115105 e . preventDefault ( ) ;
116- handleSave ( ) ;
117- // Focus the main ChatInput after selecting a model
118- onComplete ?.( ) ;
106+ // Only call onComplete if save succeeded (had matches)
107+ if ( filteredModels . length > 0 ) {
108+ handleSave ( ) ;
109+ onComplete ?.( ) ;
110+ }
119111 } else if ( e . key === "Tab" ) {
120112 e . preventDefault ( ) ;
121113 // Tab auto-completes the highlighted item without closing
@@ -159,22 +151,6 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
159151 setShowDropdown ( false ) ;
160152 } ;
161153
162- const handleRemoveModel = useCallback (
163- ( model : string , event : React . MouseEvent < HTMLButtonElement > ) => {
164- event . preventDefault ( ) ;
165- event . stopPropagation ( ) ;
166- if ( ! onRemoveModel ) {
167- return ;
168- }
169- onRemoveModel ( model ) ;
170- setHighlightedIndex ( - 1 ) ;
171- if ( inputValue === model ) {
172- setInputValue ( "" ) ;
173- }
174- } ,
175- [ inputValue , onRemoveModel ]
176- ) ;
177-
178154 const handleClick = useCallback ( ( ) => {
179155 setIsEditing ( true ) ;
180156 setInputValue ( "" ) ; // Clear input to show all models
@@ -222,6 +198,19 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
222198 >
223199 { value }
224200 </ div >
201+ < TooltipWrapper inline >
202+ < button
203+ type = "button"
204+ onClick = { ( ) => openSettings ( "models" ) }
205+ className = "text-muted-light hover:text-foreground flex items-center justify-center rounded-sm p-0.5 transition-colors duration-150"
206+ aria-label = "Manage models"
207+ >
208+ < Settings className = "h-3 w-3" />
209+ </ button >
210+ < Tooltip className = "tooltip" align = "center" >
211+ Manage models
212+ </ Tooltip >
213+ </ TooltipWrapper >
225214 </ div >
226215 ) ;
227216 }
@@ -241,24 +230,28 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
241230 < div className = "text-danger-soft font-monospace mt-0.5 text-[9px]" > { error } </ div >
242231 ) }
243232 </ div >
244- { showDropdown && filteredModels . length > 0 && (
233+ { showDropdown && (
245234 < div className = "bg-separator border-border-light absolute bottom-full left-0 z-[1000] mb-1 max-h-[200px] min-w-80 overflow-y-auto rounded border shadow-[0_4px_12px_rgba(0,0,0,0.3)]" >
246- { filteredModels . map ( ( model , index ) => (
247- < div
248- key = { model }
249- ref = { ( el ) => ( dropdownItemRefs . current [ index ] = el ) }
250- className = { cn (
251- "text-[11px] font-monospace py-1.5 px-2.5 cursor-pointer transition-colors duration-100" ,
252- "first:rounded-t last:rounded-b" ,
253- index === highlightedIndex
254- ? "text-foreground bg-hover"
255- : "text-light bg-transparent hover:bg-hover hover:text-foreground"
256- ) }
257- onClick = { ( ) => handleSelectModel ( model ) }
258- >
259- < div className = "grid w-full grid-cols-[1fr_48px] items-center gap-2" >
260- < span className = "min-w-0 truncate" > { model } </ span >
261- < div className = "grid w-[48px] grid-cols-[22px_22px] justify-items-center gap-1" >
235+ { filteredModels . length === 0 ? (
236+ < div className = "text-muted-light font-monospace px-2.5 py-1.5 text-[11px]" >
237+ No matching models
238+ </ div >
239+ ) : (
240+ filteredModels . map ( ( model , index ) => (
241+ < div
242+ key = { model }
243+ ref = { ( el ) => ( dropdownItemRefs . current [ index ] = el ) }
244+ className = { cn (
245+ "text-[11px] font-monospace py-1.5 px-2.5 cursor-pointer transition-colors duration-100" ,
246+ "first:rounded-t last:rounded-b" ,
247+ index === highlightedIndex
248+ ? "text-foreground bg-hover"
249+ : "text-light bg-transparent hover:bg-hover hover:text-foreground"
250+ ) }
251+ onClick = { ( ) => handleSelectModel ( model ) }
252+ >
253+ < div className = "grid w-full grid-cols-[1fr_24px] items-center gap-2" >
254+ < span className = "min-w-0 truncate" > { model } </ span >
262255 { onSetDefaultModel && (
263256 < TooltipWrapper inline >
264257 < button
@@ -287,21 +280,10 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
287280 </ Tooltip >
288281 </ TooltipWrapper >
289282 ) }
290- { onRemoveModel && defaultModel !== model && (
291- < button
292- type = "button"
293- onMouseDown = { ( e ) => e . preventDefault ( ) }
294- onClick = { ( event ) => handleRemoveModel ( model , event ) }
295- className = "text-muted-light border-border-light/40 hover:border-danger-soft/60 hover:text-danger-soft rounded-sm border px-1 py-0.5 text-[9px] font-semibold tracking-wide uppercase transition-colors duration-150"
296- aria-label = { `Remove ${ model } from recent models` }
297- >
298- ×
299- </ button >
300- ) }
301283 </ div >
302284 </ div >
303- </ div >
304- ) ) }
285+ ) )
286+ ) }
305287 </ div >
306288 ) }
307289 </ div >
0 commit comments