mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-03-10 06:00:19 +00:00
add model search
This commit is contained in:
parent
294adfdd1b
commit
b25db9b27e
@ -1,6 +1,7 @@
|
|||||||
import type { ProviderInfo } from '~/types/model';
|
import type { ProviderInfo } from '~/types/model';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import type { ModelInfo } from '~/lib/modules/llm/types';
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
|
import { classNames } from '~/utils/classNames';
|
||||||
|
|
||||||
interface ModelSelectorProps {
|
interface ModelSelectorProps {
|
||||||
model?: string;
|
model?: string;
|
||||||
@ -22,12 +23,30 @@ export const ModelSelector = ({
|
|||||||
providerList,
|
providerList,
|
||||||
modelLoading,
|
modelLoading,
|
||||||
}: ModelSelectorProps) => {
|
}: ModelSelectorProps) => {
|
||||||
// Load enabled providers from cookies
|
const [modelSearchQuery, setModelSearchQuery] = useState('');
|
||||||
|
const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false);
|
||||||
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
// Filter models based on search query
|
||||||
|
const filteredModels = [...modelList]
|
||||||
|
.filter((e) => e.provider === provider?.name && e.name)
|
||||||
|
.filter(
|
||||||
|
(model) =>
|
||||||
|
model.label.toLowerCase().includes(modelSearchQuery.toLowerCase()) ||
|
||||||
|
model.name.toLowerCase().includes(modelSearchQuery.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Focus search input when dropdown opens
|
||||||
|
useEffect(() => {
|
||||||
|
if (isModelDropdownOpen && searchInputRef.current) {
|
||||||
|
searchInputRef.current.focus();
|
||||||
|
}
|
||||||
|
}, [isModelDropdownOpen]);
|
||||||
|
|
||||||
// Update enabled providers when cookies change
|
// Update enabled providers when cookies change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If current provider is disabled, switch to first enabled provider
|
// If current provider is disabled, switch to first enabled provider
|
||||||
if (providerList.length == 0) {
|
if (providerList.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,27 +99,91 @@ export const ModelSelector = ({
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<select
|
|
||||||
key={provider?.name}
|
<div className="relative flex-1 lg:max-w-[70%]">
|
||||||
value={model}
|
<div
|
||||||
onChange={(e) => setModel?.(e.target.value)}
|
className={classNames(
|
||||||
className="flex-1 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus transition-all lg:max-w-[70%]"
|
'w-full p-2 rounded-lg border border-bolt-elements-borderColor',
|
||||||
disabled={modelLoading === 'all' || modelLoading === provider?.name}
|
'bg-bolt-elements-prompt-background text-bolt-elements-textPrimary',
|
||||||
|
'focus-within:outline-none focus-within:ring-2 focus-within:ring-bolt-elements-focus',
|
||||||
|
'transition-all cursor-pointer',
|
||||||
|
isModelDropdownOpen ? 'ring-2 ring-bolt-elements-focus' : undefined,
|
||||||
|
)}
|
||||||
|
onClick={() => setIsModelDropdownOpen(!isModelDropdownOpen)}
|
||||||
>
|
>
|
||||||
{modelLoading == 'all' || modelLoading == provider?.name ? (
|
<div className="flex items-center justify-between">
|
||||||
<option key={0} value="">
|
<span>{modelList.find((m) => m.name === model)?.label || 'Select model'}</span>
|
||||||
Loading...
|
<span
|
||||||
</option>
|
className={classNames(
|
||||||
|
'i-ph:caret-down transition-transform',
|
||||||
|
isModelDropdownOpen ? 'rotate-180' : undefined,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isModelDropdownOpen && (
|
||||||
|
<div className="absolute z-10 w-full mt-1 py-1 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background shadow-lg">
|
||||||
|
<div className="px-2 pb-2">
|
||||||
|
<div className="relative">
|
||||||
|
<input
|
||||||
|
ref={searchInputRef}
|
||||||
|
type="text"
|
||||||
|
value={modelSearchQuery}
|
||||||
|
onChange={(e) => setModelSearchQuery(e.target.value)}
|
||||||
|
placeholder="Search models..."
|
||||||
|
className={classNames(
|
||||||
|
'w-full pl-8 pr-3 py-1.5 rounded-md text-sm',
|
||||||
|
'bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor',
|
||||||
|
'text-bolt-elements-textPrimary placeholder:text-bolt-elements-textTertiary',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-bolt-elements-focus',
|
||||||
|
'transition-all',
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
<div className="absolute left-2.5 top-1/2 -translate-y-1/2">
|
||||||
|
<span className="i-ph:magnifying-glass text-bolt-elements-textTertiary" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'max-h-60 overflow-y-auto',
|
||||||
|
'scrollbar-thin scrollbar-track-bolt-elements-background-depth-2',
|
||||||
|
'scrollbar-thumb-bolt-elements-borderColor hover:scrollbar-thumb-bolt-elements-borderColorHover',
|
||||||
|
'scrollbar-thumb-rounded-full scrollbar-track-rounded-full',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{modelLoading === 'all' || modelLoading === provider?.name ? (
|
||||||
|
<div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">Loading...</div>
|
||||||
|
) : filteredModels.length === 0 ? (
|
||||||
|
<div className="px-3 py-2 text-sm text-bolt-elements-textTertiary">No models found</div>
|
||||||
) : (
|
) : (
|
||||||
[...modelList]
|
filteredModels.map((modelOption, index) => (
|
||||||
.filter((e) => e.provider == provider?.name && e.name)
|
<div
|
||||||
.map((modelOption, index) => (
|
key={index}
|
||||||
<option key={index} value={modelOption.name}>
|
className={classNames(
|
||||||
|
'px-3 py-2 text-sm cursor-pointer',
|
||||||
|
'hover:bg-bolt-elements-background-depth-3',
|
||||||
|
'text-bolt-elements-textPrimary',
|
||||||
|
model === modelOption.name ? 'bg-bolt-elements-background-depth-2' : undefined,
|
||||||
|
)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setModel?.(modelOption.name);
|
||||||
|
setIsModelDropdownOpen(false);
|
||||||
|
setModelSearchQuery('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
{modelOption.label}
|
{modelOption.label}
|
||||||
</option>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</select>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user