1- import { createSignal , createMemo , For , Show , onCleanup , type Component } from "solid-js" ;
1+ import { createSignal , createMemo , For , Show , onCleanup , type Component } from 'solid-js' ;
2+ import { resolveProviderId } from '../services/routing-utils.js' ;
3+ import { providerIcon } from './ProviderIcon.jsx' ;
24
35interface ModelPricesFilterBarProps {
46 allModels : string [ ] ;
@@ -15,66 +17,53 @@ interface ModelPricesFilterBarProps {
1517}
1618
1719interface Suggestion {
18- type : " Provider" | " Model" ;
20+ type : ' Provider' | ' Model' ;
1921 value : string ;
2022}
2123
2224interface Tag {
23- type : " Provider" | " Model" ;
25+ type : ' Provider' | ' Model' ;
2426 value : string ;
2527}
2628
27- /** Maps provider display names to icon filenames in /icons/providers/ */
28- const providerIconMap : Record < string , string > = {
29- OpenAI : "openai" ,
30- Anthropic : "anthropic" ,
31- Google : "google" ,
32- DeepSeek : "deepseek" ,
33- Mistral : "mistral" ,
34- Meta : "meta" ,
35- Amazon : "amazon" ,
36- Alibaba : "alibaba" ,
37- Moonshot : "moonshot" ,
38- Zhipu : "zhipu" ,
39- Cohere : "cohere" ,
40- xAI : "xai" ,
41- } ;
42-
43- /** Providers whose icons are monochrome (dark fill) and need inversion in dark mode */
44- const monoProviders = new Set ( [ "OpenAI" , "Anthropic" , "Moonshot" , "xAI" ] ) ;
45-
46- const getProviderIconSrc = ( provider : string ) : string | null => {
47- const key = providerIconMap [ provider ] ;
48- return key ? `/icons/providers/${ key } .svg` : null ;
49- } ;
50-
51- const ProviderIcon : Component < { provider : string ; size ?: number } > = ( props ) => {
52- const src = ( ) => getProviderIconSrc ( props . provider ) ;
29+ const FilterProviderIcon : Component < { provider : string ; size ?: number } > = ( props ) => {
5330 const size = ( ) => props . size ?? 16 ;
54- const isMono = ( ) => monoProviders . has ( props . provider ) ;
31+ const id = ( ) => resolveProviderId ( props . provider ) ;
32+ const icon = ( ) => {
33+ const pid = id ( ) ;
34+ return pid ? providerIcon ( pid , size ( ) ) : null ;
35+ } ;
5536
5637 return (
57- < Show when = { src ( ) } fallback = {
58- < svg class = "model-filter__provider-icon" width = { size ( ) } height = { size ( ) } viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >
59- < rect x = "3" y = "3" width = "18" height = "18" rx = "3" />
60- < circle cx = "12" cy = "12" r = "3" />
61- </ svg >
62- } >
63- < img
64- class = "model-filter__provider-icon"
65- classList = { { "model-filter__provider-icon--mono" : isMono ( ) } }
66- src = { src ( ) ! }
67- alt = ""
68- width = { size ( ) }
69- height = { size ( ) }
70- aria-hidden = "true"
71- />
38+ < Show
39+ when = { icon ( ) }
40+ fallback = {
41+ < svg
42+ class = "model-filter__provider-icon"
43+ width = { size ( ) }
44+ height = { size ( ) }
45+ viewBox = "0 0 24 24"
46+ fill = "none"
47+ stroke = "currentColor"
48+ stroke-width = "2"
49+ stroke-linecap = "round"
50+ stroke-linejoin = "round"
51+ aria-hidden = "true"
52+ >
53+ < rect x = "3" y = "3" width = "18" height = "18" rx = "3" />
54+ < circle cx = "12" cy = "12" r = "3" />
55+ </ svg >
56+ }
57+ >
58+ < span class = "model-filter__provider-icon" aria-hidden = "true" >
59+ { icon ( ) }
60+ </ span >
7261 </ Show >
7362 ) ;
7463} ;
7564
7665const ModelPricesFilterBar : Component < ModelPricesFilterBarProps > = ( props ) => {
77- const [ query , setQuery ] = createSignal ( "" ) ;
66+ const [ query , setQuery ] = createSignal ( '' ) ;
7867 const [ dropdownOpen , setDropdownOpen ] = createSignal ( false ) ;
7968 const [ highlightIndex , setHighlightIndex ] = createSignal ( - 1 ) ;
8069 let comboboxRef : HTMLDivElement | undefined ;
@@ -85,10 +74,10 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
8574 const activeTags = createMemo < Tag [ ] > ( ( ) => {
8675 const tags : Tag [ ] = [ ] ;
8776 for ( const p of props . selectedProviders ) {
88- tags . push ( { type : " Provider" , value : p } ) ;
77+ tags . push ( { type : ' Provider' , value : p } ) ;
8978 }
9079 for ( const m of props . selectedModels ) {
91- tags . push ( { type : " Model" , value : m } ) ;
80+ tags . push ( { type : ' Model' , value : m } ) ;
9281 }
9382 return tags ;
9483 } ) ;
@@ -112,28 +101,28 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
112101 const flatSuggestions = createMemo < Suggestion [ ] > ( ( ) => {
113102 const suggestions : Suggestion [ ] = [ ] ;
114103 for ( const p of matchingProviders ( ) ) {
115- suggestions . push ( { type : " Provider" , value : p } ) ;
104+ suggestions . push ( { type : ' Provider' , value : p } ) ;
116105 }
117106 for ( const m of matchingModels ( ) ) {
118- suggestions . push ( { type : " Model" , value : m } ) ;
107+ suggestions . push ( { type : ' Model' , value : m } ) ;
119108 }
120109 return suggestions ;
121110 } ) ;
122111
123112 const selectSuggestion = ( suggestion : Suggestion ) => {
124- if ( suggestion . type === " Provider" ) {
113+ if ( suggestion . type === ' Provider' ) {
125114 props . onAddProvider ( suggestion . value ) ;
126115 } else {
127116 props . onAddModel ( suggestion . value ) ;
128117 }
129- setQuery ( "" ) ;
118+ setQuery ( '' ) ;
130119 setDropdownOpen ( false ) ;
131120 setHighlightIndex ( - 1 ) ;
132121 inputRef ?. focus ( ) ;
133122 } ;
134123
135124 const removeTag = ( tag : Tag ) => {
136- if ( tag . type === " Provider" ) {
125+ if ( tag . type === ' Provider' ) {
137126 props . onRemoveProvider ( tag . value ) ;
138127 } else {
139128 props . onRemoveModel ( tag . value ) ;
@@ -150,25 +139,25 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
150139 const handleKeyDown = ( e : KeyboardEvent ) => {
151140 const suggestions = flatSuggestions ( ) ;
152141
153- if ( e . key === " ArrowDown" ) {
142+ if ( e . key === ' ArrowDown' ) {
154143 e . preventDefault ( ) ;
155144 if ( ! dropdownOpen ( ) && query ( ) . trim ( ) . length >= 2 ) {
156145 setDropdownOpen ( true ) ;
157146 }
158147 setHighlightIndex ( ( i ) => Math . min ( i + 1 , suggestions . length - 1 ) ) ;
159- } else if ( e . key === " ArrowUp" ) {
148+ } else if ( e . key === ' ArrowUp' ) {
160149 e . preventDefault ( ) ;
161150 setHighlightIndex ( ( i ) => Math . max ( i - 1 , 0 ) ) ;
162- } else if ( e . key === " Enter" ) {
151+ } else if ( e . key === ' Enter' ) {
163152 e . preventDefault ( ) ;
164153 const idx = highlightIndex ( ) ;
165154 if ( idx >= 0 && idx < suggestions . length ) {
166155 selectSuggestion ( suggestions [ idx ] ! ) ;
167156 }
168- } else if ( e . key === " Escape" ) {
157+ } else if ( e . key === ' Escape' ) {
169158 setDropdownOpen ( false ) ;
170159 setHighlightIndex ( - 1 ) ;
171- } else if ( e . key === " Backspace" && query ( ) === "" ) {
160+ } else if ( e . key === ' Backspace' && query ( ) === '' ) {
172161 const tags = activeTags ( ) ;
173162 if ( tags . length > 0 ) {
174163 removeTag ( tags [ tags . length - 1 ] ! ) ;
@@ -183,10 +172,10 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
183172 }
184173 } ;
185174
186- if ( typeof document !== " undefined" ) {
187- document . addEventListener ( " click" , handleClickOutside ) ;
175+ if ( typeof document !== ' undefined' ) {
176+ document . addEventListener ( ' click' , handleClickOutside ) ;
188177 onCleanup ( ( ) => {
189- document . removeEventListener ( " click" , handleClickOutside ) ;
178+ document . removeEventListener ( ' click' , handleClickOutside ) ;
190179 } ) ;
191180 }
192181
@@ -231,19 +220,24 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
231220 < div class = "model-filter__dropdown-label" > Providers</ div >
232221 < For each = { matchingProviders ( ) } >
233222 { ( provider ) => {
234- const idx = ( ) => flatSuggestions ( ) . findIndex ( ( s ) => s . type === "Provider" && s . value === provider ) ;
223+ const idx = ( ) =>
224+ flatSuggestions ( ) . findIndex (
225+ ( s ) => s . type === 'Provider' && s . value === provider ,
226+ ) ;
235227 return (
236228 < button
237229 class = "model-filter__dropdown-item"
238- classList = { { "model-filter__dropdown-item--highlighted" : highlightIndex ( ) === idx ( ) } }
239- onClick = { ( ) => selectSuggestion ( { type : "Provider" , value : provider } ) }
230+ classList = { {
231+ 'model-filter__dropdown-item--highlighted' : highlightIndex ( ) === idx ( ) ,
232+ } }
233+ onClick = { ( ) => selectSuggestion ( { type : 'Provider' , value : provider } ) }
240234 onMouseEnter = { ( ) => setHighlightIndex ( idx ( ) ) }
241235 type = "button"
242236 role = "option"
243237 aria-selected = { highlightIndex ( ) === idx ( ) }
244238 >
245239 < span class = "model-filter__dropdown-item-name" >
246- < ProviderIcon provider = { provider } size = { 16 } />
240+ < FilterProviderIcon provider = { provider } size = { 16 } />
247241 { provider }
248242 </ span >
249243 < span class = "model-filter__dropdown-item-type" > Provider</ span >
@@ -258,12 +252,15 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
258252 < div class = "model-filter__dropdown-label" > Models</ div >
259253 < For each = { matchingModels ( ) } >
260254 { ( model ) => {
261- const idx = ( ) => flatSuggestions ( ) . findIndex ( ( s ) => s . type === "Model" && s . value === model ) ;
255+ const idx = ( ) =>
256+ flatSuggestions ( ) . findIndex ( ( s ) => s . type === 'Model' && s . value === model ) ;
262257 return (
263258 < button
264259 class = "model-filter__dropdown-item"
265- classList = { { "model-filter__dropdown-item--highlighted" : highlightIndex ( ) === idx ( ) } }
266- onClick = { ( ) => selectSuggestion ( { type : "Model" , value : model } ) }
260+ classList = { {
261+ 'model-filter__dropdown-item--highlighted' : highlightIndex ( ) === idx ( ) ,
262+ } }
263+ onClick = { ( ) => selectSuggestion ( { type : 'Model' , value : model } ) }
267264 onMouseEnter = { ( ) => setHighlightIndex ( idx ( ) ) }
268265 type = "button"
269266 role = "option"
@@ -281,13 +278,17 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
281278 </ Show >
282279 </ div >
283280 < div class = "model-filter__summary" >
284- < Show when = { hasActiveFilters ( ) } fallback = {
285- < span > { props . totalCount } models </ span >
286- } >
287- < span > { props . filteredCount } of { props . totalCount } models < /span >
281+ < Show when = { hasActiveFilters ( ) } fallback = { < span > { props . totalCount } models </ span > } >
282+ < span >
283+ { props . filteredCount } of { props . totalCount } models
284+ </ span >
288285 < button
289286 class = "model-filter__clear-all"
290- onClick = { ( ) => { props . onClearFilters ( ) ; setQuery ( "" ) ; setDropdownOpen ( false ) ; } }
287+ onClick = { ( ) => {
288+ props . onClearFilters ( ) ;
289+ setQuery ( '' ) ;
290+ setDropdownOpen ( false ) ;
291+ } }
291292 type = "button"
292293 >
293294 Clear filters
@@ -299,11 +300,14 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
299300 < div class = "model-filter__tags" >
300301 < For each = { activeTags ( ) } >
301302 { ( tag ) => (
302- < span class = "model-filter__tag" classList = { { "model-filter__tag--provider" : tag . type === "Provider" } } >
303- < Show when = { tag . type === "Provider" } >
304- < ProviderIcon provider = { tag . value } size = { 16 } />
303+ < span
304+ class = "model-filter__tag"
305+ classList = { { 'model-filter__tag--provider' : tag . type === 'Provider' } }
306+ >
307+ < Show when = { tag . type === 'Provider' } >
308+ < FilterProviderIcon provider = { tag . value } size = { 16 } />
305309 </ Show >
306- < Show when = { tag . type === " Model" } >
310+ < Show when = { tag . type === ' Model' } >
307311 < span class = "model-filter__tag-type" > Model:</ span >
308312 </ Show >
309313 < span class = "model-filter__tag-value" > { tag . value } </ span >
@@ -313,7 +317,17 @@ const ModelPricesFilterBar: Component<ModelPricesFilterBarProps> = (props) => {
313317 type = "button"
314318 aria-label = { `Remove ${ tag . type } ${ tag . value } ` }
315319 >
316- < svg width = "10" height = "10" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke-width = "2.5" stroke-linecap = "round" stroke-linejoin = "round" aria-hidden = "true" >
320+ < svg
321+ width = "10"
322+ height = "10"
323+ viewBox = "0 0 24 24"
324+ fill = "none"
325+ stroke = "currentColor"
326+ stroke-width = "2.5"
327+ stroke-linecap = "round"
328+ stroke-linejoin = "round"
329+ aria-hidden = "true"
330+ >
317331 < path d = "M18 6 6 18" />
318332 < path d = "m6 6 12 12" />
319333 </ svg >
0 commit comments