feat(ui): add search filter to provider grid (#10437)
This commit is contained in:
@@ -10,7 +10,8 @@ import {
|
||||
acpDeleteCustomProvider,
|
||||
acpUpdateCustomProviderFromRequest,
|
||||
} from '../../../acp/providers';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { Plus, Search } from 'lucide-react';
|
||||
import { Input } from '../../ui/input';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../../ui/dialog';
|
||||
import CustomProviderForm from './modal/subcomponents/forms/CustomProviderForm';
|
||||
import { SwitchModelModal } from '../models/subcomponents/SwitchModelModal';
|
||||
@@ -43,6 +44,14 @@ const i18n = defineMessages({
|
||||
id: 'providerGrid.chooseModel',
|
||||
defaultMessage: 'Choose Model',
|
||||
},
|
||||
searchPlaceholder: {
|
||||
id: 'providerGrid.searchPlaceholder',
|
||||
defaultMessage: 'Search providers...',
|
||||
},
|
||||
noMatch: {
|
||||
id: 'providerGrid.noMatch',
|
||||
defaultMessage: 'No providers match "{query}"',
|
||||
},
|
||||
});
|
||||
|
||||
const GridLayout = memo(function GridLayout({ children }: { children: React.ReactNode }) {
|
||||
@@ -97,6 +106,7 @@ function ProviderCards({
|
||||
onModelSelected?: (model?: string) => void;
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [configuringProvider, setConfiguringProvider] = useState<ProviderDetails | null>(null);
|
||||
const [showCustomProviderModal, setShowCustomProviderModal] = useState(false);
|
||||
const [showSwitchModelModal, setShowSwitchModelModal] = useState(false);
|
||||
@@ -232,6 +242,8 @@ function ProviderCards({
|
||||
[refreshProviders]
|
||||
);
|
||||
|
||||
const query = searchQuery.trim().toLowerCase();
|
||||
|
||||
const providerCards = useMemo(() => {
|
||||
// providers needs to be an array
|
||||
const providersArray = Array.isArray(providers) ? providers : [];
|
||||
@@ -239,7 +251,14 @@ function ProviderCards({
|
||||
const sortedProviders = [...providersArray].sort((a, b) =>
|
||||
a.metadata.display_name.localeCompare(b.metadata.display_name)
|
||||
);
|
||||
const cards = sortedProviders.map((provider) => (
|
||||
const filteredProviders = query
|
||||
? sortedProviders.filter(
|
||||
(provider) =>
|
||||
provider.metadata.display_name.toLowerCase().includes(query) ||
|
||||
provider.metadata.description.toLowerCase().includes(query)
|
||||
)
|
||||
: sortedProviders;
|
||||
const cards = filteredProviders.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.name}
|
||||
provider={provider}
|
||||
@@ -254,7 +273,18 @@ function ProviderCards({
|
||||
);
|
||||
|
||||
return cards;
|
||||
}, [providers, isOnboarding, configureProviderViaModal, handleProviderLaunchWithModelSelection]);
|
||||
}, [
|
||||
providers,
|
||||
query,
|
||||
isOnboarding,
|
||||
configureProviderViaModal,
|
||||
handleProviderLaunchWithModelSelection,
|
||||
]);
|
||||
|
||||
const hasNoMatches =
|
||||
query.length > 0 &&
|
||||
providerCards.length === 1 &&
|
||||
(Array.isArray(providers) ? providers.length : 0) > 0;
|
||||
|
||||
const initialData = editingProvider && {
|
||||
engine: editingProvider.config.engine,
|
||||
@@ -277,7 +307,25 @@ function ProviderCards({
|
||||
: intl.formatMessage(i18n.addProviderTitle);
|
||||
return (
|
||||
<>
|
||||
{providerCards}
|
||||
<div className="mx-auto mb-4 max-w-md px-1">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" />
|
||||
<Input
|
||||
type="search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder={intl.formatMessage(i18n.searchPlaceholder)}
|
||||
className="pl-9"
|
||||
data-testid="provider-search-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<GridLayout>{providerCards}</GridLayout>
|
||||
{hasNoMatches && (
|
||||
<div className="mt-2 text-center text-sm text-text-secondary">
|
||||
{intl.formatMessage(i18n.noMatch, { query: searchQuery.trim() })}
|
||||
</div>
|
||||
)}
|
||||
<Dialog open={showCustomProviderModal} onOpenChange={handleCloseModal}>
|
||||
<DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
@@ -330,14 +378,12 @@ export default function ProviderGrid({
|
||||
onModelSelected?: (model?: string) => void;
|
||||
}) {
|
||||
return (
|
||||
<GridLayout>
|
||||
<ProviderCards
|
||||
providers={providers}
|
||||
isOnboarding={isOnboarding}
|
||||
refreshProviders={refreshProviders}
|
||||
setView={setView}
|
||||
onModelSelected={onModelSelected}
|
||||
/>
|
||||
</GridLayout>
|
||||
<ProviderCards
|
||||
providers={providers}
|
||||
isOnboarding={isOnboarding}
|
||||
refreshProviders={refreshProviders}
|
||||
setView={setView}
|
||||
onModelSelected={onModelSelected}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Keine Apps verfügbar"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Erneut versuchen"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Erneut versuchen"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "Apps"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Aus Vorlage oder manuelle Einrichtung"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Keine Anbieter entsprechen \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Anbieter suchen..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName}-Logo"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "From template or manual setup"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "No providers match \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Search providers..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName} logo"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Desde plantilla o configuración manual"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Ningún proveedor coincide con \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Buscar proveedores..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logotipo de {providerName}"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Aucune application disponible"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Réessayer"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Réessayer"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "Applications"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "À partir d'un modèle ou d'une configuration manuelle"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Aucun fournisseur ne correspond à \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Rechercher des fournisseurs..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logo {providerName}"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "टेम्प्लेट या मैन्युअल सेटअप से"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "\"{query}\" से मेल खाने वाला कोई प्रदाता नहीं"
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "प्रदाता खोजें..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName} लोगो"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Tidak ada aplikasi yang tersedia"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Coba lagi"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Coba lagi"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "Aplikasi"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Dari templat atau penyiapan manual"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Tidak ada penyedia yang cocok dengan \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Cari penyedia..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logo {providerName}"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Nessuna app disponibile"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Riprova"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Riprova"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "App"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Da modello o configurazione manuale"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Nessun provider corrisponde a \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Cerca provider..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logo {providerName}"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "テンプレートまたは手動設定から"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "「{query}」に一致するプロバイダーはありません"
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "プロバイダーを検索..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName}のロゴ"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "템플릿 또는 수동 설정으로 시작"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "\"{query}\"와 일치하는 제공업체가 없습니다"
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "제공업체 검색..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName} 로고"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Tiada apl tersedia"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Cuba semula"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Cuba semula"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "Apl"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Daripada templat atau persediaan manual"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Tiada penyedia yang sepadan dengan \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Cari penyedia..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logo {providerName}"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Nenhuma aplicação disponível"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Tentar novamente"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Tentar novamente"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "Aplicações"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "A partir de um modelo ou configuração manual"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Nenhum fornecedor corresponde a \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Pesquisar fornecedores..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logótipo de {providerName}"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Из шаблона или вручную"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Нет провайдеров, соответствующих \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Поиск провайдеров..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Логотип {providerName}"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Şablondan veya manuel kurulumdan"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "\"{query}\" ile eşleşen sağlayıcı yok"
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Sağlayıcı ara..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName} logosu"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "Không có ứng dụng nào"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Thử lại"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "Thử lại"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "Ứng dụng"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "Từ mẫu hoặc thiết lập thủ công"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "Không có nhà cung cấp nào khớp với \"{query}\""
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "Tìm kiếm nhà cung cấp..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "Logo {providerName}"
|
||||
},
|
||||
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "使用模板或手动设置"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "没有匹配 \"{query}\" 的提供商"
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "搜索提供商..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName} 徽标"
|
||||
},
|
||||
|
||||
@@ -50,15 +50,15 @@
|
||||
"appsView.noAppsTitle": {
|
||||
"defaultMessage": "沒有可用的應用程式"
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "重試"
|
||||
},
|
||||
"appsView.retiredChatApp": {
|
||||
"defaultMessage": "Chat app retired"
|
||||
},
|
||||
"appsView.retiredChatAppDetail": {
|
||||
"defaultMessage": "We removed this feature because MCP sampling is no longer supported."
|
||||
},
|
||||
"appsView.retry": {
|
||||
"defaultMessage": "重試"
|
||||
},
|
||||
"appsView.title": {
|
||||
"defaultMessage": "應用程式"
|
||||
},
|
||||
@@ -2816,6 +2816,12 @@
|
||||
"providerGrid.fromTemplateOrManual": {
|
||||
"defaultMessage": "從範本或手動設定"
|
||||
},
|
||||
"providerGrid.noMatch": {
|
||||
"defaultMessage": "沒有符合 \"{query}\" 的提供者"
|
||||
},
|
||||
"providerGrid.searchPlaceholder": {
|
||||
"defaultMessage": "搜尋提供者..."
|
||||
},
|
||||
"providerLogo.alt": {
|
||||
"defaultMessage": "{providerName} 標誌"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user