wu981526092's picture
🚀 Deploy AgentGraph: Complete agent monitoring and knowledge graph system
c2ea5ed
/**
* Context Upload Dialog Component
*
* Dialog for uploading files or pasting text as context documents
*/
import React, { useState, useCallback } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Upload, FileText, AlertCircle, Check } from "lucide-react";
import { ContextDocumentType, CONTEXT_DOCUMENT_TYPES } from "@/types/context";
import { useContextDocuments } from "@/hooks/useContextDocuments";
interface ContextUploadDialogProps {
isOpen: boolean;
onClose: () => void;
traceId: string;
onSuccess: () => void;
}
export function ContextUploadDialog({
isOpen,
onClose,
traceId,
onSuccess,
}: ContextUploadDialogProps) {
const { createDocument, uploadFile, loading, error } = useContextDocuments();
// Form state
const [title, setTitle] = useState("");
const [documentType, setDocumentType] =
useState<ContextDocumentType>("domain_knowledge");
const [content, setContent] = useState("");
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [dragActive, setDragActive] = useState(false);
const resetForm = () => {
setTitle("");
setDocumentType("domain_knowledge");
setContent("");
setSelectedFile(null);
setDragActive(false);
};
const handleClose = () => {
resetForm();
onClose();
};
const handleCreateFromText = async () => {
if (!title.trim() || !content.trim()) {
return;
}
const result = await createDocument(traceId, {
title: title.trim(),
document_type: documentType,
content: content.trim(),
});
if (result) {
resetForm();
onSuccess();
}
};
const handleFileUpload = async () => {
if (!selectedFile || !title.trim()) {
return;
}
const result = await uploadFile(
traceId,
selectedFile,
title.trim(),
documentType
);
if (result) {
resetForm();
onSuccess();
}
};
const handleFileSelect = (file: File) => {
setSelectedFile(file);
if (!title) {
setTitle(file.name.replace(/\.[^/.]+$/, ""));
}
};
const handleDrag = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true);
} else if (e.type === "dragleave") {
setDragActive(false);
}
}, []);
const handleDrop = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
handleFileSelect(e.dataTransfer.files[0]);
}
}, []);
const isValidFile = (file: File) => {
const allowedTypes = [".txt", ".md", ".json", ".csv"];
return allowedTypes.some((type) => file.name.toLowerCase().endsWith(type));
};
const formatFileSize = (bytes: number) => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const sizes = ["Bytes", "KB", "MB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
};
return (
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Add Context Document</DialogTitle>
</DialogHeader>
<Tabs defaultValue="paste" className="w-full">
<TabsList className="grid w-full grid-cols-2">
<TabsTrigger value="paste">
<FileText className="h-4 w-4 mr-2" />
Paste Text
</TabsTrigger>
<TabsTrigger value="upload">
<Upload className="h-4 w-4 mr-2" />
Upload File
</TabsTrigger>
</TabsList>
{/* Common Fields */}
<div className="space-y-4 mt-6">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="title">Title *</Label>
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter document title"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="document-type">Document Type *</Label>
<Select
value={documentType}
onValueChange={(value: ContextDocumentType) =>
setDocumentType(value)
}
>
<SelectTrigger>
<SelectValue placeholder="Select document type" />
</SelectTrigger>
<SelectContent>
{CONTEXT_DOCUMENT_TYPES.map((type) => (
<SelectItem key={type.value} value={type.value}>
<div>
<div className="font-medium">{type.label}</div>
<div className="text-xs text-muted-foreground">
{type.description}
</div>
</div>
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
{/* Tab Content */}
<TabsContent value="paste" className="space-y-4">
<div className="space-y-2">
<Label htmlFor="content">Content *</Label>
<Textarea
id="content"
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Paste or type your context document content here..."
className="min-h-[200px] font-mono text-sm"
required
/>
<div className="text-xs text-muted-foreground text-right">
{content.length.toLocaleString()} / 100,000 characters
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button
onClick={handleCreateFromText}
disabled={
loading ||
!title.trim() ||
!content.trim() ||
content.length > 100000
}
>
{loading ? "Creating..." : "Create Document"}
</Button>
</div>
</TabsContent>
<TabsContent value="upload" className="space-y-4">
<div className="space-y-4">
{/* File Drop Zone */}
<div
className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
dragActive
? "border-primary bg-primary/5"
: "border-muted-foreground/25 hover:border-muted-foreground/50"
}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
>
{selectedFile ? (
<div className="space-y-2">
<Check className="h-8 w-8 text-green-500 mx-auto" />
<div className="font-medium">{selectedFile.name}</div>
<div className="text-sm text-muted-foreground">
{formatFileSize(selectedFile.size)}
</div>
<Button
variant="outline"
size="sm"
onClick={() => setSelectedFile(null)}
>
Choose Different File
</Button>
</div>
) : (
<div className="space-y-2">
<Upload className="h-8 w-8 text-muted-foreground mx-auto" />
<div className="font-medium">
Drop your file here or click to browse
</div>
<div className="text-sm text-muted-foreground">
Supported formats: .txt, .md, .json, .csv (max 1MB)
</div>
<Input
type="file"
accept=".txt,.md,.json,.csv"
onChange={(e) => {
const file = e.target.files?.[0];
if (file && isValidFile(file)) {
handleFileSelect(file);
}
}}
className="hidden"
id="file-upload"
/>
<Button
variant="outline"
onClick={() =>
document.getElementById("file-upload")?.click()
}
>
Browse Files
</Button>
</div>
)}
</div>
{selectedFile && !isValidFile(selectedFile) && (
<div className="flex items-center gap-2 text-destructive text-sm">
<AlertCircle className="h-4 w-4" />
Unsupported file type. Please select a .txt, .md, .json, or
.csv file.
</div>
)}
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={handleClose}>
Cancel
</Button>
<Button
onClick={handleFileUpload}
disabled={
loading ||
!selectedFile ||
!title.trim() ||
!isValidFile(selectedFile)
}
>
{loading ? "Uploading..." : "Upload Document"}
</Button>
</div>
</TabsContent>
</Tabs>
{error && (
<div className="flex items-center gap-2 text-destructive text-sm mt-4 p-3 bg-destructive/10 rounded">
<AlertCircle className="h-4 w-4" />
{error}
</div>
)}
</DialogContent>
</Dialog>
);
}