Spaces:
Build error
Build error
| import { useState } from 'react'; | |
| import { Search, Globe, Zap, ExternalLink } from 'lucide-react'; | |
| export default function SearchEngine() { | |
| const [query, setQuery] = useState(''); | |
| const [engine, setEngine] = useState('tavily'); | |
| const [results, setResults] = useState([]); | |
| const [loading, setLoading] = useState(false); | |
| const handleSearch = async (e) => { | |
| e.preventDefault(); | |
| if (!query.trim()) return; | |
| setLoading(true); | |
| setResults([]); | |
| try { | |
| const res = await fetch('/api/search', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ query, engine }), | |
| }); | |
| const data = await res.json(); | |
| setResults(data.results || []); | |
| } catch (err) { | |
| console.error('Search failed', err); | |
| setResults([]); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="bg-slate-800 rounded-xl p-6 shadow-xl border border-slate-700"> | |
| <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-4 gap-3"> | |
| <div className="flex items-center gap-2"> | |
| <Search className="w-5 h-5 text-yellow-500" /> | |
| <h2 className="text-lg font-semibold text-white">Unified Search</h2> | |
| </div> | |
| <div className="flex bg-slate-900 rounded-lg p-1 border border-slate-600 w-full sm:w-auto"> | |
| <button | |
| onClick={() => setEngine('tavily')} | |
| className={`flex-1 sm:flex-none px-3 py-1.5 rounded-md text-sm flex items-center justify-center gap-1.5 transition-all ${ | |
| engine === 'tavily' | |
| ? 'bg-yellow-600 text-white shadow-md' | |
| : 'text-slate-400 hover:text-white hover:bg-slate-800' | |
| }`} | |
| > | |
| <Zap className="w-3 h-3" /> Tavily | |
| </button> | |
| <button | |
| onClick={() => setEngine('searxng')} | |
| className={`flex-1 sm:flex-none px-3 py-1.5 rounded-md text-sm flex items-center justify-center gap-1.5 transition-all ${ | |
| engine === 'searxng' | |
| ? 'bg-yellow-600 text-white shadow-md' | |
| : 'text-slate-400 hover:text-white hover:bg-slate-800' | |
| }`} | |
| > | |
| <Globe className="w-3 h-3" /> SearXNG | |
| </button> | |
| </div> | |
| </div> | |
| <form onSubmit={handleSearch} className="relative mb-6"> | |
| <input | |
| type="text" | |
| value={query} | |
| onChange={(e) => setQuery(e.target.value)} | |
| placeholder={`Search using ${engine === 'tavily' ? 'Tavily AI' : 'SearXNG'}...`} | |
| className="w-full bg-slate-900 border border-slate-600 rounded-lg pl-10 pr-4 py-3 text-white placeholder-slate-500 focus:ring-2 focus:ring-yellow-500 focus:border-transparent outline-none transition-all" | |
| /> | |
| <button | |
| type="submit" | |
| disabled={loading} | |
| className="absolute right-2 top-2 bg-yellow-600 hover:bg-yellow-500 text-white p-1.5 rounded-md transition-colors disabled:opacity-50" | |
| > | |
| <Search className="w-5 h-5" /> | |
| </button> | |
| </form> | |
| <div className="space-y-3 max-h-96 overflow-y-auto pr-2"> | |
| {loading ? ( | |
| <div className="flex flex-col items-center justify-center py-12 text-slate-400 gap-2"> | |
| <div className="w-8 h-8 border-4 border-slate-700 border-t-yellow-500 rounded-full animate-spin" /> | |
| <span className="text-sm">Searching the web...</span> | |
| </div> | |
| ) : results.length > 0 ? ( | |
| results.map((result, i) => ( | |
| <a | |
| key={i} | |
| href={result.url} | |
| target="_blank" | |
| rel="noopener noreferrer" | |
| className="block p-4 bg-slate-900 rounded-lg border border-slate-700 hover:border-yellow-600/50 hover:bg-slate-850 hover:shadow-lg hover:shadow-yellow-900/10 transition-all group" | |
| > | |
| <div className="flex items-start justify-between gap-2"> | |
| <h3 className="text-blue-400 font-medium group-hover |