wu981526092's picture
🚀 Deploy AgentGraph: Complete agent monitoring and knowledge graph system
c2ea5ed
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<FileUpload[]>([]);
const [uploadComplete, setUploadComplete] = useState(false);
const [concatenationMethod, setConcatenationMethod] =
useState<ConcatenationMethod>("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<HTMLInputElement>) => {
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<string> => {
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 (
<div className="p-6 space-y-6 max-w-7xl mx-auto">
{/* Upload Instructions */}
<div
className={`border border-dashed rounded-lg p-8 text-center space-y-4 transition-colors ${
isDragging
? "border-primary bg-primary/5 border-2"
: "border-gray-300 hover:border-primary/50"
}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<div className="space-y-2">
<Upload className="h-12 w-12 text-muted-foreground mx-auto" />
<h3 className="text-lg font-semibold">
Drop files here or click to upload
</h3>
<p className="text-muted-foreground max-w-md mx-auto">
Select trace files (JSON, TXT, JSONL) to upload. You can select
multiple files at once.
</p>
</div>
<Button size="lg" variant="outline" asChild>
<label className="cursor-pointer">
<input
type="file"
className="hidden"
accept=".json,.txt,.log"
multiple
onChange={handleFileSelect}
/>
Browse Files
</label>
</Button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Left Column - Upload Functionality */}
<div className="space-y-6">
{/* Selected Files */}
{selectedFiles.length > 0 && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h4 className="font-medium">
Selected Files ({selectedFiles.length})
</h4>
<Button variant="ghost" size="sm" onClick={handleReset}>
<X className="h-4 w-4 mr-1" />
Clear All
</Button>
</div>
<div className="space-y-2 max-h-60 overflow-y-auto">
{selectedFiles.map((fileUpload, index) => (
<div
key={fileUpload.id}
className="flex items-center gap-3 p-3 border rounded-lg bg-muted/20"
>
<div className="text-2xl">
{getFileIcon(fileUpload.file.name)}
</div>
<div className="flex-1 min-w-0">
<p
className="font-medium text-sm truncate"
title={fileUpload.file.name}
>
{fileUpload.file.name}
</p>
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<span>{formatFileSize(fileUpload.file.size)}</span>
<span></span>
<span>{fileUpload.file.type || "Unknown type"}</span>
</div>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => moveFile(fileUpload.id, "up")}
disabled={index === 0}
>
<ArrowUpDown className="h-3 w-3" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => removeFile(fileUpload.id)}
>
<Trash2 className="h-3 w-3" />
</Button>
</div>
</div>
))}
</div>
</div>
)}
{/* Multiple Files Configuration */}
{selectedFiles.length > 1 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Copy className="h-5 w-5" />
File Concatenation
</CardTitle>
<CardDescription>
Configure how multiple files should be combined
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="concatenation-method">
Concatenation Method
</Label>
<Select
value={concatenationMethod}
onValueChange={(value: ConcatenationMethod) =>
setConcatenationMethod(value)
}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="newlines">
Separate with blank lines
</SelectItem>
<SelectItem value="timestamps">
Add timestamps and filenames
</SelectItem>
<SelectItem value="custom">Custom separator</SelectItem>
<SelectItem value="none">
No separation (direct concatenation)
</SelectItem>
</SelectContent>
</Select>
</div>
{concatenationMethod === "custom" && (
<div className="space-y-2">
<Label htmlFor="custom-separator">Custom Separator</Label>
<Textarea
id="custom-separator"
value={customSeparator}
onChange={(e) => setCustomSeparator(e.target.value)}
placeholder="Enter custom separator..."
rows={2}
/>
</div>
)}
<div className="space-y-2">
<Label htmlFor="final-filename">Final Filename</Label>
<input
id="final-filename"
type="text"
value={finalFilename}
onChange={(e) => setFinalFilename(e.target.value)}
className="w-full px-3 py-2 border rounded-lg text-sm"
placeholder="Enter filename for combined file..."
/>
</div>
</CardContent>
</Card>
)}
{/* Upload Progress & Actions */}
{selectedFiles.length > 0 && (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
{uploadComplete ? "Upload Complete" : "Ready to Upload"}
</CardTitle>
<CardDescription>
{selectedFiles.length === 1 && selectedFiles[0]
? `Upload ${selectedFiles[0].file.name}`
: `Combine and upload ${selectedFiles.length} files`}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{/* Upload Progress */}
{isUploading && (
<div className="space-y-3">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">
Uploading...
</span>
<span className="font-medium">
{Math.round(uploadProgress)}%
</span>
</div>
<Progress value={uploadProgress} className="h-3" />
</div>
)}
{/* Success State */}
{uploadComplete && (
<div className="p-4 bg-green-50 border border-green-200 rounded-lg">
<div className="flex items-center gap-2 text-green-800 mb-2">
<CheckCircle className="h-5 w-5" />
<span className="font-medium">Upload Successful!</span>
</div>
<p className="text-sm text-green-700 mb-4">
Your trace file has been uploaded and is ready for
analysis.
</p>
<div className="flex gap-2">
<Button onClick={handleViewTrace} size="sm">
View in Traces
</Button>
<Button onClick={handleReset} variant="outline" size="sm">
Upload Another
</Button>
</div>
</div>
)}
{/* Upload Actions */}
{!uploadComplete && (
<div className="flex gap-3">
<Button
variant="outline"
onClick={handleReset}
disabled={isUploading}
className="flex-1"
>
Reset
</Button>
<Button
onClick={handleUpload}
disabled={isUploading}
className="flex-1"
size="lg"
>
{isUploading
? "Uploading..."
: selectedFiles.length > 1
? "Combine & Upload"
: "Upload File"}
</Button>
</div>
)}
</CardContent>
</Card>
)}
{/* Minimum Viable Trace Example */}
<Card>
<CardHeader>
<CardTitle className="text-lg">
Minimum Viable Trace Example
</CardTitle>
<CardDescription>
AgentGraph can process even basic conversation logs with just
agent identification and input/output pairs
</CardDescription>
</CardHeader>
<CardContent>
<div className="bg-slate-50 border rounded-lg p-4">
<pre className="text-xs text-slate-700 whitespace-pre-wrap">
{`{
"agent_name": "assistant",
"input": "user message",
"output": "agent response"
}`}
</pre>
</div>
<p className="text-xs text-muted-foreground mt-3">
<span className="font-medium">Note:</span> Analysis capabilities
scale significantly with additional structured metadata.
</p>
</CardContent>
</Card>
{/* File Guidelines */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Upload className="h-5 w-5" />
File Guidelines
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4 text-sm">
<div>
<h4 className="font-medium mb-2">Supported File Types</h4>
<ul className="space-y-1 text-muted-foreground">
<li>• JSON files (.json) - Structured trace data</li>
<li>• Text files (.txt) - Plain text traces</li>
<li>• Log files (.log) - Application logs</li>
</ul>
</div>
<Separator />
<div>
<h4 className="font-medium mb-2">File Requirements</h4>
<ul className="space-y-1 text-muted-foreground">
<li>• Maximum file size: 50MB per file</li>
<li>• UTF-8 encoding recommended</li>
<li>• Well-formed JSON for .json files</li>
<li>
• Multiple files will be concatenated based on your
settings
</li>
</ul>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Right Column - AgentGraph Trace Requirements */}
<div className="space-y-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
AgentGraph Trace Requirements
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-6 text-sm">
{/* Essential Requirements */}
<div>
<h4 className="font-semibold text-green-700 mb-3">
Essential (Absolute Minimum):
</h4>
<ul className="space-y-2 text-muted-foreground">
<li>
•{" "}
<span className="font-medium">Agent Identification</span>{" "}
- Clear agent names/types to distinguish different actors
</li>
<li>
•{" "}
<span className="font-medium">Input/Output Messages</span>{" "}
- Core conversation content (user inputs and agent
responses)
</li>
</ul>
</div>
<Separator />
{/* Highly Recommended */}
<div>
<h4 className="font-semibold text-blue-700 mb-3">
Highly Recommended:
</h4>
<ul className="space-y-2 text-muted-foreground">
<li>
<span className="font-medium">System Prompts</span> -
Agent instructions and personas (improves entity
classification)
</li>
<li>
•{" "}
<span className="font-medium">
Hierarchical Structure
</span>{" "}
- Parent-child relationships between spans/steps for
execution flow analysis
</li>
<li>
<span className="font-medium">Tool Usage Details</span>{" "}
- Function calls with parameters and return values
(critical for multi-step agents)
</li>
<li>
<span className="font-medium">Error Information</span> -
Exception details and failure modes for anomaly detection
</li>
</ul>
</div>
<Separator />
{/* Optional */}
<div>
<h4 className="font-semibold text-purple-700 mb-3">
Optional (Enhanced Analysis):
</h4>
<ul className="space-y-2 text-muted-foreground">
<li>
•{" "}
<span className="font-medium">
Intermediate Reasoning
</span>{" "}
- Chain-of-thought processes (enhances causal analysis)
</li>
<li>
•{" "}
<span className="font-medium">
Unique Trace Identifier
</span>{" "}
- Consistent ID to track complete agent workflows (e.g.,
trace_id, session_id)
</li>
<li>
<span className="font-medium">Timestamps</span> -
Start/end times for temporal analysis and performance
optimization
</li>
<li>
<span className="font-medium">Basic Status</span> -
Success/failure indication for each step
</li>
<li>
<span className="font-medium">Model Information</span> -
LLM model names, versions, parameters (enables
model-specific insights)
</li>
<li>
<span className="font-medium">Token Usage</span> -
Input/output token counts (for cost analysis)
</li>
<li>
<span className="font-medium">Execution Context</span> -
Environment details, API endpoints, service versions
</li>
</ul>
</div>
<Separator />
{/* Multi-Agent Specific */}
<div>
<h4 className="font-semibold text-orange-700 mb-3">
Multi-Agent Specific (Optional):
</h4>
<ul className="space-y-2 text-muted-foreground">
<li>
<span className="font-medium">Task Decomposition</span>{" "}
- How tasks are split among agents (Normally in the Agent
Setting yaml file in CrewAI)
</li>
<li>
<span className="font-medium">Dependency Tracking</span>{" "}
- Inter-agent dependencies (Access level of each agent)
</li>
<li>
•{" "}
<span className="font-medium">Orchestration Events</span>{" "}
- Workflow coordination and handoffs (Sometimes defined in
the parameters or code)
</li>
</ul>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}