Spaces:
Running
Running
| import React, { useState, useCallback } from "react"; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogDescription, | |
| DialogHeader, | |
| DialogTitle, | |
| } from "@/components/ui/dialog"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Progress } from "@/components/ui/progress"; | |
| import { Upload, FileText, X } from "lucide-react"; | |
| import { useSystemNotifications } from "@/hooks/useSystemNotifications"; | |
| import { useAgentGraph } from "@/context/AgentGraphContext"; | |
| import { api } from "@/lib/api"; | |
| interface UploadDialogProps { | |
| open: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| } | |
| export function UploadDialog({ open, onOpenChange }: UploadDialogProps) { | |
| const [isDragging, setIsDragging] = useState(false); | |
| const [uploadProgress, setUploadProgress] = useState(0); | |
| const [isUploading, setIsUploading] = useState(false); | |
| const [selectedFile, setSelectedFile] = useState<File | null>(null); | |
| const { notifySuccess, notifyError } = useSystemNotifications(); | |
| const { actions } = useAgentGraph(); | |
| 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) { | |
| setSelectedFile(files[0] || null); | |
| } | |
| }, []); | |
| const handleFileSelect = useCallback( | |
| (e: React.ChangeEvent<HTMLInputElement>) => { | |
| const files = e.target.files; | |
| if (files && files.length > 0) { | |
| setSelectedFile(files[0] || null); | |
| } | |
| }, | |
| [] | |
| ); | |
| const handleUpload = async () => { | |
| if (!selectedFile) return; | |
| setIsUploading(true); | |
| setUploadProgress(0); | |
| try { | |
| const trace = await api.traces.upload(selectedFile, (progress) => { | |
| setUploadProgress(progress); | |
| }); | |
| notifySuccess( | |
| "Upload Successful", | |
| `${selectedFile.name} has been uploaded successfully.` | |
| ); | |
| // Refresh traces list | |
| const traces = await api.traces.list(); | |
| actions.setTraces(traces); | |
| // Select the uploaded trace | |
| actions.setSelectedTrace(trace); | |
| // Close dialog | |
| onOpenChange(false); | |
| setSelectedFile(null); | |
| setUploadProgress(0); | |
| } catch (error) { | |
| notifyError( | |
| "Upload Failed", | |
| error instanceof Error ? error.message : "Failed to upload file" | |
| ); | |
| } finally { | |
| setIsUploading(false); | |
| } | |
| }; | |
| const handleClose = () => { | |
| if (!isUploading) { | |
| onOpenChange(false); | |
| setSelectedFile(null); | |
| setUploadProgress(0); | |
| } | |
| }; | |
| return ( | |
| <Dialog open={open} onOpenChange={handleClose}> | |
| <DialogContent className="sm:max-w-md max-w-[90vw] w-full"> | |
| <DialogHeader> | |
| <DialogTitle>Upload Trace File</DialogTitle> | |
| <DialogDescription> | |
| Upload a trace file to analyze with AgentGraph pipeline | |
| </DialogDescription> | |
| </DialogHeader> | |
| <div className="space-y-4 overflow-hidden"> | |
| {!selectedFile ? ( | |
| <div | |
| className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${ | |
| isDragging | |
| ? "border-primary bg-primary/5" | |
| : "border-muted-foreground/25 hover:border-primary/50" | |
| }`} | |
| onDragOver={handleDragOver} | |
| onDragLeave={handleDragLeave} | |
| onDrop={handleDrop} | |
| > | |
| <Upload className="h-12 w-12 mx-auto mb-4 text-muted-foreground" /> | |
| <h4 className="text-lg font-medium mb-2"> | |
| Drop your trace file here | |
| </h4> | |
| <p className="text-sm text-muted-foreground mb-4"> | |
| or click to browse files | |
| </p> | |
| <Button variant="outline" asChild> | |
| <label> | |
| <input | |
| type="file" | |
| className="hidden" | |
| accept=".json,.txt,.log" | |
| onChange={handleFileSelect} | |
| /> | |
| Browse Files | |
| </label> | |
| </Button> | |
| </div> | |
| ) : ( | |
| <div className="space-y-4"> | |
| <div className="flex items-center gap-3 p-3 border rounded-lg min-w-0"> | |
| <FileText className="h-8 w-8 text-primary flex-shrink-0" /> | |
| <div className="flex-1 min-w-0"> | |
| <p className="font-medium truncate" title={selectedFile.name}> | |
| {selectedFile.name} | |
| </p> | |
| <p className="text-sm text-muted-foreground"> | |
| {(selectedFile.size / 1024 / 1024).toFixed(2)} MB | |
| </p> | |
| </div> | |
| {!isUploading && ( | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => setSelectedFile(null)} | |
| className="flex-shrink-0" | |
| > | |
| <X className="h-4 w-4" /> | |
| </Button> | |
| )} | |
| </div> | |
| {isUploading && ( | |
| <div className="space-y-2"> | |
| <Progress value={uploadProgress} /> | |
| <p className="text-sm text-center text-muted-foreground"> | |
| Uploading... {Math.round(uploadProgress)}% | |
| </p> | |
| </div> | |
| )} | |
| <div className="flex gap-2"> | |
| <Button | |
| variant="outline" | |
| onClick={() => setSelectedFile(null)} | |
| disabled={isUploading} | |
| className="flex-1" | |
| > | |
| Cancel | |
| </Button> | |
| <Button | |
| onClick={handleUpload} | |
| disabled={isUploading} | |
| className="flex-1" | |
| > | |
| {isUploading ? "Uploading..." : "Upload"} | |
| </Button> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </DialogContent> | |
| </Dialog> | |
| ); | |
| } | |