@@ -10,6 +10,9 @@ import {
1010 RECOMMENDED_MODELS ,
1111 DEFAULT_MAX_MODEL_PRICE ,
1212 DEFAULT_MAX_MODEL_AGE_DAYS ,
13+ DEFAULT_MIN_CONTEXT_LENGTH ,
14+ DEFAULT_ALLOWED_VENDORS ,
15+ DEFAULT_MODEL_NAME_EXCLUSIONS ,
1316 COST_TIER_THRESHOLDS ,
1417} from '@shared/constants' ;
1518
@@ -24,17 +27,23 @@ const fromCache = ref(false);
2427const filterOptions = ref < ModelFilterOptions > ( {
2528 maxPrice : DEFAULT_MAX_MODEL_PRICE ,
2629 maxAgeDays : DEFAULT_MAX_MODEL_AGE_DAYS ,
30+ minContextLength : DEFAULT_MIN_CONTEXT_LENGTH ,
31+ allowedVendors : [ ...DEFAULT_ALLOWED_VENDORS ] ,
32+ nameExclusions : [ ...DEFAULT_MODEL_NAME_EXCLUSIONS ] ,
2733 searchQuery : '' ,
28- showAll : false ,
2934} ) ;
3035
3136/**
32- * Calculate blended price (average of prompt and completion)
37+ * Calculate blended price per 1M tokens (weighted: 75% input, 25% output)
38+ * OpenRouter returns prices per token, so we multiply by 1M
3339 */
3440function calculateBlendedPrice ( pricing : { prompt : string ; completion : string } ) : number {
35- const promptPrice = parseFloat ( pricing . prompt ) || 0 ;
36- const completionPrice = parseFloat ( pricing . completion ) || 0 ;
37- return ( promptPrice + completionPrice ) / 2 ;
41+ const inputPricePerToken = parseFloat ( pricing . prompt ) || 0 ;
42+ const outputPricePerToken = parseFloat ( pricing . completion ) || 0 ;
43+ // Convert to per 1M tokens and apply 3:1 weighting (input:output)
44+ const inputPer1M = inputPricePerToken * 1_000_000 ;
45+ const outputPer1M = outputPricePerToken * 1_000_000 ;
46+ return ( 3 * inputPer1M + outputPer1M ) / 4 ;
3847}
3948
4049/**
@@ -62,12 +71,26 @@ export function useModels() {
6271 // Computed values
6372 const recommendedModels = computed ( ( ) => {
6473 const recommendedSet = new Set < string > ( RECOMMENDED_MODELS ) ;
65- return models . value . filter ( ( m ) => recommendedSet . has ( m . id ) ) ;
74+ const filtered = models . value . filter ( ( m ) => recommendedSet . has ( m . id ) ) ;
75+ // Sort by RECOMMENDED_MODELS order
76+ const orderMap = new Map < string , number > ( RECOMMENDED_MODELS . map ( ( id , idx ) => [ id , idx ] ) ) ;
77+ return filtered . sort ( ( a , b ) => ( orderMap . get ( a . id ) ?? Infinity ) - ( orderMap . get ( b . id ) ?? Infinity ) ) ;
6678 } ) ;
6779
6880 const filteredModels = computed ( ( ) => {
6981 let result = [ ...models . value ] ;
7082
83+ // Apply vendor filter
84+ if ( filterOptions . value . allowedVendors ?. length ) {
85+ const vendors = filterOptions . value . allowedVendors ;
86+ result = result . filter ( ( m ) => vendors . some ( ( v ) => m . id . startsWith ( `${ v } /` ) ) ) ;
87+ }
88+
89+ // Apply context length filter
90+ if ( filterOptions . value . minContextLength !== undefined ) {
91+ result = result . filter ( ( m ) => m . context_length >= ( filterOptions . value . minContextLength ?? 0 ) ) ;
92+ }
93+
7194 // Apply price filter
7295 if ( filterOptions . value . maxPrice !== undefined ) {
7396 result = result . filter ( ( m ) => {
@@ -85,6 +108,16 @@ export function useModels() {
85108 } ) ;
86109 }
87110
111+ // Apply name exclusions filter
112+ if ( filterOptions . value . nameExclusions ?. length ) {
113+ const exclusions = filterOptions . value . nameExclusions . map ( ( e ) => e . toLowerCase ( ) ) ;
114+ result = result . filter ( ( m ) => {
115+ const nameLower = m . name . toLowerCase ( ) ;
116+ const idLower = m . id . toLowerCase ( ) ;
117+ return ! exclusions . some ( ( ex ) => nameLower . includes ( ex ) || idLower . includes ( ex ) ) ;
118+ } ) ;
119+ }
120+
88121 // Apply search filter
89122 if ( filterOptions . value . searchQuery ) {
90123 const query = filterOptions . value . searchQuery . toLowerCase ( ) ;
@@ -96,28 +129,27 @@ export function useModels() {
96129 ) ;
97130 }
98131
99- // Sort: recommended first, then by price
132+ // Sort: recommended first (in RECOMMENDED_MODELS order), then by release date (newest first)
133+ const recommendedOrder = new Map < string , number > ( RECOMMENDED_MODELS . map ( ( id , idx ) => [ id , idx ] ) ) ;
100134 result . sort ( ( a , b ) => {
101- // Recommended models first
135+ // Recommended models first, preserving RECOMMENDED_MODELS order
102136 if ( a . isRecommended && ! b . isRecommended ) return - 1 ;
103137 if ( ! a . isRecommended && b . isRecommended ) return 1 ;
138+ if ( a . isRecommended && b . isRecommended ) {
139+ const orderA = recommendedOrder . get ( a . id ) ?? Infinity ;
140+ const orderB = recommendedOrder . get ( b . id ) ?? Infinity ;
141+ return orderA - orderB ;
142+ }
104143
105- // Then by price (cheaper first)
106- const priceA = calculateBlendedPrice ( a . pricing ) ;
107- const priceB = calculateBlendedPrice ( b . pricing ) ;
108- return priceA - priceB ;
144+ // Non-recommended: sort by release date (newest first)
145+ const createdA = a . created ?? 0 ;
146+ const createdB = b . created ?? 0 ;
147+ return createdB - createdA ;
109148 } ) ;
110149
111150 return result ;
112151 } ) ;
113152
114- const displayModels = computed ( ( ) => {
115- if ( filterOptions . value . showAll ) {
116- return filteredModels . value ;
117- }
118- return recommendedModels . value ;
119- } ) ;
120-
121153 const hasRecommendedModels = computed ( ( ) => recommendedModels . value . length > 0 ) ;
122154
123155 /**
@@ -174,16 +206,6 @@ export function useModels() {
174206 filterOptions . value = { ...filterOptions . value , searchQuery : query } ;
175207 }
176208
177- /**
178- * Toggle show all models
179- */
180- function toggleShowAll ( ) : void {
181- filterOptions . value = {
182- ...filterOptions . value ,
183- showAll : ! filterOptions . value . showAll ,
184- } ;
185- }
186-
187209 /**
188210 * Get model by ID
189211 */
@@ -214,14 +236,11 @@ export function useModels() {
214236 }
215237
216238 /**
217- * Format price for display
239+ * Format price for display (blended price per 1M tokens)
218240 */
219241 function formatPrice ( pricing : { prompt : string ; completion : string } ) : string {
220242 const blendedPrice = calculateBlendedPrice ( pricing ) ;
221- if ( blendedPrice < 0.01 ) {
222- return `$${ ( blendedPrice * 1000 ) . toFixed ( 3 ) } /1K` ;
223- }
224- return `$${ blendedPrice . toFixed ( 4 ) } /1M` ;
243+ return `$${ blendedPrice . toFixed ( 2 ) } /1M` ;
225244 }
226245
227246 return {
@@ -236,15 +255,13 @@ export function useModels() {
236255 // Computed
237256 recommendedModels,
238257 filteredModels,
239- displayModels,
240258 hasRecommendedModels,
241259
242260 // Actions
243261 fetchModels,
244262 refreshModels,
245263 updateFilters,
246264 setSearchQuery,
247- toggleShowAll,
248265 getModel,
249266 isValidModel,
250267 getModelDisplayInfo,
0 commit comments