"use client" import { useEffect, useMemo, useState } from "react" import { Button } from "@/components/ui/button" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Progress } from "@/components/ui/progress" import { Alert, AlertDescription } from "@/components/ui/alert" import { Upload, File as FileIcon, Loader2, ChevronDown, ChevronUp, X } from "lucide-react" import { getApiUrl } from "@/config/backend" interface JobStatus { job_id: string stage: string status_message?: string | null processed_files: number total_files: number error?: string | null } const STAGE_COPY: Record = { received: "Job received", saving_files: "Saving files", extracting: "Extracting document content", analyzing: "Calling Claude for causal relations", building_graph: "Writing to Neo4j knowledge graph", completed: "Completed", failed: "Failed", } interface MultiDocsUploadInlineProps { onJobCreated?: (jobId: string) => void } // Default export for easier importing export default MultiDocsUploadInline export function MultiDocsUploadInline({ onJobCreated }: MultiDocsUploadInlineProps) { const [isExpanded, setIsExpanded] = useState(false) const [files, setFiles] = useState([]) const [uploading, setUploading] = useState(false) const [jobId, setJobId] = useState(null) const [status, setStatus] = useState(null) const [error, setError] = useState(null) const uploadUrl = useMemo(() => getApiUrl("/api/multi-docs/jobs"), []) useEffect(() => { if (!jobId) return let cancelled = false const interval = setInterval(async () => { try { const response = await fetch(getApiUrl(`/api/multi-docs/jobs/${jobId}`)) if (!response.ok) { throw new Error(`Status request failed with ${response.status}`) } const data = (await response.json()) as JobStatus if (!cancelled) { setStatus(data) if (data.stage === "completed" || data.stage === "failed") { clearInterval(interval) if (data.stage === "completed" && onJobCreated) { onJobCreated(jobId) } } } } catch (err: any) { if (!cancelled) { console.error("Failed to poll job status:", err) setError(err.message ?? "Failed to fetch job status") clearInterval(interval) } } }, 4000) return () => { cancelled = true clearInterval(interval) } }, [jobId, onJobCreated]) const handleFileSelect = (event: React.ChangeEvent) => { if (!event.target.files) return const selected = Array.from(event.target.files) setFiles(prev => [...prev, ...selected]) } const removeFile = (fileName: string) => { setFiles(prev => prev.filter(file => file.name !== fileName)) } const startUpload = async () => { setError(null) if (files.length === 0) { setError("Please select at least one file.") return } const formData = new FormData() files.forEach(file => formData.append("files", file)) setUploading(true) try { const response = await fetch(uploadUrl, { method: "POST", body: formData, }) if (!response.ok) { let errorMessage = `Upload failed with ${response.status}` try { const errorData = await response.json() errorMessage = errorData.detail || errorData.message || errorMessage } catch { const text = await response.text() errorMessage = text || errorMessage } throw new Error(errorMessage) } const data = await response.json() setJobId(data.job_id) setStatus({ job_id: data.job_id, stage: data.stage, total_files: data.total_files, processed_files: 0, }) setUploading(false) setIsExpanded(true) // Keep expanded when upload starts } catch (err: any) { console.error("Upload failed:", err) setError(err.message ?? "Upload failed. Please try again.") setUploading(false) } } // Calculate progress based on stage and file processing const progressPercent = status ? (() => { // File extraction progress (0-30%) const fileProgress = status.total_files > 0 ? Math.round((status.processed_files / status.total_files) * 30) : 0 // Stage-based progress let stageProgress = 0 if (status.stage === "saving_files") { stageProgress = 5 } else if (status.stage === "extracting") { stageProgress = 10 + fileProgress // 10-40% } else if (status.stage === "analyzing") { // Analyzing takes the longest, show 40-85% stageProgress = 40 + Math.min(fileProgress * 1.5, 45) // 40-85% } else if (status.stage === "building_graph") { stageProgress = 85 + Math.min(fileProgress * 0.5, 10) // 85-95% } else if (status.stage === "completed") { stageProgress = 100 } else if (status.stage === "failed") { stageProgress = fileProgress // Show progress up to failure point } return Math.min(stageProgress, 100) })() : 0 const resetUpload = () => { setFiles([]) setJobId(null) setStatus(null) setError(null) setUploading(false) } return ( setIsExpanded(!isExpanded)} >
Upload Documents for Knowledge Graph
{status && status.stage === "completed" && ( Completed )} {status && status.stage === "failed" && ( Failed )} {isExpanded ? ( ) : ( )}

Upload documents to extract causal relationships and build a knowledge graph in Neo4j

{isExpanded && ( {status?.stage === "completed" ? (
✅ Knowledge graph created successfully! {status.total_files} file(s) processed.
) : status?.stage === "failed" ? (
{error || "Upload failed. Please try again."}
) : ( <>
{files.length > 0 && (

Selected Files ({files.length})

    {files.map(file => (
  • {file.name}
  • ))}
)} {error && ( {error} )} {status && (
{STAGE_COPY[status.stage] || status.stage} {progressPercent}%
{status.status_message && (

{status.status_message}

)}
)} )}
)}
) }