|
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"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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; |
|
|