From 81ed89b84fe11e25bc7db7c9c7a9d5aea4ffd31b Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Thu, 12 Feb 2026 19:29:24 +0800 Subject: [PATCH] Vendor can show more --- web/pages/ASRLibrary.tsx | 16 ++++++++++++---- web/pages/LLMLibrary.tsx | 16 ++++++++++++---- web/pages/VoiceLibrary.tsx | 18 +++++++++++++----- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/web/pages/ASRLibrary.tsx b/web/pages/ASRLibrary.tsx index b5a6a24..03f54ae 100644 --- a/web/pages/ASRLibrary.tsx +++ b/web/pages/ASRLibrary.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Search, Filter, Plus, Trash2, Key, Server, Ear, Globe, Languages, Pencil, Mic, Square, Upload } from 'lucide-react'; import { Button, Input, Select, TableHeader, TableRow, TableHead, TableCell, Dialog, Badge, LibraryPageShell, TableStatusRow, LibraryActionCell } from '../components/UI'; import { ASRModel } from '../types'; @@ -85,7 +85,7 @@ const convertRecordedBlobToWav = async (blob: Blob): Promise => { export const ASRLibraryPage: React.FC = () => { const [models, setModels] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - const [vendorFilter, setVendorFilter] = useState('OpenAI Compatible'); + const [vendorFilter, setVendorFilter] = useState('all'); const [langFilter, setLangFilter] = useState('all'); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [editingModel, setEditingModel] = useState(null); @@ -108,10 +108,15 @@ export const ASRLibraryPage: React.FC = () => { loadModels(); }, []); + const vendorOptions = useMemo( + () => Array.from(new Set(models.map((m) => String(m.vendor || '').trim()).filter(Boolean))).sort(), + [models] + ); + const filteredModels = models.filter((m) => { const q = searchTerm.toLowerCase(); const matchesSearch = m.name.toLowerCase().includes(q) || (m.modelName || '').toLowerCase().includes(q); - const matchesVendor = m.vendor === vendorFilter; + const matchesVendor = vendorFilter === 'all' || m.vendor === vendorFilter; const matchesLang = langFilter === 'all' || m.language === langFilter || (langFilter !== 'all' && m.language === 'Multi-lingual'); return matchesSearch && matchesVendor && matchesLang; }); @@ -159,7 +164,10 @@ export const ASRLibraryPage: React.FC = () => { value={vendorFilter} onChange={(e) => setVendorFilter(e.target.value)} > - + + {vendorOptions.map((vendor) => ( + + ))}
diff --git a/web/pages/LLMLibrary.tsx b/web/pages/LLMLibrary.tsx index 808ef84..9e4f1f4 100644 --- a/web/pages/LLMLibrary.tsx +++ b/web/pages/LLMLibrary.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Search, Filter, Plus, BrainCircuit, Trash2, Key, Settings2, Server, Thermometer, Pencil, Play } from 'lucide-react'; import { Button, Input, Select, TableHeader, TableRow, TableHead, TableCell, Dialog, Badge, LibraryPageShell, TableStatusRow, LibraryActionCell } from '../components/UI'; import { LLMModel } from '../types'; @@ -13,7 +13,7 @@ const maskApiKey = (key?: string) => { export const LLMLibraryPage: React.FC = () => { const [models, setModels] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - const [vendorFilter, setVendorFilter] = useState('OpenAI Compatible'); + const [vendorFilter, setVendorFilter] = useState('all'); const [typeFilter, setTypeFilter] = useState('all'); const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [editingModel, setEditingModel] = useState(null); @@ -35,13 +35,18 @@ export const LLMLibraryPage: React.FC = () => { load(); }, []); + const vendorOptions = useMemo( + () => Array.from(new Set(models.map((m) => String(m.vendor || '').trim()).filter(Boolean))).sort(), + [models] + ); + const filteredModels = models.filter((m) => { const q = searchTerm.toLowerCase(); const matchesSearch = m.name.toLowerCase().includes(q) || (m.modelName || '').toLowerCase().includes(q) || (m.baseUrl || '').toLowerCase().includes(q); - const matchesVendor = m.vendor === vendorFilter; + const matchesVendor = vendorFilter === 'all' || m.vendor === vendorFilter; const matchesType = typeFilter === 'all' || m.type === typeFilter; return matchesSearch && matchesVendor && matchesType; }); @@ -89,7 +94,10 @@ export const LLMLibraryPage: React.FC = () => { value={vendorFilter} onChange={(e) => setVendorFilter(e.target.value)} > - + + {vendorOptions.map((vendor) => ( + + ))}
diff --git a/web/pages/VoiceLibrary.tsx b/web/pages/VoiceLibrary.tsx index 5c94b1c..4932463 100644 --- a/web/pages/VoiceLibrary.tsx +++ b/web/pages/VoiceLibrary.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useEffect, useMemo, useState, useRef } from 'react'; import { Search, Mic2, Play, Pause, Upload, Filter, Plus, Volume2, Pencil, Trash2 } from 'lucide-react'; import { Button, Input, Select, TableHeader, TableRow, TableHead, TableCell, Dialog, Badge, LibraryPageShell, TableStatusRow, LibraryActionCell } from '../components/UI'; import { Voice } from '../types'; @@ -15,7 +15,7 @@ const buildOpenAICompatibleVoiceKey = (rawId: string, model: string): string => export const VoiceLibraryPage: React.FC = () => { const [voices, setVoices] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - const [vendorFilter, setVendorFilter] = useState<'OpenAI Compatible'>('OpenAI Compatible'); + const [vendorFilter, setVendorFilter] = useState('all'); const [genderFilter, setGenderFilter] = useState<'all' | 'Male' | 'Female'>('all'); const [langFilter, setLangFilter] = useState<'all' | 'zh' | 'en'>('all'); @@ -42,9 +42,14 @@ export const VoiceLibraryPage: React.FC = () => { loadVoices(); }, []); + const vendorOptions = useMemo( + () => Array.from(new Set(voices.map((v) => String(v.vendor || '').trim()).filter(Boolean))).sort(), + [voices] + ); + const filteredVoices = voices.filter((voice) => { const matchesSearch = voice.name.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesVendor = voice.vendor === vendorFilter; + const matchesVendor = vendorFilter === 'all' || voice.vendor === vendorFilter; const matchesGender = genderFilter === 'all' || voice.gender === genderFilter; const matchesLang = langFilter === 'all' || voice.language === langFilter; return matchesSearch && matchesVendor && matchesGender && matchesLang; @@ -135,9 +140,12 @@ export const VoiceLibraryPage: React.FC = () => {