openrag/frontend/src/app/page.tsx
2025-07-29 02:12:44 -04:00

215 lines
8.2 KiB
TypeScript

"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Search, Loader2, FileText, Zap } from "lucide-react"
import { ProtectedRoute } from "@/components/protected-route"
interface SearchResult {
filename: string
mimetype: string
page: number
text: string
score: number
}
function SearchPage() {
const [query, setQuery] = useState("")
const [loading, setLoading] = useState(false)
const [results, setResults] = useState<SearchResult[]>([])
const [searchPerformed, setSearchPerformed] = useState(false)
const handleSearch = async (e: React.FormEvent) => {
e.preventDefault()
if (!query.trim()) return
setLoading(true)
setSearchPerformed(false)
try {
const response = await fetch("/api/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ query }),
})
const result = await response.json()
if (response.ok) {
setResults(result.results || [])
setSearchPerformed(true)
} else {
console.error("Search failed:", result.error)
setResults([])
setSearchPerformed(true)
}
} catch (error) {
console.error("Search error:", error)
setResults([])
setSearchPerformed(true)
} finally {
setLoading(false)
}
}
return (
<div className="space-y-8">
{/* Hero Section */}
<div className="space-y-4">
<div className="mb-4">
<h1 className="text-4xl font-bold tracking-tight text-white">
Search
</h1>
</div>
<p className="text-xl text-muted-foreground">
Find documents using semantic search
</p>
<p className="text-sm text-muted-foreground max-w-2xl">
Enter your search query to find relevant documents using AI-powered semantic search across your document collection.
</p>
</div>
{/* Search Interface */}
<Card className="w-full bg-card/50 backdrop-blur-sm border-border/50">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Search className="h-5 w-5" />
Search Documents
</CardTitle>
<CardDescription>
Enter your search query to find relevant documents using semantic search
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<form onSubmit={handleSearch} className="space-y-4">
<div className="space-y-3">
<Label htmlFor="search-query" className="font-medium">
Search Query
</Label>
<Input
id="search-query"
type="text"
placeholder="e.g., 'financial reports from Q4' or 'user authentication setup'"
value={query}
onChange={(e) => setQuery(e.target.value)}
className="h-12 bg-background/50 border-border/50 focus:border-blue-400/50 focus:ring-blue-400/20"
/>
</div>
<Button
type="submit"
disabled={!query.trim() || loading}
className="w-full h-12 transition-all duration-200"
>
{loading ? (
<>
<Loader2 className="mr-3 h-5 w-5 animate-spin" />
Searching...
</>
) : (
<>
<Search className="mr-3 h-5 w-5" />
Search Documents
</>
)}
</Button>
</form>
{/* Results Section */}
<div className="mt-8">
{searchPerformed ? (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-2xl font-semibold flex items-center gap-2">
<Zap className="h-6 w-6 text-yellow-400" />
Search Results
</h2>
<div className="flex items-center gap-2">
<div className="h-2 w-2 bg-green-400 rounded-full animate-pulse"></div>
<span className="text-sm text-muted-foreground">
{results.length} result{results.length !== 1 ? 's' : ''} found
</span>
</div>
</div>
{results.length === 0 ? (
<Card className="bg-muted/20 border-dashed border-muted-foreground/30">
<CardContent className="pt-8 pb-8">
<div className="text-center space-y-3">
<div className="mx-auto w-16 h-16 bg-muted/30 rounded-full flex items-center justify-center">
<Search className="h-8 w-8 text-muted-foreground/50" />
</div>
<p className="text-lg font-medium text-muted-foreground">
No documents found
</p>
<p className="text-sm text-muted-foreground/70 max-w-md mx-auto">
Try adjusting your search terms or check if documents have been indexed.
</p>
</div>
</CardContent>
</Card>
) : (
<div className="space-y-4">
{results.map((result, index) => (
<Card key={index} className="bg-card/50 backdrop-blur-sm border-border/50 hover:bg-card/70 transition-all duration-200 hover:shadow-lg hover:shadow-blue-500/10">
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="text-lg flex items-center gap-3">
<div className="p-2 rounded-lg bg-blue-500/20 border border-blue-500/30">
<FileText className="h-4 w-4 text-blue-400" />
</div>
<span className="truncate">{result.filename}</span>
</CardTitle>
<div className="flex items-center gap-2">
<div className="px-2 py-1 rounded-md bg-green-500/20 border border-green-500/30">
<span className="text-xs font-medium text-green-400">
{result.score.toFixed(2)}
</span>
</div>
</div>
</div>
<CardDescription className="flex items-center gap-4 text-sm">
<span className="px-2 py-1 rounded bg-muted/50 text-muted-foreground">
{result.mimetype}
</span>
<span className="text-muted-foreground">
Page {result.page}
</span>
</CardDescription>
</CardHeader>
<CardContent>
<div className="border-l-2 border-blue-400/50 pl-4 py-2 bg-muted/20 rounded-r-lg">
<p className="text-sm leading-relaxed text-foreground/90">
{result.text}
</p>
</div>
</CardContent>
</Card>
))}
</div>
)}
</div>
) : (
<div className="h-32 flex items-center justify-center">
<p className="text-muted-foreground/50 text-sm">
Enter a search query above to get started
</p>
</div>
)}
</div>
</CardContent>
</Card>
</div>
)
}
export default function ProtectedSearchPage() {
return (
<ProtectedRoute>
<SearchPage />
</ProtectedRoute>
)
}