Merge pull request #1860 from xKevIsDev/openrouter-filter
feat: add filter for free models in ModelSelector component for OpenRouter
This commit is contained in:
@@ -15,6 +15,21 @@ interface ModelSelectorProps {
|
|||||||
modelLoading?: string;
|
modelLoading?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to determine if a model is likely free
|
||||||
|
const isModelLikelyFree = (model: ModelInfo, providerName?: string): boolean => {
|
||||||
|
// OpenRouter models with zero pricing in the label
|
||||||
|
if (providerName === 'OpenRouter' && model.label.includes('in:$0.00') && model.label.includes('out:$0.00')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Models with "free" in the name or label
|
||||||
|
if (model.name.toLowerCase().includes('free') || model.label.toLowerCase().includes('free')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export const ModelSelector = ({
|
export const ModelSelector = ({
|
||||||
model,
|
model,
|
||||||
setModel,
|
setModel,
|
||||||
@@ -36,6 +51,7 @@ export const ModelSelector = ({
|
|||||||
const providerSearchInputRef = useRef<HTMLInputElement>(null);
|
const providerSearchInputRef = useRef<HTMLInputElement>(null);
|
||||||
const providerOptionsRef = useRef<(HTMLDivElement | null)[]>([]);
|
const providerOptionsRef = useRef<(HTMLDivElement | null)[]>([]);
|
||||||
const providerDropdownRef = useRef<HTMLDivElement>(null);
|
const providerDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [showFreeModelsOnly, setShowFreeModelsOnly] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
@@ -57,19 +73,31 @@ export const ModelSelector = ({
|
|||||||
|
|
||||||
const filteredModels = [...modelList]
|
const filteredModels = [...modelList]
|
||||||
.filter((e) => e.provider === provider?.name && e.name)
|
.filter((e) => e.provider === provider?.name && e.name)
|
||||||
.filter(
|
.filter((model) => {
|
||||||
(model) =>
|
// Apply free models filter
|
||||||
|
if (showFreeModelsOnly && !isModelLikelyFree(model, provider?.name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply search filter
|
||||||
|
return (
|
||||||
model.label.toLowerCase().includes(modelSearchQuery.toLowerCase()) ||
|
model.label.toLowerCase().includes(modelSearchQuery.toLowerCase()) ||
|
||||||
model.name.toLowerCase().includes(modelSearchQuery.toLowerCase()),
|
model.name.toLowerCase().includes(modelSearchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const filteredProviders = providerList.filter((p) =>
|
const filteredProviders = providerList.filter((p) =>
|
||||||
p.name.toLowerCase().includes(providerSearchQuery.toLowerCase()),
|
p.name.toLowerCase().includes(providerSearchQuery.toLowerCase()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Reset free models filter when provider changes
|
||||||
|
useEffect(() => {
|
||||||
|
setShowFreeModelsOnly(false);
|
||||||
|
}, [provider?.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFocusedModelIndex(-1);
|
setFocusedModelIndex(-1);
|
||||||
}, [modelSearchQuery, isModelDropdownOpen]);
|
}, [modelSearchQuery, isModelDropdownOpen, showFreeModelsOnly]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFocusedProviderIndex(-1);
|
setFocusedProviderIndex(-1);
|
||||||
@@ -384,7 +412,36 @@ export const ModelSelector = ({
|
|||||||
role="listbox"
|
role="listbox"
|
||||||
id="model-listbox"
|
id="model-listbox"
|
||||||
>
|
>
|
||||||
<div className="px-2 pb-2">
|
<div className="px-2 pb-2 space-y-2">
|
||||||
|
{/* Free Models Filter Toggle - Only show for OpenRouter */}
|
||||||
|
{provider?.name === 'OpenRouter' && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowFreeModelsOnly(!showFreeModelsOnly);
|
||||||
|
}}
|
||||||
|
className={classNames(
|
||||||
|
'flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium transition-all',
|
||||||
|
'hover:bg-bolt-elements-background-depth-3',
|
||||||
|
showFreeModelsOnly
|
||||||
|
? 'bg-purple-500/20 text-purple-400 border border-purple-500/30'
|
||||||
|
: 'bg-bolt-elements-background-depth-3 text-bolt-elements-textSecondary border border-bolt-elements-borderColor',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="i-ph:gift text-xs" />
|
||||||
|
Free models only
|
||||||
|
</button>
|
||||||
|
{showFreeModelsOnly && (
|
||||||
|
<span className="text-xs text-bolt-elements-textTertiary">
|
||||||
|
{filteredModels.length} free model{filteredModels.length !== 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Search Input */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
ref={modelSearchInputRef}
|
ref={modelSearchInputRef}
|
||||||
@@ -428,7 +485,9 @@ export const ModelSelector = ({
|
|||||||
{modelLoading === 'all' || modelLoading === provider?.name ? (
|
{modelLoading === 'all' || modelLoading === provider?.name ? (
|
||||||
<div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">Loading...</div>
|
<div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">Loading...</div>
|
||||||
) : filteredModels.length === 0 ? (
|
) : filteredModels.length === 0 ? (
|
||||||
<div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">No models found</div>
|
<div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">
|
||||||
|
{showFreeModelsOnly ? 'No free models found' : 'No models found'}
|
||||||
|
</div>
|
||||||
) : (
|
) : (
|
||||||
filteredModels.map((modelOption, index) => (
|
filteredModels.map((modelOption, index) => (
|
||||||
<div
|
<div
|
||||||
@@ -454,7 +513,12 @@ export const ModelSelector = ({
|
|||||||
}}
|
}}
|
||||||
tabIndex={focusedModelIndex === index ? 0 : -1}
|
tabIndex={focusedModelIndex === index ? 0 : -1}
|
||||||
>
|
>
|
||||||
{modelOption.label}
|
<div className="flex items-center justify-between">
|
||||||
|
<span>{modelOption.label}</span>
|
||||||
|
{isModelLikelyFree(modelOption, provider?.name) && (
|
||||||
|
<span className="i-ph:gift text-xs text-purple-400 ml-2" title="Free model" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user