@@ -17,6 +17,8 @@ import ProvidersAvatar from '@/containers/ProvidersAvatar'
1717import { Fzf } from 'fzf'
1818import { localStorageKey } from '@/constants/localStorage'
1919import { useTranslation } from '@/i18n/react-i18next-compat'
20+ import { useFavoriteModel } from '@/hooks/useFavoriteModel'
21+ import { predefinedProviders } from '@/consts/providers'
2022
2123type DropdownModelProviderProps = {
2224 model ?: ThreadModel
@@ -69,6 +71,7 @@ const DropdownModelProvider = ({
6971 const { updateCurrentThreadModel } = useThreads ( )
7072 const navigate = useNavigate ( )
7173 const { t } = useTranslation ( )
74+ const { favoriteModels } = useFavoriteModel ( )
7275
7376 // Search state
7477 const [ open , setOpen ] = useState ( false )
@@ -151,9 +154,13 @@ const DropdownModelProvider = ({
151154
152155 provider . models . forEach ( ( modelItem ) => {
153156 // Skip models that require API key but don't have one (except llamacpp)
154- if ( provider . provider !== 'llamacpp' && ! provider . api_key ?. length ) {
155- return
156- }
157+ if (
158+ provider &&
159+ predefinedProviders . some ( ( e ) =>
160+ e . provider . includes ( provider . provider )
161+ ) && provider . provider !== 'llamacpp' && ! provider . api_key ?. length
162+ )
163+ return
157164
158165 const capabilities = modelItem . capabilities || [ ]
159166 const capabilitiesString = capabilities . join ( ' ' )
@@ -182,6 +189,13 @@ const DropdownModelProvider = ({
182189 } )
183190 } , [ searchableItems ] )
184191
192+ // Get favorite models that are currently available
193+ const favoriteItems = useMemo ( ( ) => {
194+ return searchableItems . filter ( ( item ) =>
195+ favoriteModels . some ( ( fav ) => fav . id === item . model . id )
196+ )
197+ } , [ searchableItems , favoriteModels ] )
198+
185199 // Filter models based on search value
186200 const filteredItems = useMemo ( ( ) => {
187201 if ( ! searchValue ) return searchableItems
@@ -202,7 +216,7 @@ const DropdownModelProvider = ({
202216 } )
203217 } , [ searchableItems , searchValue , fzfInstance ] )
204218
205- // Group filtered items by provider
219+ // Group filtered items by provider, excluding favorites when not searching
206220 const groupedItems = useMemo ( ( ) => {
207221 const groups : Record < string , SearchableModel [ ] > = { }
208222
@@ -221,11 +235,16 @@ const DropdownModelProvider = ({
221235 if ( ! groups [ providerKey ] ) {
222236 groups [ providerKey ] = [ ]
223237 }
238+
239+ // When not searching, exclude favorite models from regular provider sections
240+ const isFavorite = favoriteModels . some ( ( fav ) => fav . id === item . model . id )
241+ if ( ! searchValue && isFavorite ) return // Skip adding this item to regular provider section
242+
224243 groups [ providerKey ] . push ( item )
225244 } )
226245
227246 return groups
228- } , [ filteredItems , providers , searchValue ] )
247+ } , [ filteredItems , providers , searchValue , favoriteModels ] )
229248
230249 const handleSelect = useCallback (
231250 ( searchableModel : SearchableModel ) => {
@@ -330,6 +349,64 @@ const DropdownModelProvider = ({
330349 </ div >
331350 ) : (
332351 < div className = "py-1" >
352+ { /* Favorites section - only show when not searching */ }
353+ { ! searchValue && favoriteItems . length > 0 && (
354+ < div className = "bg-main-view-fg/2 backdrop-blur-2xl rounded-sm my-1.5 mx-1.5" >
355+ { /* Favorites header */ }
356+ < div className = "flex items-center gap-1.5 px-2 py-1" >
357+ < span className = "text-sm font-medium text-main-view-fg/80" >
358+ { t ( 'common:favorites' ) }
359+ </ span >
360+ </ div >
361+
362+ { /* Favorite models */ }
363+ { favoriteItems . map ( ( searchableModel ) => {
364+ const isSelected =
365+ selectedModel ?. id === searchableModel . model . id &&
366+ selectedProvider === searchableModel . provider . provider
367+ const capabilities =
368+ searchableModel . model . capabilities || [ ]
369+
370+ return (
371+ < div
372+ key = { `fav-${ searchableModel . value } ` }
373+ title = { searchableModel . model . id }
374+ onClick = { ( ) => handleSelect ( searchableModel ) }
375+ className = { cn (
376+ 'mx-1 mb-1 px-2 py-1.5 rounded-sm cursor-pointer flex items-center gap-2 transition-all duration-200' ,
377+ 'hover:bg-main-view-fg/4' ,
378+ isSelected &&
379+ 'bg-main-view-fg/8 hover:bg-main-view-fg/8'
380+ ) }
381+ >
382+ < div className = "flex items-center gap-1 flex-1 min-w-0" >
383+ < div className = "shrink-0 -ml-1" >
384+ < ProvidersAvatar
385+ provider = { searchableModel . provider }
386+ />
387+ </ div >
388+ < span className = "truncate text-main-view-fg/80 text-sm" >
389+ { searchableModel . model . id }
390+ </ span >
391+ < div className = "flex-1" > </ div >
392+ { capabilities . length > 0 && (
393+ < div className = "flex-shrink-0 -mr-1.5" >
394+ < Capabilities capabilities = { capabilities } />
395+ </ div >
396+ ) }
397+ </ div >
398+ </ div >
399+ )
400+ } ) }
401+ </ div >
402+ ) }
403+
404+ { /* Divider between favorites and regular providers */ }
405+ { favoriteItems . length > 0 && (
406+ < div className = "border-b border-1 border-main-view-fg/8 mx-2" > </ div >
407+ ) }
408+
409+ { /* Regular provider sections */ }
333410 { Object . entries ( groupedItems ) . map ( ( [ providerKey , models ] ) => {
334411 const providerInfo = providers . find (
335412 ( p ) => p . provider === providerKey
@@ -340,7 +417,7 @@ const DropdownModelProvider = ({
340417 return (
341418 < div
342419 key = { providerKey }
343- className = "bg-main-view-fg/4 backdrop-blur-2xl first:mt-0 rounded-sm my-1.5 mx-1.5 first:mb-0"
420+ className = "bg-main-view-fg/2 backdrop-blur-2xl first:mt-0 rounded-sm my-1.5 mx-1.5 first:mb-0"
344421 >
345422 { /* Provider header */ }
346423 < div className = "flex items-center justify-between px-2 py-1" >
@@ -384,11 +461,13 @@ const DropdownModelProvider = ({
384461 return (
385462 < div
386463 key = { searchableModel . value }
464+ title = { searchableModel . model . id }
387465 onClick = { ( ) => handleSelect ( searchableModel ) }
388466 className = { cn (
389467 'mx-1 mb-1 px-2 py-1.5 rounded-sm cursor-pointer flex items-center gap-2 transition-all duration-200' ,
390- 'hover:bg-main-view-fg/10' ,
391- isSelected && 'bg-main-view-fg/15'
468+ 'hover:bg-main-view-fg/4' ,
469+ isSelected &&
470+ 'bg-main-view-fg/8 hover:bg-main-view-fg/8'
392471 ) }
393472 >
394473 < div className = "flex items-center gap-2 flex-1 min-w-0" >
0 commit comments