You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

109 lines
4.0 KiB

'use client';
import { search, SearchResult } from '@/lib/api';
import { useState, useCallback } from 'react';
import { Search, Loader2, FileText, Tag } from 'lucide-react';
import Link from 'next/link';
export default function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
const [loading, setLoading] = useState(false);
const [queryTime, setQueryTime] = useState<number | null>(null);
const [error, setError] = useState('');
const handleSearch = useCallback(async (e: React.FormEvent) => {
e.preventDefault();
if (!query.trim()) return;
setLoading(true);
setError('');
try {
const res = await search(query.trim());
setResults(res.results);
setQueryTime(res.query_time_ms);
} catch (err) {
setError('Search failed. Is the API running?');
} finally {
setLoading(false);
}
}, [query]);
return (
<div className="max-w-3xl mx-auto">
<h1 className="text-2xl font-bold text-slate-900 mb-6">Search</h1>
<form onSubmit={handleSearch} className="flex gap-2 mb-6">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" size={18} />
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search your knowledge base..."
className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-brain-500 focus:border-transparent"
/>
</div>
<button
type="submit"
disabled={loading || !query.trim()}
className="px-5 py-2.5 bg-brain-600 text-white rounded-lg font-medium hover:bg-brain-700 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{loading && <Loader2 size={16} className="animate-spin" />}
Search
</button>
</form>
{error && (
<div className="mb-4 p-3 bg-red-50 text-red-700 rounded-lg border border-red-200">{error}</div>
)}
{queryTime !== null && results.length > 0 && (
<p className="text-sm text-slate-400 mb-4">
{results.length} results in {queryTime}ms
</p>
)}
<div className="space-y-4">
{results.map((result) => (
<Link
key={result.chunk_id}
href={`/documents/${result.document_id}`}
className="block bg-white border border-slate-200 rounded-xl p-5 hover:border-brain-400 hover:shadow-sm transition-all"
>
<div className="flex items-start justify-between gap-3 mb-2">
<div className="flex items-center gap-2">
<FileText size={16} className="text-brain-500 shrink-0 mt-0.5" />
<h3 className="font-semibold text-slate-900">{result.title}</h3>
</div>
<span className="text-xs text-slate-400 shrink-0 bg-slate-100 px-2 py-0.5 rounded-full">
{(result.score * 100).toFixed(0)}%
</span>
</div>
{result.highlight && (
<p
className="text-sm text-slate-600 mb-3 line-clamp-3"
dangerouslySetInnerHTML={{ __html: result.highlight }}
/>
)}
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs text-slate-400">{result.path}</span>
{result.tags.slice(0, 4).map((tag) => (
<span key={tag} className="text-xs bg-brain-50 text-brain-700 px-2 py-0.5 rounded-full flex items-center gap-1">
<Tag size={10} />
{tag}
</span>
))}
</div>
</Link>
))}
{results.length === 0 && queryTime !== null && !loading && (
<p className="text-center text-slate-400 py-12">No results found for "{query}"</p>
)}
</div>
</div>
);
}

Powered by TurnKey Linux.