import React, { useState, useEffect, useCallback } from "react"; import { createRoot } from "react-dom/client"; interface Document { id: number; title: string; content: string; snippet: string; source: string; source_type: string; url?: string; relevance_score: number; rank: number; metadata?: Record; } interface SearchResponse { documents: Document[]; search_time: number; query: string; total_count: number; } interface KnowledgeBrowserProps { query?: string; results?: Document[]; search_type?: "semantic" | "keyword" | "hybrid"; max_results?: number; elem_id?: string; elem_classes?: string[]; visible?: boolean; onSubmit?: (query: string) => void; onSelect?: (document: Document) => void; } const KnowledgeBrowser: React.FC = ({ query: initialQuery = "", results: initialResults = [], search_type: initialSearchType = "semantic", max_results: initialMaxResults = 10, elem_id, elem_classes = [], visible = true, onSubmit, onSelect, }) => { const [query, setQuery] = useState(initialQuery); const [results, setResults] = useState(initialResults); const [searchType, setSearchType] = useState(initialSearchType); const [maxResults, setMaxResults] = useState(initialMaxResults); const [isLoading, setIsLoading] = useState(false); const [expandedResults, setExpandedResults] = useState>(new Set()); const [searchTime, setSearchTime] = useState(0); const performSearch = useCallback(async (searchQuery: string) => { if (!searchQuery.trim()) return; setIsLoading(true); try { const response = await fetch("/api/search", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ query: searchQuery, search_type: searchType, limit: maxResults, offset: 0, }), }); if (!response.ok) { throw new Error(`Search failed: ${response.statusText}`); } const data: SearchResponse = await response.json(); setResults(data.documents || []); setSearchTime(data.search_time || 0); // Trigger submit event if (onSubmit) { onSubmit(searchQuery); } } catch (error) { console.error("Search error:", error); setResults([]); } finally { setIsLoading(false); } }, [searchType, maxResults, onSubmit]); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); performSearch(query); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); performSearch(query); } }; const toggleExpanded = (documentId: number) => { const newExpanded = new Set(expandedResults); if (newExpanded.has(documentId)) { newExpanded.delete(documentId); } else { newExpanded.add(documentId); } setExpandedResults(newExpanded); }; const handleSelect = (document: Document) => { if (onSelect) { onSelect(document); } }; const getSourceTypeBadgeClass = (sourceType: string) => { const baseClass = "source-type-badge"; switch (sourceType) { case "academic": return `${baseClass} source-type-academic`; case "web": return `${baseClass} source-type-web`; case "pdf": return `${baseClass} source-type-pdf`; case "code": return `${baseClass} source-type-code`; default: return `${baseClass} source-type-web`; } }; const formatRelevanceScore = (score: number) => { return `${Math.round(score * 100)}%`; }; const formatSearchTime = (time: number) => { return time < 1 ? `${Math.round(time * 1000)}ms` : `${time.toFixed(2)}s`; }; if (!visible) { return null; } const containerClasses = ["kb-browser", ...elem_classes].join(" "); return (
{/* Search Interface */}
setQuery(e.target.value)} onKeyDown={handleKeyDown} disabled={isLoading} />
{/* Results Container */}
{results.length > 0 && (
{results.length} results found in{" "} {formatSearchTime(searchTime)}
)} {isLoading ? (

Searching knowledge base...

) : results.length === 0 && query ? (

No results found for "{query}"

Try adjusting your search terms or search type.

) : results.length === 0 ? (

Enter a search query to find relevant documents

) : (
{results.map((result) => (
{result.source_type} {formatRelevanceScore(result.relevance_score)}

{result.title}

{result.source}

{result.snippet}
{result.url && ( )}
{expandedResults.has(result.id) && (

Full Content

{result.content.length > 500 ? `${result.content.substring(0, 500)}...` : result.content}

{result.metadata && Object.keys(result.metadata).length > 0 && ( <>

Metadata

{Object.entries(result.metadata).map(([key, value]) => (
{key.charAt(0).toUpperCase() + key.slice(1)}: {Array.isArray(value) ? value.join(", ") : String(value)}
))}
)}
)}
))}
)}
); }; // Initialize the component when DOM is ready const initializeComponent = () => { const container = document.getElementById("kb-browser-root"); if (container) { const root = createRoot(container); root.render( { console.log("Search submitted:", query); // Dispatch custom event for Gradio integration window.dispatchEvent(new CustomEvent("kb-browser-submit", { detail: { query } })); }} onSelect={(document) => { console.log("Document selected:", document); // Dispatch custom event for Gradio integration window.dispatchEvent(new CustomEvent("kb-browser-select", { detail: { document } })); }} /> ); } }; // Initialize when DOM is loaded if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initializeComponent); } else { initializeComponent(); } export default KnowledgeBrowser;