demo / frontend /src /components /BenchmarkCreateForm.jsx
tfrere's picture
block >1mo files | translate comments in english
d6f0b38
raw
history blame
12.6 kB
import React, { useState, useRef } from "react";
import {
Box,
Paper,
Typography,
CircularProgress,
Button,
Snackbar,
Alert,
Grid,
IconButton,
Tooltip,
} from "@mui/material";
import { alpha } from "@mui/material/styles";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile";
import DescriptionIcon from "@mui/icons-material/Description";
import ArticleIcon from "@mui/icons-material/Article";
import MenuBookIcon from "@mui/icons-material/MenuBook";
import DownloadIcon from "@mui/icons-material/Download";
import { useThemeMode } from "../hooks/useThemeMode";
import getTheme from "../config/theme";
import API_CONFIG from "../config/api";
/**
* Component for creating a new benchmark, including file upload and generation initiation
*
* @param {Object} props - Component props
* @param {Function} props.onStartGeneration - Callback when generation starts with sessionId
* @returns {JSX.Element} BenchmarkCreateForm component
*/
function BenchmarkCreateForm({ onStartGeneration }) {
const { mode } = useThemeMode();
const theme = getTheme(mode);
const [isDragging, setIsDragging] = useState(false);
const [uploadStatus, setUploadStatus] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [sessionId, setSessionId] = useState(null);
const [openSnackbar, setOpenSnackbar] = useState(false);
const [selectedDocument, setSelectedDocument] = useState(null);
const [isDefaultDocument, setIsDefaultDocument] = useState(false);
const [isDownloading, setIsDownloading] = useState(false);
const fileInputRef = useRef(null);
const defaultDocuments = [
{
id: "the-bitter-lesson",
name: "The Bitter Lesson",
icon: <ArticleIcon sx={{ fontSize: 40 }} />,
description: "A seminal paper on AI development by Rich Sutton",
},
{
id: "hurricane-faq",
name: "Hurricane FAQ",
icon: <DescriptionIcon sx={{ fontSize: 40 }} />,
description: "Frequently asked questions about hurricanes",
},
{
id: "pokemon-guide",
name: "Pokemon Guide",
icon: <MenuBookIcon sx={{ fontSize: 40 }} />,
description: "A comprehensive guide for Pokemon enthusiasts",
},
];
const handleCloseSnackbar = () => {
setOpenSnackbar(false);
};
const handleDragOver = (e) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => {
setIsDragging(false);
};
const handleClick = () => {
fileInputRef.current.click();
};
const handleFileChange = (e) => {
const file = e.target.files[0];
if (!file) return;
// Check if it's a PDF, TXT, HTML or MD
if (
!file.name.endsWith(".pdf") &&
!file.name.endsWith(".txt") &&
!file.name.endsWith(".html") &&
!file.name.endsWith(".md")
) {
setUploadStatus({
success: false,
message: "Only PDF, TXT, HTML and MD files are accepted",
});
setOpenSnackbar(true);
return;
}
// Check file size limit (1MB = 1048576 bytes)
if (file.size > 1048576) {
setUploadStatus({
success: false,
message: "File size exceeds the 1MB limit",
});
setOpenSnackbar(true);
return;
}
handleFileUpload(file);
};
const handleFileUpload = async (file) => {
setIsLoading(true);
setUploadStatus(null);
setIsDefaultDocument(false);
setSelectedDocument(null);
try {
const formData = new FormData();
formData.append("file", file);
const response = await fetch(`${API_CONFIG.BASE_URL}/upload`, {
method: "POST",
body: formData,
});
const result = await response.json();
if (response.ok) {
setUploadStatus({
success: true,
message: `File ${result.filename} uploaded successfully`,
});
setOpenSnackbar(true);
setSessionId(result.session_id);
setSelectedDocument({ name: file.name });
} else {
setUploadStatus({
success: false,
message: result.error || "Upload failed",
});
setOpenSnackbar(true);
}
} catch (error) {
setUploadStatus({
success: false,
message: "Server connection error",
});
setOpenSnackbar(true);
} finally {
setIsLoading(false);
}
};
const handleDrop = async (e) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (!file) {
setUploadStatus({ success: false, message: "No file detected" });
setOpenSnackbar(true);
return;
}
// Check if it's a PDF, TXT, HTML or MD
if (
!file.name.endsWith(".pdf") &&
!file.name.endsWith(".txt") &&
!file.name.endsWith(".html") &&
!file.name.endsWith(".md")
) {
setUploadStatus({
success: false,
message: "Only PDF, TXT, HTML and MD files are accepted",
});
setOpenSnackbar(true);
return;
}
// Check file size limit (1MB = 1048576 bytes)
if (file.size > 1048576) {
setUploadStatus({
success: false,
message: "File size exceeds the 1MB limit",
});
setOpenSnackbar(true);
return;
}
handleFileUpload(file);
};
const handleDefaultDocClick = (doc) => {
setSelectedDocument(doc);
setSessionId(doc.id);
setIsDefaultDocument(true);
};
const handleGenerateClick = () => {
if (onStartGeneration && sessionId) {
onStartGeneration(sessionId, isDefaultDocument);
}
};
const handleDownloadDocument = async (doc) => {
setIsDownloading(true);
try {
const link = document.createElement("a");
link.href = `/${doc.id}.${
doc.id === "the-bitter-lesson"
? "html"
: doc.id === "hurricane-faq"
? "md"
: "txt"
}`;
link.setAttribute(
"download",
`${doc.name}.${
doc.id === "the-bitter-lesson"
? "html"
: doc.id === "hurricane-faq"
? "md"
: "txt"
}`
);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
console.error("Error downloading document:", error);
setUploadStatus({
success: false,
message: "Error downloading document",
});
setOpenSnackbar(true);
} finally {
setIsDownloading(false);
}
};
return (
<Box sx={{ mt: -2 }}>
<Typography
variant="subtitle1"
component="div"
align="center"
sx={{ mb: 2, color: "text.secondary" }}
>
To create a benchmark, choose a sample document
</Typography>
<Grid container spacing={2} sx={{ mb: 2 }}>
{defaultDocuments.map((doc) => (
<Grid item xs={12} md={4} key={doc.id}>
<Box
elevation={2}
sx={{
p: 2,
display: "flex",
flexDirection: "column",
borderRadius: 1.5,
alignItems: "center",
cursor: "pointer",
transition: "all 0.2s ease",
height: "100%",
position: "relative",
border:
selectedDocument?.id === doc.id
? `2px solid ${theme.palette.primary.main}`
: `2px solid ${theme.palette.divider}`,
"&:hover": {
transform: "translateY(-2px)",
boxShadow: 3,
},
}}
onClick={() => handleDefaultDocClick(doc)}
>
<Tooltip title="Download document">
<IconButton
onClick={(e) => {
e.stopPropagation();
handleDownloadDocument(doc);
}}
sx={{
position: "absolute",
top: 4,
right: 4,
color: "text.secondary",
opacity: 0.6,
"&:hover": {
opacity: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.05),
},
padding: 0.5,
"& .MuiSvgIcon-root": {
fontSize: 18,
},
}}
disabled={isDownloading}
>
{isDownloading ? (
<CircularProgress size={16} />
) : (
<DownloadIcon />
)}
</IconButton>
</Tooltip>
<Box sx={{ color: "primary.main", mb: 1 }}>{doc.icon}</Box>
<Typography variant="subtitle1" component="div" gutterBottom>
{doc.name}
</Typography>
<Typography
variant="body2"
color="text.secondary"
align="center"
sx={{ flexGrow: 1 }}
>
{doc.description}
</Typography>
</Box>
</Grid>
))}
</Grid>
<Typography
variant="subtitle1"
component="div"
align="center"
sx={{ mb: 2, color: "text.secondary" }}
>
Or upload your own ...
</Typography>
<Box
sx={{
p: 4,
mt: 2,
mb: 2,
borderRadius: 1.5,
border:
selectedDocument?.name && !isDefaultDocument
? `2px solid ${theme.palette.primary.main}`
: isDragging
? `2px dashed ${theme.palette.primary.main}`
: `2px dashed ${theme.palette.divider}`,
backgroundColor: isDragging
? alpha(theme.palette.action.hover, 0.5)
: "transparent",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: 180,
cursor: "pointer",
transition: "all 0.3s ease",
}}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={handleClick}
>
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
accept=".pdf,.txt,.html,.md"
style={{ display: "none" }}
/>
{selectedDocument?.name && !isDefaultDocument ? (
<>
<InsertDriveFileIcon
sx={{ fontSize: 50, color: "primary.main", mb: 1 }}
/>
<Typography variant="h6" component="div" gutterBottom>
{selectedDocument.name}
</Typography>
<Typography variant="body2" color="text.secondary">
Click to upload a different file
</Typography>
</>
) : (
<>
{isLoading ? (
<CircularProgress size={50} sx={{ mb: 1 }} />
) : (
<CloudUploadIcon
sx={{ fontSize: 50, color: "primary.main", mb: 1 }}
/>
)}
<Typography variant="h6" component="div" gutterBottom>
{isLoading
? "Uploading your file..."
: "Drag and drop your file here or click to browse"}
</Typography>
<Typography variant="body2" color="text.secondary">
Accepted formats: PDF, TXT, HTML, MD
</Typography>
</>
)}
</Box>
<Box sx={{ display: "flex", justifyContent: "center" }}>
<Button
variant="contained"
color="primary"
onClick={handleGenerateClick}
startIcon={<AutoFixHighIcon />}
disabled={!sessionId}
sx={{ mt: 2 }}
>
Generate Benchmark
</Button>
</Box>
<Snackbar
open={openSnackbar}
autoHideDuration={6000}
onClose={handleCloseSnackbar}
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
>
<Alert
onClose={handleCloseSnackbar}
severity={uploadStatus?.success ? "success" : "error"}
sx={{ width: "100%" }}
>
{uploadStatus?.message}
</Alert>
</Snackbar>
</Box>
);
}
export default BenchmarkCreateForm;