import React, { useState, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { Label } from "@/components/ui/label"; import { Upload, X, CheckCircle, AlertCircle, FileText, Trash2, ArrowUpDown, Copy, } from "lucide-react"; import { useSystemNotifications } from "@/hooks/useSystemNotifications"; import { useAgentGraph } from "@/context/AgentGraphContext"; import { api } from "@/lib/api"; type ConcatenationMethod = "newlines" | "timestamps" | "custom" | "none"; interface FileUpload { file: File; id: string; preview?: string; } export function UploadView() { const [uploadProgress, setUploadProgress] = useState(0); const [isUploading, setIsUploading] = useState(false); const [selectedFiles, setSelectedFiles] = useState([]); const [uploadComplete, setUploadComplete] = useState(false); const [concatenationMethod, setConcatenationMethod] = useState("newlines"); const [customSeparator, setCustomSeparator] = useState("\n---\n"); const [finalFilename, setFinalFilename] = useState(""); const [isDragging, setIsDragging] = useState(false); const { notifySuccess, notifyError } = useSystemNotifications(); const { actions } = useAgentGraph(); const handleFileSelect = useCallback( (e: React.ChangeEvent) => { const files = e.target.files; if (files) { addFiles(Array.from(files)); } }, [] ); const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }, []); const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }, []); const handleDrop = useCallback((e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); const files = Array.from(e.dataTransfer.files); if (files.length > 0) { addFiles(files); } }, []); const addFiles = (files: File[]) => { const newFiles = files.map((file) => ({ file, id: Math.random().toString(36).substr(2, 9), preview: file.size < 50000 ? "Loading..." : undefined, // Only preview small files })); setSelectedFiles((prev) => [...prev, ...newFiles]); setUploadComplete(false); // Generate filename suggestion if (files.length === 1 && selectedFiles.length === 0) { setFinalFilename(files[0]?.name || "trace"); } else { setFinalFilename(`combined_trace_${Date.now()}.${getOutputExtension()}`); } }; const removeFile = (id: string) => { setSelectedFiles((prev) => prev.filter((f) => f.id !== id)); }; const moveFile = (id: string, direction: "up" | "down") => { setSelectedFiles((prev) => { const index = prev.findIndex((f) => f.id === id); if (index === -1) return prev; const newFiles = [...prev]; const targetIndex = direction === "up" ? index - 1 : index + 1; if ( targetIndex >= 0 && targetIndex < newFiles.length && newFiles[index] && newFiles[targetIndex] ) { [newFiles[index], newFiles[targetIndex]] = [ newFiles[targetIndex], newFiles[index], ]; } return newFiles; }); }; const getOutputExtension = () => { const hasJson = selectedFiles.some((f) => f.file.name.endsWith(".json")); return hasJson ? "json" : "txt"; }; const concatenateFiles = async (): Promise => { if (selectedFiles.length === 1) { return (await selectedFiles[0]?.file.text()) || ""; } const fileContents = await Promise.all( selectedFiles.map(async (fileUpload) => { const content = await fileUpload.file.text(); return { filename: fileUpload.file.name, content: content.trim(), }; }) ); switch (concatenationMethod) { case "none": return fileContents.map((f) => f.content).join(""); case "newlines": return fileContents.map((f) => f.content).join("\n\n"); case "timestamps": return fileContents .map( (f) => `=== File: ${f.filename} (${new Date().toISOString()}) ===\n${ f.content }` ) .join("\n\n"); case "custom": return fileContents.map((f) => f.content).join(customSeparator); default: return fileContents.map((f) => f.content).join("\n\n"); } }; const handleUpload = async () => { if (selectedFiles.length === 0) return; setIsUploading(true); setUploadProgress(0); try { let fileToUpload: File; if (selectedFiles.length === 1 && selectedFiles[0]) { fileToUpload = selectedFiles[0].file; } else { // Concatenate multiple files const combinedContent = await concatenateFiles(); const blob = new Blob([combinedContent], { type: "text/plain" }); fileToUpload = new File([blob], finalFilename, { type: blob.type }); } const trace = await api.traces.upload(fileToUpload, (progress) => { setUploadProgress(progress); }); notifySuccess( "Upload Successful", `${fileToUpload.name} has been uploaded successfully.` ); // Refresh traces list const traces = await api.traces.list(); actions.setTraces(traces); // Select the uploaded trace actions.setSelectedTrace(trace); // Mark upload as complete setUploadComplete(true); setUploadProgress(100); } catch (error) { notifyError( "Upload Failed", error instanceof Error ? error.message : "Failed to upload file" ); } finally { setIsUploading(false); } }; const handleReset = () => { setSelectedFiles([]); setUploadProgress(0); setUploadComplete(false); setFinalFilename(""); }; const handleViewTrace = () => { actions.setActiveView("traces"); }; const getFileIcon = (fileName: string) => { const extension = fileName.toLowerCase().split(".").pop(); switch (extension) { case "json": return "📄"; case "txt": return "📝"; case "log": return "📋"; default: return "📄"; } }; const formatFileSize = (bytes: number) => { if (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 (
{/* Upload Instructions */}

Drop files here or click to upload

Select trace files (JSON, TXT, JSONL) to upload. You can select multiple files at once.

{/* Left Column - Upload Functionality */}
{/* Selected Files */} {selectedFiles.length > 0 && (

Selected Files ({selectedFiles.length})

{selectedFiles.map((fileUpload, index) => (
{getFileIcon(fileUpload.file.name)}

{fileUpload.file.name}

{formatFileSize(fileUpload.file.size)} {fileUpload.file.type || "Unknown type"}
))}
)} {/* Multiple Files Configuration */} {selectedFiles.length > 1 && ( File Concatenation Configure how multiple files should be combined
{concatenationMethod === "custom" && (