11import { useCallback , useMemo } from 'react'
2- import { Check , ChevronRight , Clock , Sparkles } from 'lucide-react'
2+ import { Check , ChevronRight , Star } from 'lucide-react'
33import {
44 DropdownMenu ,
55 DropdownMenuContent ,
66 DropdownMenuItem ,
7- DropdownMenuLabel ,
8- DropdownMenuSeparator ,
97 DropdownMenuTrigger ,
108} from '@/components/ui/dropdown-menu'
119import { useModelSelection } from '@/hooks/useModelSelection'
1210import { useVariants } from '@/hooks/useVariants'
13- import { formatModelName , getProviders } from '@/api/providers'
11+ import { formatModelName , formatProviderName , getProviders } from '@/api/providers'
1412import { useQuery } from '@tanstack/react-query'
1513import { useOpenCodeClient } from '@/hooks/useOpenCode'
1614
@@ -29,34 +27,40 @@ export function ModelQuickSelect({
2927 disabled,
3028 children,
3129} : ModelQuickSelectProps ) {
32- const { modelString, recentModels, favoriteModels, setModel } = useModelSelection ( opcodeUrl , directory )
30+ const { model , modelString, recentModels, favoriteModels, setModel, toggleFavorite } = useModelSelection ( opcodeUrl , directory )
3331 const { availableVariants, currentVariant, setVariant, clearVariant, hasVariants } = useVariants ( opcodeUrl , directory )
3432 const client = useOpenCodeClient ( opcodeUrl , directory )
3533
3634 const { data : providersData } = useQuery ( {
3735 queryKey : [ 'opencode' , 'providers' , opcodeUrl , directory ] ,
38- queryFn : ( ) => getProviders ( ) ,
36+ queryFn : ( ) => getProviders ( directory ) ,
3937 enabled : ! ! client ,
4038 staleTime : 30000 ,
4139 } )
4240
4341 const getDisplayName = useCallback ( ( providerID : string , modelID : string ) => {
4442 const modelData = providersData ?. providers
45- . find ( provider => provider . id === providerID )
46- ?. models ?. [ modelID ]
43+ . find ( provider => provider . id === providerID )
44+ ?. models ?. [ modelID ]
4745 return modelData ? formatModelName ( modelData ) : modelID
4846 } , [ providersData ] )
4947
48+ const getProviderName = useCallback ( ( providerID : string ) => {
49+ const provider = providersData ?. providers . find ( provider => provider . id === providerID )
50+ return provider ? formatProviderName ( provider ) : providerID
51+ } , [ providersData ] )
52+
5053 const favoriteModelsWithNames = useMemo ( ( ) => {
5154 return favoriteModels
5255 . filter ( favorite => `${ favorite . providerID } /${ favorite . modelID } ` !== modelString )
5356 . slice ( 0 , 5 )
5457 . map ( favorite => ( {
55- ...favorite ,
56- displayName : getDisplayName ( favorite . providerID , favorite . modelID ) ,
57- key : `${ favorite . providerID } /${ favorite . modelID } ` ,
58- } ) )
59- } , [ favoriteModels , getDisplayName , modelString ] )
58+ ...favorite ,
59+ displayName : getDisplayName ( favorite . providerID , favorite . modelID ) ,
60+ providerName : getProviderName ( favorite . providerID ) ,
61+ key : `${ favorite . providerID } /${ favorite . modelID } ` ,
62+ } ) )
63+ } , [ favoriteModels , getDisplayName , getProviderName , modelString ] )
6064
6165 const recentModelsWithNames = useMemo ( ( ) => {
6266 return recentModels
@@ -66,11 +70,21 @@ export function ModelQuickSelect({
6670 } )
6771 . slice ( 0 , 5 )
6872 . map ( recent => ( {
69- ...recent ,
70- displayName : getDisplayName ( recent . providerID , recent . modelID ) ,
71- key : `${ recent . providerID } /${ recent . modelID } ` ,
72- } ) )
73- } , [ recentModels , favoriteModels , getDisplayName , modelString ] )
73+ ...recent ,
74+ displayName : getDisplayName ( recent . providerID , recent . modelID ) ,
75+ providerName : getProviderName ( recent . providerID ) ,
76+ key : `${ recent . providerID } /${ recent . modelID } ` ,
77+ } ) )
78+ } , [ recentModels , favoriteModels , getDisplayName , getProviderName , modelString ] )
79+
80+ const duplicateDisplayNames = useMemo ( ( ) => {
81+ const counts = [ ...favoriteModelsWithNames , ...recentModelsWithNames ] . reduce < Record < string , number > > ( ( acc , item ) => {
82+ acc [ item . displayName ] = ( acc [ item . displayName ] || 0 ) + 1
83+ return acc
84+ } , { } )
85+
86+ return new Set ( Object . entries ( counts ) . filter ( ( [ , count ] ) => count > 1 ) . map ( ( [ name ] ) => name ) )
87+ } , [ favoriteModelsWithNames , recentModelsWithNames ] )
7488
7589 const handleVariantSelect = ( variant : string | undefined ) => {
7690 if ( variant === undefined ) {
@@ -84,6 +98,14 @@ export function ModelQuickSelect({
8498 setModel ( { providerID, modelID } )
8599 }
86100
101+ const handleCurrentFavoriteToggle = ( ) => {
102+ if ( ! model ) return
103+ toggleFavorite ( model )
104+ }
105+
106+ const isCurrentFavorite = model
107+ ? favoriteModels . some ( ( favorite ) => favorite . providerID === model . providerID && favorite . modelID === model . modelID )
108+ : false
87109 const hasFavorites = favoriteModelsWithNames . length > 0
88110 const hasRecents = recentModelsWithNames . length > 0
89111
@@ -93,12 +115,18 @@ export function ModelQuickSelect({
93115 { children }
94116 </ DropdownMenuTrigger >
95117 < DropdownMenuContent align = "start" className = "w-56" >
118+ { model && (
119+ < DropdownMenuItem
120+ onClick = { handleCurrentFavoriteToggle }
121+ className = "flex items-center justify-between"
122+ >
123+ < span > { isCurrentFavorite ? 'Remove from favorites' : 'Add to favorites' } </ span >
124+ < Star className = { `h-4 w-4 ${ isCurrentFavorite ? 'fill-yellow-400 text-yellow-400' : '' } ` } />
125+ </ DropdownMenuItem >
126+ ) }
127+
96128 { hasVariants && (
97129 < >
98- < DropdownMenuLabel className = "flex items-center gap-1.5" >
99- < Sparkles className = "h-3 w-3" />
100- Thinking Effort
101- </ DropdownMenuLabel >
102130 < DropdownMenuItem
103131 onClick = { ( ) => handleVariantSelect ( undefined ) }
104132 className = "flex items-center justify-between"
@@ -116,47 +144,44 @@ export function ModelQuickSelect({
116144 { currentVariant === variant && < Check className = "h-4 w-4" /> }
117145 </ DropdownMenuItem >
118146 ) ) }
119- { ( hasFavorites || hasRecents ) && < DropdownMenuSeparator /> }
120147 </ >
121148 ) }
122149
123150 { hasFavorites && (
124151 < >
125- < DropdownMenuLabel className = "flex items-center gap-1.5" >
126- < Sparkles className = "h-3 w-3" />
127- Favorite Models
128- </ DropdownMenuLabel >
129152 { favoriteModelsWithNames . map ( ( favorite ) => (
130153 < DropdownMenuItem
131154 key = { favorite . key }
132155 onClick = { ( ) => handleModelSelect ( favorite . providerID , favorite . modelID ) }
133156 className = "flex items-center justify-between"
134157 >
135- < span className = "truncate" > { favorite . displayName } </ span >
158+ < span className = "truncate" >
159+ { duplicateDisplayNames . has ( favorite . displayName )
160+ ? `${ favorite . providerName } /${ favorite . displayName } `
161+ : favorite . displayName }
162+ </ span >
136163 { modelString === favorite . key && < Check className = "h-4 w-4" /> }
137164 </ DropdownMenuItem >
138165 ) ) }
139- { hasRecents && < DropdownMenuSeparator /> }
140166 </ >
141167 ) }
142168
143169 { hasRecents && (
144170 < >
145- < DropdownMenuLabel className = "flex items-center gap-1.5" >
146- < Clock className = "h-3 w-3" />
147- Recent Models
148- </ DropdownMenuLabel >
149171 { recentModelsWithNames . map ( ( recent ) => (
150172 < DropdownMenuItem
151173 key = { recent . key }
152174 onClick = { ( ) => handleModelSelect ( recent . providerID , recent . modelID ) }
153175 className = "flex items-center justify-between"
154176 >
155- < span className = "truncate" > { recent . displayName } </ span >
177+ < span className = "truncate" >
178+ { duplicateDisplayNames . has ( recent . displayName )
179+ ? `${ recent . providerName } /${ recent . displayName } `
180+ : recent . displayName }
181+ </ span >
156182 { modelString === recent . key && < Check className = "h-4 w-4" /> }
157183 </ DropdownMenuItem >
158184 ) ) }
159- < DropdownMenuSeparator />
160185 </ >
161186 ) }
162187
0 commit comments