mirror of
https://github.com/samanhappy/mcphub.git
synced 2025-12-24 02:39:19 -05:00
Add semantic search UI to servers management page
Co-authored-by: samanhappy <2755122+samanhappy@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import EditServerForm from '@/components/EditServerForm';
|
||||
import { useServerData } from '@/hooks/useServerData';
|
||||
import DxtUploadForm from '@/components/DxtUploadForm';
|
||||
import JSONImportForm from '@/components/JSONImportForm';
|
||||
import { apiGet } from '@/utils/fetchInterceptor';
|
||||
|
||||
const ServersPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -27,6 +28,10 @@ const ServersPage: React.FC = () => {
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [showDxtUpload, setShowDxtUpload] = useState(false);
|
||||
const [showJsonImport, setShowJsonImport] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [similarityThreshold, setSimilarityThreshold] = useState(0.65);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState<Server[] | null>(null);
|
||||
|
||||
const handleEditClick = async (server: Server) => {
|
||||
const fullServerData = await handleServerEdit(server);
|
||||
@@ -63,6 +68,31 @@ const ServersPage: React.FC = () => {
|
||||
triggerRefresh();
|
||||
};
|
||||
|
||||
const handleSemanticSearch = async () => {
|
||||
if (!searchQuery.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSearching(true);
|
||||
try {
|
||||
const result = await apiGet(`/servers/search?query=${encodeURIComponent(searchQuery)}&threshold=${similarityThreshold}`);
|
||||
if (result.success && result.data) {
|
||||
setSearchResults(result.data.servers);
|
||||
} else {
|
||||
setError(result.message || 'Search failed');
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Search failed');
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClearSearch = () => {
|
||||
setSearchQuery('');
|
||||
setSearchResults(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
@@ -116,6 +146,72 @@ const ServersPage: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Semantic Search Section */}
|
||||
<div className="bg-white shadow rounded-lg p-6 mb-6 page-card">
|
||||
<div className="space-y-4">
|
||||
<div className="flex space-x-4">
|
||||
<div className="flex-grow">
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSemanticSearch()}
|
||||
placeholder={t('pages.servers.semanticSearchPlaceholder')}
|
||||
className="shadow appearance-none border border-gray-200 rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline form-input"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSemanticSearch}
|
||||
disabled={isSearching || !searchQuery.trim()}
|
||||
className="px-6 py-2 bg-blue-100 text-blue-800 rounded hover:bg-blue-200 flex items-center btn-primary transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{isSearching ? (
|
||||
<svg className="animate-spin h-4 w-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clipRule="evenodd" />
|
||||
</svg>
|
||||
)}
|
||||
{t('pages.servers.searchButton')}
|
||||
</button>
|
||||
{searchResults && (
|
||||
<button
|
||||
onClick={handleClearSearch}
|
||||
className="border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded hover:bg-gray-50 btn-secondary transition-all duration-200"
|
||||
>
|
||||
{t('pages.servers.clearSearch')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<label className="text-sm text-gray-700 font-medium min-w-max">{t('pages.servers.similarityThreshold')}: {similarityThreshold.toFixed(2)}</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value={similarityThreshold}
|
||||
onChange={(e) => setSimilarityThreshold(parseFloat(e.target.value))}
|
||||
className="flex-grow h-2 bg-blue-200 rounded-lg appearance-none cursor-pointer"
|
||||
/>
|
||||
<span className="text-xs text-gray-500">{t('pages.servers.similarityThresholdHelp')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{searchResults && (
|
||||
<div className="mb-4 bg-blue-50 border-l-4 border-blue-500 p-4 rounded">
|
||||
<p className="text-blue-800">
|
||||
{searchResults.length > 0
|
||||
? t('pages.servers.searchResults', { count: searchResults.length })
|
||||
: t('pages.servers.noSearchResults')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="mb-6 bg-red-50 border-l-4 border-red-500 p-4 rounded shadow-sm error-box">
|
||||
<div className="flex items-center justify-between">
|
||||
@@ -145,13 +241,13 @@ const ServersPage: React.FC = () => {
|
||||
<p className="text-gray-600">{t('app.loading')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : servers.length === 0 ? (
|
||||
) : (searchResults ? searchResults : servers).length === 0 ? (
|
||||
<div className="bg-white shadow rounded-lg p-6 empty-state">
|
||||
<p className="text-gray-600">{t('app.noServers')}</p>
|
||||
<p className="text-gray-600">{searchResults ? t('pages.servers.noSearchResults') : t('app.noServers')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{servers.map((server, index) => (
|
||||
{(searchResults || servers).map((server, index) => (
|
||||
<ServerCard
|
||||
key={index}
|
||||
server={server}
|
||||
|
||||
@@ -268,7 +268,15 @@
|
||||
"recentServers": "Recent Servers"
|
||||
},
|
||||
"servers": {
|
||||
"title": "Servers Management"
|
||||
"title": "Servers Management",
|
||||
"semanticSearch": "Intelligent search for tools...",
|
||||
"semanticSearchPlaceholder": "Describe the functionality you need, e.g.: maps, weather, file processing",
|
||||
"similarityThreshold": "Similarity Threshold",
|
||||
"similarityThresholdHelp": "Higher values return more precise results, lower values return broader matches",
|
||||
"searchButton": "Search",
|
||||
"clearSearch": "Clear Search",
|
||||
"searchResults": "Found {{count}} matching server(s)",
|
||||
"noSearchResults": "No matching servers found"
|
||||
},
|
||||
"groups": {
|
||||
"title": "Group Management"
|
||||
|
||||
@@ -268,7 +268,15 @@
|
||||
"recentServers": "Serveurs récents"
|
||||
},
|
||||
"servers": {
|
||||
"title": "Gestion des serveurs"
|
||||
"title": "Gestion des serveurs",
|
||||
"semanticSearch": "Recherche intelligente d'outils...",
|
||||
"semanticSearchPlaceholder": "Décrivez la fonctionnalité dont vous avez besoin, par ex. : cartes, météo, traitement de fichiers",
|
||||
"similarityThreshold": "Seuil de similarité",
|
||||
"similarityThresholdHelp": "Des valeurs plus élevées renvoient des résultats plus précis, des valeurs plus faibles des correspondances plus larges",
|
||||
"searchButton": "Rechercher",
|
||||
"clearSearch": "Effacer la recherche",
|
||||
"searchResults": "{{count}} serveur(s) correspondant(s) trouvé(s)",
|
||||
"noSearchResults": "Aucun serveur correspondant trouvé"
|
||||
},
|
||||
"groups": {
|
||||
"title": "Gestion des groupes"
|
||||
|
||||
@@ -269,7 +269,15 @@
|
||||
"recentServers": "最近的服务器"
|
||||
},
|
||||
"servers": {
|
||||
"title": "服务器管理"
|
||||
"title": "服务器管理",
|
||||
"semanticSearch": "智能搜索工具...",
|
||||
"semanticSearchPlaceholder": "描述您需要的功能,例如:地图、天气、文件处理",
|
||||
"similarityThreshold": "相似度阈值",
|
||||
"similarityThresholdHelp": "较高值返回更精确结果,较低值返回更广泛匹配",
|
||||
"searchButton": "搜索",
|
||||
"clearSearch": "清除搜索",
|
||||
"searchResults": "找到 {{count}} 个匹配的服务器",
|
||||
"noSearchResults": "未找到匹配的服务器"
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置",
|
||||
|
||||
Reference in New Issue
Block a user