mirror of
https://github.com/aljazceru/enclava.git
synced 2025-12-17 07:24:34 +01:00
264 lines
8.8 KiB
TypeScript
264 lines
8.8 KiB
TypeScript
"use client"
|
|
|
|
import { useState, useEffect } from "react"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Progress } from "@/components/ui/progress"
|
|
import { Separator } from "@/components/ui/separator"
|
|
import { Plus, Database, Upload, Search, Trash2, FileText, AlertCircle } from "lucide-react"
|
|
import { CollectionManager } from "@/components/rag/collection-manager"
|
|
import { DocumentUpload } from "@/components/rag/document-upload"
|
|
import { DocumentBrowser } from "@/components/rag/document-browser"
|
|
import { useAuth } from "@/components/providers/auth-provider"
|
|
import { ProtectedRoute } from '@/components/auth/ProtectedRoute'
|
|
import { apiClient } from '@/lib/api-client'
|
|
|
|
interface Collection {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
document_count: number
|
|
size_bytes: number
|
|
vector_count: number
|
|
created_at: string
|
|
status: 'active' | 'indexing' | 'error'
|
|
is_managed?: boolean
|
|
source?: 'database' | 'qdrant'
|
|
is_active?: boolean
|
|
updated_at?: string
|
|
}
|
|
|
|
interface CollectionStats {
|
|
collections: {
|
|
total: number
|
|
active: number
|
|
}
|
|
documents: {
|
|
total: number
|
|
processing: number
|
|
processed: number
|
|
}
|
|
storage: {
|
|
total_size_bytes: number
|
|
total_size_mb: number
|
|
}
|
|
vectors: {
|
|
total: number
|
|
}
|
|
}
|
|
|
|
export default function RAGPage() {
|
|
return (
|
|
<ProtectedRoute>
|
|
<RAGPageContent />
|
|
</ProtectedRoute>
|
|
)
|
|
}
|
|
|
|
function RAGPageContent() {
|
|
const { user } = useAuth()
|
|
const [collections, setCollections] = useState<Collection[]>([])
|
|
const [stats, setStats] = useState<CollectionStats | null>(null)
|
|
const [selectedCollection, setSelectedCollection] = useState<string | null>(null)
|
|
const [loading, setLoading] = useState(true)
|
|
const [activeTab, setActiveTab] = useState("collections")
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
loadCollections()
|
|
loadStats()
|
|
}
|
|
}, [user])
|
|
|
|
const loadCollections = async () => {
|
|
try {
|
|
const data = await apiClient.get('/api-internal/v1/rag/collections')
|
|
setCollections(data.collections || [])
|
|
} catch (error) {
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const loadStats = async () => {
|
|
try {
|
|
const data = await apiClient.get('/api-internal/v1/rag/stats')
|
|
console.log('Stats API response:', data)
|
|
|
|
// Check if the response has the expected structure
|
|
if (data && data.stats && data.stats.collections) {
|
|
console.log('✓ Stats has collections property')
|
|
setStats(data.stats)
|
|
} else {
|
|
console.error('✗ Invalid stats structure:', data)
|
|
// Set default empty stats to prevent error
|
|
setStats({
|
|
collections: { total: 0, active: 0 },
|
|
documents: { total: 0, processing: 0, processed: 0 },
|
|
storage: { total_size_bytes: 0, total_size_mb: 0 },
|
|
vectors: { total: 0 }
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error)
|
|
// Set default empty stats on error
|
|
setStats({
|
|
collections: { total: 0, active: 0 },
|
|
documents: { total: 0, processing: 0, processed: 0 },
|
|
storage: { total_size_bytes: 0, total_size_mb: 0 },
|
|
vectors: { total: 0 }
|
|
})
|
|
}
|
|
}
|
|
|
|
const handleCollectionCreated = (newCollection: Collection) => {
|
|
setCollections(prev => [...prev, newCollection])
|
|
loadStats()
|
|
}
|
|
|
|
const handleCollectionDeleted = (collectionId: string) => {
|
|
setCollections(prev => prev.filter(c => c.id !== collectionId))
|
|
if (selectedCollection === collectionId) {
|
|
setSelectedCollection(null)
|
|
}
|
|
loadStats()
|
|
}
|
|
|
|
const handleDocumentUploaded = () => {
|
|
loadCollections()
|
|
loadStats()
|
|
}
|
|
|
|
if (!user) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<Card className="w-full max-w-md">
|
|
<CardContent className="flex flex-col items-center justify-center py-8">
|
|
<AlertCircle className="h-12 w-12 text-muted-foreground mb-4" />
|
|
<h3 className="text-lg font-semibold mb-2">Authentication Required</h3>
|
|
<p className="text-muted-foreground text-center mb-4">
|
|
Please log in to access the RAG document management system.
|
|
</p>
|
|
<Button asChild>
|
|
<a href="/login">Login</a>
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const formatBytes = (bytes: number | null | undefined) => {
|
|
if (!bytes || bytes === 0) return '0 Bytes'
|
|
const k = 1024
|
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">RAG Document Management</h1>
|
|
<p className="text-muted-foreground">
|
|
Manage collections, upload documents, and organize your knowledge base.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stats Overview */}
|
|
{stats && (
|
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Collections</CardTitle>
|
|
<Database className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{stats.collections.total}</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
{stats.collections.active} active
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Documents</CardTitle>
|
|
<FileText className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{stats.documents.total}</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Across all collections
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Storage Used</CardTitle>
|
|
<Upload className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold">{formatBytes(stats.storage.total_size_bytes)}</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
Total indexed content
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
<Card>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
<CardTitle className="text-sm font-medium">Status</CardTitle>
|
|
<Search className="h-4 w-4 text-muted-foreground" />
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="text-2xl font-bold text-green-600">Healthy</div>
|
|
<p className="text-xs text-muted-foreground">
|
|
All systems operational
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)}
|
|
|
|
{/* Main Content */}
|
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-4">
|
|
<TabsList>
|
|
<TabsTrigger value="collections">Collections</TabsTrigger>
|
|
<TabsTrigger value="upload">Upload Documents</TabsTrigger>
|
|
<TabsTrigger value="browse">Browse Documents</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="collections" className="space-y-4">
|
|
<CollectionManager
|
|
collections={collections}
|
|
onCollectionCreated={handleCollectionCreated}
|
|
onCollectionDeleted={handleCollectionDeleted}
|
|
onCollectionSelected={setSelectedCollection}
|
|
selectedCollection={selectedCollection}
|
|
loading={loading}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="upload" className="space-y-4">
|
|
<DocumentUpload
|
|
collections={collections}
|
|
selectedCollection={selectedCollection}
|
|
onDocumentUploaded={handleDocumentUploaded}
|
|
/>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="browse" className="space-y-4">
|
|
<DocumentBrowser
|
|
collections={collections}
|
|
selectedCollection={selectedCollection}
|
|
onCollectionSelected={setSelectedCollection}
|
|
/>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
)
|
|
}
|