Skip to content

Commit 96dbff2

Browse files
authored
Merge pull request #50 from mcp-agents-ai/feat/leixu/mcp_server_categories
Feat/leixu/mcp server categories
2 parents 74699c7 + b7961fb commit 96dbff2

File tree

18 files changed

+522
-134
lines changed

18 files changed

+522
-134
lines changed
33.8 KB
Loading
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { useState, useRef, useEffect } from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { useLanguage } from '../contexts/LanguageContext';
4+
5+
type CategoryProps = {
6+
isMobile?: boolean;
7+
onSelectMobile?: () => void;
8+
};
9+
10+
export function CategoriesDropdown({ isMobile, onSelectMobile }: CategoryProps) {
11+
const [isCategoriesMenuOpen, setIsCategoriesMenuOpen] = useState(false);
12+
const categoriesMenuRef = useRef<HTMLDivElement>(null);
13+
const { t } = useLanguage();
14+
const [categoryKeys, setCategoryKeys] = useState<string[]>([]);
15+
const [isLoading, setIsLoading] = useState(true);
16+
17+
// Fetch categories from API
18+
useEffect(() => {
19+
const fetchCategories = async () => {
20+
try {
21+
setIsLoading(true);
22+
const response = await fetch('/v1/hub/server_categories');
23+
if (!response.ok) {
24+
throw new Error('Failed to fetch categories');
25+
}
26+
const data = await response.json();
27+
setCategoryKeys(data);
28+
} catch (error) {
29+
console.error('Error fetching categories:', error);
30+
// Show an empty menu if the API call fails
31+
setCategoryKeys([]);
32+
} finally {
33+
setIsLoading(false);
34+
}
35+
};
36+
37+
fetchCategories();
38+
}, []);
39+
40+
// Map category keys to their respective SVG icons
41+
const categoryIcons: Record<string, JSX.Element> = {
42+
"browser-automation": (
43+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
44+
<path strokeLinecap="round" strokeLinejoin="round" d="M16.5 12a4.5 4.5 0 1 1-9 0 4.5 4.5 0 0 1 9 0Zm0 0c0 1.657 1.007 3 2.25 3S21 13.657 21 12a9 9 0 1 0-2.636 6.364M16.5 12V8.25" />
45+
</svg>
46+
),
47+
"cloud-platforms": (
48+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
49+
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 15a4.5 4.5 0 0 0 4.5 4.5H18a3.75 3.75 0 0 0 1.332-7.257 3 3 0 0 0-3.758-3.848 5.25 5.25 0 0 0-10.233 2.33A4.502 4.502 0 0 0 2.25 15Z" />
50+
</svg>
51+
),
52+
"communication": (
53+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
54+
<path strokeLinecap="round" strokeLinejoin="round" d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" />
55+
</svg>
56+
),
57+
"databases": (
58+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
59+
<path strokeLinecap="round" strokeLinejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
60+
</svg>
61+
),
62+
"file-systems": (
63+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
64+
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
65+
</svg>
66+
),
67+
"knowledge-memory": (
68+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
69+
<path strokeLinecap="round" strokeLinejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
70+
</svg>
71+
),
72+
"location-services": (
73+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
74+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
75+
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z" />
76+
</svg>
77+
),
78+
"monitoring": (
79+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
80+
<path strokeLinecap="round" strokeLinejoin="round" d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z" />
81+
</svg>
82+
),
83+
"search": (
84+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
85+
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
86+
</svg>
87+
),
88+
"version-control": (
89+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
90+
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" />
91+
</svg>
92+
),
93+
"integrations": (
94+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
95+
<path strokeLinecap="round" strokeLinejoin="round" d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
96+
</svg>
97+
),
98+
"other-tools": (
99+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
100+
<path strokeLinecap="round" strokeLinejoin="round" d="M11.42 15.17 17.25 21A2.652 2.652 0 0 0 21 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 1 1-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 0 0 4.486-6.336l-3.276 3.277a3.004 3.004 0 0 1-2.25-2.25l3.276-3.276a4.5 4.5 0 0 0-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437 1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008Z" />
101+
</svg>
102+
),
103+
"developer-tools": (
104+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-5 h-5 mr-2">
105+
<path strokeLinecap="round" strokeLinejoin="round" d="M6.75 7.5l3 2.25-3 2.25m4.5 0h3m-9 8.25h13.5A2.25 2.25 0 0021 18V6a2.25 2.25 0 00-2.25-2.25H5.25A2.25 2.25 0 003 6v12a2.25 2.25 0 002.25 2.25z" />
106+
</svg>
107+
)
108+
};
109+
110+
// Close the dropdown menu when selecting a category on mobile
111+
const handleCategoryClick = () => {
112+
if (isMobile && onSelectMobile) {
113+
onSelectMobile();
114+
}
115+
setIsCategoriesMenuOpen(false);
116+
};
117+
118+
// Handle clicks outside the menu to close it
119+
useEffect(() => {
120+
const handleClickOutside = (event: MouseEvent) => {
121+
if (
122+
isCategoriesMenuOpen &&
123+
categoriesMenuRef.current &&
124+
!categoriesMenuRef.current.contains(event.target as Node) &&
125+
!(event.target as Element).closest('button.categories-toggle')
126+
) {
127+
setIsCategoriesMenuOpen(false);
128+
}
129+
};
130+
131+
document.addEventListener('mousedown', handleClickOutside);
132+
133+
return () => {
134+
document.removeEventListener('mousedown', handleClickOutside);
135+
};
136+
}, [isCategoriesMenuOpen]);
137+
138+
if (isMobile) {
139+
// Mobile version
140+
return (
141+
<div className="border-b border-gray-100 pb-2 mb-2">
142+
<div className="text-gray-700 font-medium text-base py-2 flex items-center">
143+
<svg
144+
xmlns="http://www.w3.org/2000/svg"
145+
fill="none"
146+
viewBox="0 0 24 24"
147+
strokeWidth="1.5"
148+
stroke="currentColor"
149+
className="w-5 h-5 mr-2"
150+
>
151+
<path
152+
strokeLinecap="round"
153+
strokeLinejoin="round"
154+
d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"
155+
/>
156+
</svg>
157+
{t('nav.categories')}
158+
</div>
159+
<div className="flex flex-col space-y-2 mt-1 pl-4">
160+
{isLoading ? (
161+
<div className="text-gray-500 text-sm pl-2">Loading categories...</div>
162+
) : (
163+
categoryKeys.map((key, index) => (
164+
<Link
165+
key={index}
166+
to={`/category/${key}`}
167+
className="text-gray-600 hover:text-indigo-600 text-sm flex items-center"
168+
onClick={handleCategoryClick}
169+
>
170+
{categoryIcons[key]}
171+
{t(`category.${key}`)}
172+
</Link>
173+
))
174+
)}
175+
</div>
176+
</div>
177+
);
178+
}
179+
180+
// Desktop version
181+
return (
182+
<div className="relative">
183+
<button
184+
className="categories-toggle flex items-center text-gray-700 hover:text-indigo-600 font-medium"
185+
onClick={() => setIsCategoriesMenuOpen(!isCategoriesMenuOpen)}
186+
>
187+
<svg
188+
xmlns="http://www.w3.org/2000/svg"
189+
fill="none"
190+
viewBox="0 0 24 24"
191+
strokeWidth="1.5"
192+
stroke="currentColor"
193+
className="w-5 h-5 mr-1"
194+
>
195+
<path
196+
strokeLinecap="round"
197+
strokeLinejoin="round"
198+
d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"
199+
/>
200+
</svg>
201+
{t('nav.categories')}
202+
<svg
203+
xmlns="http://www.w3.org/2000/svg"
204+
fill="none"
205+
viewBox="0 0 24 24"
206+
strokeWidth="1.5"
207+
stroke="currentColor"
208+
className="w-4 h-4 ml-1"
209+
>
210+
<path
211+
strokeLinecap="round"
212+
strokeLinejoin="round"
213+
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
214+
/>
215+
</svg>
216+
</button>
217+
218+
{/* Categories dropdown menu */}
219+
{isCategoriesMenuOpen && (
220+
<div
221+
className="absolute left-0 mt-2 w-60 bg-white rounded-md shadow-lg z-20 py-2"
222+
ref={categoriesMenuRef}
223+
>
224+
<div className="grid grid-cols-1 gap-1">
225+
{isLoading ? (
226+
<div className="px-4 py-2 text-sm text-gray-500">Loading categories...</div>
227+
) : (
228+
categoryKeys.map((key, index) => (
229+
<Link
230+
key={index}
231+
to={`/category/${key}`}
232+
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-indigo-600"
233+
onClick={handleCategoryClick}
234+
>
235+
<div className="flex items-center">
236+
{categoryIcons[key]}
237+
{t(`category.${key}`)}
238+
</div>
239+
</Link>
240+
))
241+
)}
242+
</div>
243+
</div>
244+
)}
245+
</div>
246+
);
247+
}

0 commit comments

Comments
 (0)