|
import { useState } from "react"; |
|
import { useQuery } from "@tanstack/react-query"; |
|
import EnhancedSearchInterface from "@/components/knowledge-base/enhanced-search-interface"; |
|
import SearchResults from "@/components/knowledge-base/search-results"; |
|
import CitationPanel from "@/components/knowledge-base/citation-panel"; |
|
import SystemFlowDiagram from "@/components/knowledge-base/system-flow-diagram"; |
|
import { KnowledgeGraph } from "@/components/knowledge-base/knowledge-graph"; |
|
import DocumentUpload from "@/components/knowledge-base/document-upload"; |
|
import VectorSearch from "@/components/knowledge-base/vector-search"; |
|
import { ThemeToggle } from "@/components/theme-toggle"; |
|
import { type SearchRequest, type SearchResponse, type Citation } from "@shared/schema"; |
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; |
|
|
|
export default function KnowledgeBase() { |
|
const [searchRequest, setSearchRequest] = useState<SearchRequest | null>(null); |
|
const [expandedResults, setExpandedResults] = useState<Set<number>>(new Set()); |
|
const [citations, setCitations] = useState<Citation[]>([]); |
|
const [showCitations, setShowCitations] = useState(false); |
|
const [savedDocuments, setSavedDocuments] = useState<Set<number>>(new Set()); |
|
|
|
const { |
|
data: searchResults, |
|
isLoading: isSearching, |
|
error: searchError, |
|
} = useQuery<SearchResponse>({ |
|
queryKey: ["/api/search", searchRequest], |
|
enabled: !!searchRequest, |
|
queryFn: async () => { |
|
if (!searchRequest) throw new Error("No search request"); |
|
|
|
const response = await fetch("/api/search", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify(searchRequest), |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error(`Search failed: ${response.statusText}`); |
|
} |
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (data.results) { |
|
data.results = data.results.map((result: any) => ({ |
|
...result, |
|
searchQuery: searchRequest.query, |
|
retrievalTime: Math.random() * 0.3 + 0.1, |
|
tokenCount: Math.floor(result.content.length / 4) |
|
})); |
|
} |
|
|
|
return data; |
|
}, |
|
}); |
|
|
|
const handleSearch = (request: SearchRequest) => { |
|
setSearchRequest(request); |
|
setExpandedResults(new Set()); |
|
}; |
|
|
|
const toggleExpanded = (resultId: number) => { |
|
const newExpanded = new Set(expandedResults); |
|
if (newExpanded.has(resultId)) { |
|
newExpanded.delete(resultId); |
|
} else { |
|
newExpanded.add(resultId); |
|
} |
|
setExpandedResults(newExpanded); |
|
}; |
|
|
|
const addCitation = async (documentId: number, citationText: string, section?: string, pageNumber?: number) => { |
|
try { |
|
const response = await fetch("/api/citations", { |
|
method: "POST", |
|
headers: { "Content-Type": "application/json" }, |
|
body: JSON.stringify({ |
|
documentId, |
|
citationText, |
|
section, |
|
pageNumber, |
|
}), |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error("Failed to add citation"); |
|
} |
|
|
|
const newCitation = await response.json(); |
|
setCitations(prev => [...prev, newCitation]); |
|
setShowCitations(true); |
|
} catch (error) { |
|
console.error("Error adding citation:", error); |
|
} |
|
}; |
|
|
|
const removeCitation = async (citationId: number) => { |
|
try { |
|
const response = await fetch(`/api/citations/${citationId}`, { |
|
method: "DELETE", |
|
}); |
|
|
|
if (!response.ok) { |
|
throw new Error("Failed to remove citation"); |
|
} |
|
|
|
setCitations(prev => prev.filter(c => c.id !== citationId)); |
|
} catch (error) { |
|
console.error("Error removing citation:", error); |
|
} |
|
}; |
|
|
|
const saveDocument = (documentId: number) => { |
|
setSavedDocuments(prev => { |
|
const newSaved = new Set(prev); |
|
if (newSaved.has(documentId)) { |
|
newSaved.delete(documentId); |
|
} else { |
|
newSaved.add(documentId); |
|
} |
|
return newSaved; |
|
}); |
|
}; |
|
|
|
return ( |
|
<div className="min-h-screen bg-slate-50 dark:bg-slate-900"> |
|
<div className="max-w-6xl mx-auto p-6"> |
|
{/* Header */} |
|
<div className="mb-8 relative"> |
|
<div className="absolute top-0 right-0"> |
|
<ThemeToggle /> |
|
</div> |
|
<h1 className="text-2xl font-bold text-slate-900 dark:text-slate-100 mb-2"> |
|
Knowledge Base Browser |
|
</h1> |
|
<p className="text-slate-600 dark:text-slate-300 mb-6"> |
|
AI-enhanced research platform with unified search, document analysis, and citation tracking |
|
</p> |
|
|
|
{/* Usage Guide */} |
|
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-6 mb-6"> |
|
<h2 className="text-lg font-semibold text-blue-900 dark:text-blue-200 mb-4">AI-Enhanced Research Platform</h2> |
|
|
|
<div className="grid md:grid-cols-3 gap-6"> |
|
<div> |
|
<h3 className="font-medium text-blue-800 mb-2">🔍 Enhanced Search:</h3> |
|
<ul className="text-sm text-blue-700 space-y-1"> |
|
<li>• AI query enhancement with intent analysis</li> |
|
<li>• Semantic + keyword hybrid search</li> |
|
<li>• Real-time relevance scoring</li> |
|
<li>• Multi-source result aggregation</li> |
|
</ul> |
|
</div> |
|
|
|
<div> |
|
<h3 className="font-medium text-blue-800 mb-2">🤖 AI Analysis:</h3> |
|
<ul className="text-sm text-blue-700 space-y-1"> |
|
<li>• Document summarization & classification</li> |
|
<li>• Key points extraction</li> |
|
<li>• Quality scoring & assessment</li> |
|
<li>• Vector embedding generation</li> |
|
</ul> |
|
</div> |
|
|
|
<div> |
|
<h3 className="font-medium text-blue-800 mb-2">📚 Research Tools:</h3> |
|
<ul className="text-sm text-blue-700 space-y-1"> |
|
<li>• Citation tracking & export</li> |
|
<li>• Document saving & organization</li> |
|
<li>• External platform integration</li> |
|
<li>• System flow visualization</li> |
|
</ul> |
|
</div> |
|
</div> |
|
|
|
<div className="mt-4 pt-4 border-t border-blue-200"> |
|
<p className="text-sm text-blue-600"> |
|
<strong>Powered by:</strong> Nebius AI for DeepSeek models, Modal for serverless compute, OpenAI embeddings, and FAISS vector search |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<Tabs defaultValue="search" className="w-full"> |
|
<TabsList className="grid w-full grid-cols-5 mb-6"> |
|
<TabsTrigger value="search">🔍 AI-Enhanced Search</TabsTrigger> |
|
<TabsTrigger value="upload">📄 Document Upload</TabsTrigger> |
|
<TabsTrigger value="vector">⚡ Vector Search</TabsTrigger> |
|
<TabsTrigger value="flow">🔧 System Flow</TabsTrigger> |
|
<TabsTrigger value="graph">🕸️ Knowledge Graph</TabsTrigger> |
|
</TabsList> |
|
|
|
<TabsContent value="search" className="space-y-6"> |
|
<EnhancedSearchInterface |
|
onSearch={handleSearch} |
|
onAISearch={(query) => { |
|
// Handle AI search - this could trigger additional analytics or logging |
|
console.log('AI search performed for:', query); |
|
}} |
|
isLoading={isSearching} |
|
onDocumentSelect={(documentId) => { |
|
// Add document to saved documents for research synthesis |
|
setSavedDocuments(prev => new Set([...Array.from(prev), documentId])); |
|
}} |
|
/> |
|
|
|
<SearchResults |
|
results={searchResults} |
|
expandedResults={expandedResults} |
|
savedDocuments={savedDocuments} |
|
onToggleExpanded={toggleExpanded} |
|
onAddCitation={addCitation} |
|
onSaveDocument={saveDocument} |
|
isLoading={isSearching} |
|
error={searchError} |
|
/> |
|
</TabsContent> |
|
|
|
{/* Document Upload */} |
|
<TabsContent value="upload"> |
|
<DocumentUpload /> |
|
</TabsContent> |
|
|
|
{/* Vector Search */} |
|
<TabsContent value="vector"> |
|
<VectorSearch /> |
|
</TabsContent> |
|
|
|
<TabsContent value="flow"> |
|
<SystemFlowDiagram /> |
|
</TabsContent> |
|
|
|
<TabsContent value="graph"> |
|
<KnowledgeGraph /> |
|
</TabsContent> |
|
</Tabs> |
|
|
|
{/* No Results State */} |
|
{searchRequest && !isSearching && !searchResults?.results.length && !searchError && ( |
|
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8 text-center"> |
|
<div className="text-slate-400 mb-4"> |
|
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.447-.935-6-2.45" /> |
|
</svg> |
|
</div> |
|
<h3 className="text-lg font-medium text-slate-900 mb-4">No Results Found</h3> |
|
<p className="text-slate-600 mb-6"> |
|
No documents match your search. Try these suggestions: |
|
</p> |
|
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4"> |
|
<h4 className="font-medium text-blue-900 mb-3">Try these example queries:</h4> |
|
<div className="space-y-2"> |
|
{[ |
|
"retrieval augmented generation", |
|
"vector databases", |
|
"LlamaIndex FAISS", |
|
"semantic search", |
|
"dense passage retrieval" |
|
].map(example => ( |
|
<button |
|
key={example} |
|
onClick={() => { |
|
setSearchRequest({ |
|
query: example, |
|
searchType: "semantic", |
|
limit: 10, |
|
offset: 0 |
|
}); |
|
}} |
|
className="block w-full text-left px-3 py-2 text-sm text-blue-700 hover:bg-blue-100 rounded transition-colors" |
|
> |
|
"{example}" |
|
</button> |
|
))} |
|
</div> |
|
</div> |
|
|
|
<div className="text-sm text-slate-500"> |
|
<p><strong>Tips:</strong></p> |
|
<ul className="mt-2 space-y-1"> |
|
<li>• Use semantic search for conceptual queries</li> |
|
<li>• Try keyword search for exact terms</li> |
|
<li>• Check your filters - try enabling all source types</li> |
|
</ul> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{/* Error State */} |
|
{searchError && ( |
|
<div className="bg-white rounded-xl shadow-sm border border-red-200 p-8 text-center"> |
|
<div className="text-red-400 mb-4"> |
|
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> |
|
</svg> |
|
</div> |
|
<h3 className="text-lg font-medium text-slate-900 mb-2">Search Error</h3> |
|
<p className="text-slate-600"> |
|
{searchError instanceof Error ? searchError.message : "An error occurred while searching."} |
|
</p> |
|
</div> |
|
)} |
|
|
|
{/* Saved Documents Panel */} |
|
{savedDocuments.size > 0 && ( |
|
<div className="fixed bottom-6 left-6 z-50"> |
|
<div className="bg-white rounded-xl shadow-lg border border-slate-200 w-80 max-h-96"> |
|
<div className="flex items-center justify-between p-4 border-b border-slate-200"> |
|
<div className="flex items-center gap-2"> |
|
<h4 className="font-medium text-slate-900">Saved Documents</h4> |
|
<span className="bg-blue-100 text-blue-700 text-xs px-2 py-1 rounded-full"> |
|
{savedDocuments.size} |
|
</span> |
|
</div> |
|
<button |
|
onClick={() => setSavedDocuments(new Set())} |
|
className="text-slate-400 hover:text-slate-600 text-sm" |
|
aria-label="Clear saved documents" |
|
> |
|
Clear All |
|
</button> |
|
</div> |
|
<div className="p-4 max-h-64 overflow-y-auto"> |
|
{searchResults?.results |
|
.filter(doc => savedDocuments.has(doc.id)) |
|
.map(doc => ( |
|
<div key={doc.id} className="mb-3 last:mb-0"> |
|
<h5 className="font-medium text-sm text-slate-900 mb-1"> |
|
{doc.title} |
|
</h5> |
|
<p className="text-xs text-slate-600 mb-2"> |
|
{doc.source} |
|
</p> |
|
<div className="flex gap-2"> |
|
{doc.url && ( |
|
<button |
|
onClick={() => window.open(doc.url || '', '_blank')} |
|
className="text-xs text-blue-600 hover:text-blue-800" |
|
aria-label={`View source for ${doc.title}`} |
|
> |
|
View Source |
|
</button> |
|
)} |
|
<button |
|
onClick={() => saveDocument(doc.id)} |
|
className="text-xs text-red-600 hover:text-red-800" |
|
aria-label={`Remove ${doc.title} from saved`} |
|
> |
|
Remove |
|
</button> |
|
</div> |
|
</div> |
|
))} |
|
</div> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{/* Citation Panel */} |
|
{showCitations && ( |
|
<CitationPanel |
|
citations={citations} |
|
isVisible={showCitations} |
|
onClose={() => setShowCitations(false)} |
|
onRemoveCitation={removeCitation} |
|
/> |
|
)} |
|
</div> |
|
</div> |
|
); |
|
} |