fazeel007's picture
Fix index
f36d1f9
import React, { useState } from 'react';
import { Search, Zap, Database, Loader2, ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Separator } from '@/components/ui/separator';
interface VectorSearchResult {
id: string;
title: string;
content: string;
source: string;
relevanceScore: number;
rank: number;
snippet: string;
}
interface SearchResults {
success: boolean;
query: string;
indexName: string;
results: VectorSearchResult[];
totalFound: number;
searchTime?: number;
error?: string;
}
export default function VectorSearch() {
const [query, setQuery] = useState('');
const [indexName, setIndexName] = useState('research_papers_clean_v2');
const [maxResults, setMaxResults] = useState(10);
const [isSearching, setIsSearching] = useState(false);
const [searchResults, setSearchResults] = useState<SearchResults | null>(null);
const [comparisonMode, setComparisonMode] = useState(false);
const [traditionalResults, setTraditionalResults] = useState<any>(null);
const handleVectorSearch = async () => {
if (!query.trim()) return;
setIsSearching(true);
try {
const response = await fetch('/api/documents/search/vector', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: query.trim(),
indexName,
maxResults
})
});
const result = await response.json();
setSearchResults(result);
// If comparison mode is enabled, also run traditional search
if (comparisonMode) {
await runTraditionalSearch();
}
} catch (error) {
console.error('Vector search error:', error);
setSearchResults({
success: false,
query: query.trim(),
indexName,
results: [],
totalFound: 0,
error: error instanceof Error ? error.message : 'Search failed'
});
} finally {
setIsSearching(false);
}
};
const runTraditionalSearch = async () => {
try {
const response = await fetch('/api/search', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: query.trim(),
searchType: 'keyword',
limit: maxResults,
offset: 0
})
});
const result = await response.json();
setTraditionalResults(result);
} catch (error) {
console.error('Traditional search error:', error);
setTraditionalResults(null);
}
};
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !isSearching) {
handleVectorSearch();
}
};
const formatRelevanceScore = (score: number): string => {
return (score * 100).toFixed(1) + '%';
};
const getScoreColor = (score: number): string => {
if (score >= 0.8) return 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200';
if (score >= 0.6) return 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200';
if (score >= 0.4) return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200';
return 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200';
};
return (
<div className="space-y-6">
{/* Search Interface */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-blue-500" />
Vector Search (Modal + FAISS)
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* Search Input */}
<div className="space-y-2">
<Label htmlFor="vector-query">Search Query</Label>
<div className="flex gap-2">
<Input
id="vector-query"
placeholder="Enter your search query for semantic similarity matching..."
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={handleKeyPress}
className="flex-1"
/>
<Button
onClick={handleVectorSearch}
disabled={isSearching || !query.trim()}
>
{isSearching ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Search className="w-4 h-4" />
)}
</Button>
</div>
</div>
{/* Search Options */}
<div className="grid grid-cols-3 gap-4">
<div>
<Label htmlFor="index-name">Vector Index</Label>
<Select value={indexName} onValueChange={setIndexName}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="research_papers_clean_v2">Research Papers (Clean)</SelectItem>
<SelectItem value="main_index">Main Index (Legacy - has uploaded docs)</SelectItem>
<SelectItem value="academic_index">Academic Papers</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="max-results">Max Results</Label>
<Select value={maxResults.toString()} onValueChange={(value) => setMaxResults(parseInt(value))}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="5">5 results</SelectItem>
<SelectItem value="10">10 results</SelectItem>
<SelectItem value="20">20 results</SelectItem>
<SelectItem value="50">50 results</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-end">
<Button
variant="outline"
onClick={() => setComparisonMode(!comparisonMode)}
className="w-full"
>
<Database className="w-4 h-4 mr-2" />
{comparisonMode ? 'Comparison: ON' : 'Compare with Keyword'}
</Button>
</div>
</div>
{/* Search Info */}
<Alert>
<Database className="w-4 h-4" />
<AlertDescription>
Vector search uses Modal.com's distributed FAISS implementation for high-performance semantic similarity matching.
Enable comparison mode to see differences between vector and traditional keyword search.
</AlertDescription>
</Alert>
</CardContent>
</Card>
{/* Search Results */}
{searchResults && (
<div className="grid grid-cols-1 gap-6">
{/* Vector Search Results */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Zap className="w-5 h-5 text-blue-500" />
Vector Search Results
</CardTitle>
<div className="flex items-center gap-4 text-sm text-gray-500">
{searchResults.searchTime && (
<span>Search time: {(searchResults.searchTime * 1000).toFixed(0)}ms</span>
)}
<Badge variant="outline">
{searchResults.totalFound} results found
</Badge>
</div>
</div>
</CardHeader>
<CardContent>
{searchResults.success ? (
<div className="space-y-4">
{searchResults.results.length === 0 ? (
<div className="text-center py-8 text-gray-500">
<Database className="w-12 h-12 mx-auto mb-4 opacity-50" />
<p>No results found in vector index.</p>
<p className="text-sm">Try uploading and processing some documents first.</p>
</div>
) : (
searchResults.results.map((result, index) => (
<div key={result.id} className="border rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
<div className="flex items-start justify-between mb-2">
<div className="flex items-center gap-3">
<Badge variant="outline" className="text-xs">
#{result.rank}
</Badge>
<Badge className={getScoreColor(result.relevanceScore)}>
{formatRelevanceScore(result.relevanceScore)}
</Badge>
</div>
<span className="text-xs text-gray-500">ID: {result.id}</span>
</div>
<h3 className="font-semibold text-lg mb-2">{result.title}</h3>
<p className="text-gray-600 dark:text-gray-300 mb-3 leading-relaxed">
{result.snippet}
</p>
<div className="flex items-center justify-between text-sm text-gray-500">
<span>{result.source}</span>
<ArrowRight className="w-4 h-4" />
</div>
</div>
))
)}
</div>
) : (
<Alert variant="destructive">
<AlertDescription>
{searchResults.error || 'Vector search failed'}
</AlertDescription>
</Alert>
)}
</CardContent>
</Card>
{/* Traditional Search Results (if comparison mode) */}
{comparisonMode && traditionalResults && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Search className="w-5 h-5 text-gray-500" />
Traditional Search Results (for comparison)
</CardTitle>
</CardHeader>
<CardContent>
{traditionalResults.results && traditionalResults.results.length > 0 ? (
<div className="space-y-4">
{traditionalResults.results.slice(0, maxResults).map((result: any, index: number) => (
<div key={result.id} className="border rounded-lg p-4 opacity-75">
<div className="flex items-start justify-between mb-2">
<Badge variant="outline" className="text-xs">
#{index + 1}
</Badge>
<Badge variant="secondary">
{formatRelevanceScore(result.relevanceScore || 0)}
</Badge>
</div>
<h3 className="font-semibold text-lg mb-2">{result.title}</h3>
<p className="text-gray-600 dark:text-gray-300 mb-3 leading-relaxed">
{result.snippet}
</p>
<div className="text-sm text-gray-500">
{result.source}
</div>
</div>
))}
</div>
) : (
<div className="text-center py-4 text-gray-500">
<p>No traditional search results found.</p>
</div>
)}
</CardContent>
</Card>
)}
</div>
)}
{/* Help Text */}
{!searchResults && (
<Card>
<CardContent className="pt-6">
<div className="text-center text-gray-500 space-y-2">
<Database className="w-16 h-16 mx-auto opacity-50" />
<h3 className="text-lg font-medium">Advanced Vector Search</h3>
<p className="text-sm max-w-md mx-auto">
Search through your documents using semantic similarity powered by Modal.com's distributed FAISS implementation.
Upload documents first to build your vector index.
</p>
</div>
</CardContent>
</Card>
)}
</div>
);
}