"use client"; import { useState, useRef } from "react"; import Link from "next/link"; type SearchResult = { text: string; similarity?: number; relevance?: number; }; type SourceType = "raw_text" | "url" | "pdf" | "docx" | "txt" | "markdown"; const API_BASE = process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") || "http://localhost:8000"; export function KnowledgeBasePanel() { const [tenantId, setTenantId] = useState("tenant123"); const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState([]); const [isSearching, setIsSearching] = useState(false); const [ingestContent, setIngestContent] = useState(""); const [sourceType, setSourceType] = useState("raw_text"); const [filename, setFilename] = useState(""); const [url, setUrl] = useState(""); const [isIngesting, setIsIngesting] = useState(false); const [ingestStatus, setIngestStatus] = useState(null); const [searchError, setSearchError] = useState(null); const fileInputRef = useRef(null); async function handleSearch() { if (!searchQuery.trim() || isSearching) return; setIsSearching(true); setSearchError(null); setSearchResults([]); try { const response = await fetch(`${API_BASE}/rag/search`, { method: "POST", headers: { "Content-Type": "application/json", "x-tenant-id": tenantId, }, body: JSON.stringify({ query: searchQuery }), }); if (!response.ok) { throw new Error(`Search failed: ${response.status}`); } const data = await response.json(); setSearchResults(data.results || []); } catch (err) { console.error(err); setSearchError( err instanceof Error ? err.message : "Failed to search knowledge base. Is the RAG MCP server running?", ); } finally { setIsSearching(false); } } async function handleFileUpload(event: React.ChangeEvent) { const file = event.target.files?.[0]; if (!file) return; // Detect file type from extension const ext = file.name.split('.').pop()?.toLowerCase(); let detectedType: SourceType = "raw_text"; if (ext === "pdf") detectedType = "pdf"; else if (ext === "docx" || ext === "doc") detectedType = "docx"; else if (ext === "txt" || ext === "text") detectedType = "txt"; else if (ext === "md" || ext === "markdown") detectedType = "markdown"; setSourceType(detectedType); setFilename(file.name); // For binary files (PDF, DOCX), upload directly to server if (detectedType === "pdf" || detectedType === "docx") { await handleFileIngest(file); return; } // For text files, read and show in textarea const reader = new FileReader(); reader.onload = async (e) => { const text = e.target?.result as string; setIngestContent(text); }; reader.readAsText(file); } async function handleFileIngest(file: File) { setIsIngesting(true); setIngestStatus(null); try { const formData = new FormData(); formData.append("file", file); const response = await fetch(`${API_BASE}/rag/ingest-file`, { method: "POST", headers: { "x-tenant-id": tenantId, }, body: formData, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.detail || `File ingestion failed: ${response.status}`, ); } const data = await response.json(); setIngestStatus( `✅ ${data.message || `Successfully ingested ${data.chunks_stored || 0} chunk(s)`}`, ); setFilename(""); if (fileInputRef.current) { fileInputRef.current.value = ""; } } catch (err) { console.error(err); setIngestStatus( err instanceof Error ? `❌ Error: ${err.message}` : "Failed to ingest file. Is the RAG MCP server running?", ); } finally { setIsIngesting(false); } } async function handleIngest() { if (!ingestContent.trim() || isIngesting) return; setIsIngesting(true); setIngestStatus(null); try { // Prepare metadata const metadata: Record = {}; if (filename) metadata.filename = filename; if (url || sourceType === "url") { const ingestUrl = url || ingestContent.trim(); metadata.url = ingestUrl; } if (filename) { // Generate doc_id from filename metadata.doc_id = filename.replace(/[^a-zA-Z0-9]/g, "_").toLowerCase(); } // Use the new enhanced endpoint const response = await fetch(`${API_BASE}/rag/ingest-document`, { method: "POST", headers: { "Content-Type": "application/json", "x-tenant-id": tenantId, }, body: JSON.stringify({ action: "ingest_document", tenant_id: tenantId, source_type: sourceType, content: sourceType === "url" ? (url || ingestContent.trim()) : ingestContent, metadata: Object.keys(metadata).length > 0 ? metadata : undefined, }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new Error( errorData.detail || `Ingestion failed: ${response.status}`, ); } const data = await response.json(); setIngestStatus( `✅ ${data.message || `Successfully ingested ${data.chunks_stored || 0} chunk(s)`}`, ); setIngestContent(""); setFilename(""); setUrl(""); if (fileInputRef.current) { fileInputRef.current.value = ""; } } catch (err) { console.error(err); setIngestStatus( err instanceof Error ? `❌ Error: ${err.message}` : "Failed to ingest content. Is the RAG MCP server running?", ); } finally { setIsIngesting(false); } } return (

Knowledge Base

Search & ingest documents

View All Documents →
setTenantId(e.target.value)} className="rounded-full border border-white/15 bg-white/10 px-4 py-1.5 text-sm text-white outline-none focus:border-cyan-300" />
{/* Search Section */}
setSearchQuery(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleSearch()} className="flex-1 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white outline-none focus:border-cyan-200/80" />
{searchError && (

{searchError}

)} {searchResults.length > 0 && (

Found {searchResults.length} result(s)

{searchResults.map((result, idx) => (

{result.text}

{(result.similarity !== undefined || result.relevance !== undefined) && ( {( result.similarity ?? result.relevance ?? 0 ).toFixed(2)} )}
))}
)}
{/* Ingest Section */}

Add to Knowledge Base

Upload files (PDF, DOCX, TXT, MD), paste text, or provide URLs. Content will be chunked, embedded, and stored.

{/* Source Type Selector */}
{(["raw_text", "url", "pdf", "docx", "txt", "markdown"] as SourceType[]).map((type) => ( ))}
{/* File Upload */}
{filename && ( {filename} )}
{/* URL Input (when source type is URL) */} {sourceType === "url" && ( setUrl(e.target.value)} className="mt-4 w-full rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white outline-none focus:border-cyan-200/80" /> )} {/* Content Textarea */}