mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 18:17:26 +00:00
feat(openrouter): prioritize Exacto models for better agentic performance
- Add isExacto field to CategorizedModel for models with :exacto suffix - Add sortModelsByPriority function (Free > Exacto > Regular) - Apply priority sorting within each category in model picker - Add visual "Exacto" badge (green outline) for exacto variants Exacto models are OpenRouter's specialized variants optimized for tool use and agentic behaviors, recommended for Claude Code.
This commit is contained in:
@@ -13,6 +13,7 @@ import { Search, RefreshCw, Loader2, Sparkles } from 'lucide-react';
|
|||||||
import { useOpenRouterCatalog, useRefreshOpenRouterModels } from '@/hooks/use-openrouter-models';
|
import { useOpenRouterCatalog, useRefreshOpenRouterModels } from '@/hooks/use-openrouter-models';
|
||||||
import {
|
import {
|
||||||
searchModels,
|
searchModels,
|
||||||
|
sortModelsByPriority,
|
||||||
formatPricingPair,
|
formatPricingPair,
|
||||||
formatContextLength,
|
formatContextLength,
|
||||||
formatModelAge,
|
formatModelAge,
|
||||||
@@ -56,7 +57,7 @@ export function OpenRouterModelPicker({
|
|||||||
// Determine if we should show presets (no search query and no category filter)
|
// Determine if we should show presets (no search query and no category filter)
|
||||||
const showPresets = !search.trim() && !selectedCategory;
|
const showPresets = !search.trim() && !selectedCategory;
|
||||||
|
|
||||||
// Group by category
|
// Group by category and sort each group by priority (Free > Exacto > Regular)
|
||||||
const groupedModels = useMemo(() => {
|
const groupedModels = useMemo(() => {
|
||||||
const groups: Record<ModelCategory, CategorizedModel[]> = {
|
const groups: Record<ModelCategory, CategorizedModel[]> = {
|
||||||
anthropic: [],
|
anthropic: [],
|
||||||
@@ -72,6 +73,11 @@ export function OpenRouterModelPicker({
|
|||||||
groups[model.category].push(model);
|
groups[model.category].push(model);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Sort each category by priority
|
||||||
|
for (const category of Object.keys(groups) as ModelCategory[]) {
|
||||||
|
groups[category] = sortModelsByPriority(groups[category]);
|
||||||
|
}
|
||||||
|
|
||||||
return groups;
|
return groups;
|
||||||
}, [filteredModels]);
|
}, [filteredModels]);
|
||||||
|
|
||||||
@@ -275,6 +281,21 @@ function ModelItem({
|
|||||||
>
|
>
|
||||||
Free
|
Free
|
||||||
</Badge>
|
</Badge>
|
||||||
|
) : model.isExacto ? (
|
||||||
|
<>
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
'text-[10px] px-1 border-emerald-500/50 text-emerald-600',
|
||||||
|
isSelected
|
||||||
|
? 'border-accent-foreground/30 text-accent-foreground/80'
|
||||||
|
: 'group-hover:border-accent-foreground/30 group-hover:text-accent-foreground/80'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Exacto
|
||||||
|
</Badge>
|
||||||
|
<span className="tabular-nums">{formatPricingPair(model.pricing)}</span>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="tabular-nums">{formatPricingPair(model.pricing)}</span>
|
<span className="tabular-nums">{formatPricingPair(model.pricing)}</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -69,4 +69,5 @@ export interface CategorizedModel extends OpenRouterModel {
|
|||||||
pricePerMillionPrompt: number;
|
pricePerMillionPrompt: number;
|
||||||
pricePerMillionCompletion: number;
|
pricePerMillionCompletion: number;
|
||||||
isFree: boolean;
|
isFree: boolean;
|
||||||
|
isExacto: boolean; // Models with :exacto suffix - optimized for tool use
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export function enrichModel(model: OpenRouterModel): CategorizedModel {
|
|||||||
pricePerMillionPrompt: pricePerMillion(model.pricing.prompt),
|
pricePerMillionPrompt: pricePerMillion(model.pricing.prompt),
|
||||||
pricePerMillionCompletion: pricePerMillion(model.pricing.completion),
|
pricePerMillionCompletion: pricePerMillion(model.pricing.completion),
|
||||||
isFree: model.pricing.prompt === '0' && model.pricing.completion === '0',
|
isFree: model.pricing.prompt === '0' && model.pricing.completion === '0',
|
||||||
|
isExacto: model.id.includes(':exacto'), // Exacto variants - optimized for agentic/tool use
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +86,27 @@ export function searchModels(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort models with priority: Free > Exacto > Regular
|
||||||
|
* Within each tier, sort by name alphabetically
|
||||||
|
*/
|
||||||
|
export function sortModelsByPriority(models: CategorizedModel[]): CategorizedModel[] {
|
||||||
|
return [...models].sort((a, b) => {
|
||||||
|
// Priority 1: Free models first
|
||||||
|
if (a.isFree && !b.isFree) return -1;
|
||||||
|
if (!a.isFree && b.isFree) return 1;
|
||||||
|
|
||||||
|
// Priority 2: Exacto models second (only if both not free)
|
||||||
|
if (!a.isFree && !b.isFree) {
|
||||||
|
if (a.isExacto && !b.isExacto) return -1;
|
||||||
|
if (!a.isExacto && b.isExacto) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same tier: sort by name
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Get cached models from localStorage */
|
/** Get cached models from localStorage */
|
||||||
export function getCachedModels(): OpenRouterModel[] | null {
|
export function getCachedModels(): OpenRouterModel[] | null {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user