Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
import React, { useState, useEffect } from "react"; | |
import { | |
Box, | |
Paper, | |
Typography, | |
CircularProgress, | |
Button, | |
Snackbar, | |
Alert, | |
Grid, | |
IconButton, | |
Tooltip, | |
Dialog, | |
DialogTitle, | |
DialogContent, | |
DialogActions, | |
TextField, | |
Divider, | |
} 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 VisibilityIcon from "@mui/icons-material/Visibility"; | |
import CloseIcon from "@mui/icons-material/Close"; | |
import PublicIcon from "@mui/icons-material/Public"; | |
import { useThemeMode } from "../../hooks/useThemeMode"; | |
import getTheme from "../../config/theme"; | |
import { API_CONFIG, apiService } from "../../config/api"; | |
import { useDocumentSelection } from "./hooks/useDocumentSelection"; | |
/** | |
* 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} CreateForm component | |
*/ | |
function CreateForm({ onStartGeneration }) { | |
const { mode } = useThemeMode(); | |
const theme = getTheme(mode); | |
// États pour la visualisation du document | |
const [openSnackbar, setOpenSnackbar] = useState(false); | |
const [isDownloading, setIsDownloading] = useState(false); | |
const [documentContent, setDocumentContent] = useState(""); | |
const [openContentModal, setOpenContentModal] = useState(false); | |
const [isLoadingContent, setIsLoadingContent] = useState(false); | |
const [modalDocument, setModalDocument] = useState(null); | |
// Utiliser le hook personnalisé pour la gestion des documents | |
const { | |
isDragging, | |
isLoading, | |
sessionId, | |
selectedDocument, | |
isDefaultDocument, | |
urlInput, | |
urlSelected, | |
uploadStatus, | |
fileInputRef, | |
handleDragOver, | |
handleDragLeave, | |
handleClick, | |
handleFileChange, | |
handleDrop, | |
handleDefaultDocClick, | |
handleGenerateClick, | |
handleUrlInputChange, | |
setUploadStatus, | |
} = useDocumentSelection(onStartGeneration); | |
// Afficher le snackbar quand uploadStatus change | |
useEffect(() => { | |
if (uploadStatus) { | |
setOpenSnackbar(true); | |
} | |
}, [uploadStatus]); | |
// Liste des documents par défaut | |
const defaultDocuments = [ | |
{ | |
id: "pokemon-guide", | |
name: "Pokemon Guide", | |
icon: <MenuBookIcon sx={{ fontSize: 40 }} />, | |
description: "A comprehensive guide for Pokemon enthusiasts", | |
}, | |
{ | |
id: "hurricane-faq", | |
name: "Hurricane FAQ", | |
icon: <DescriptionIcon sx={{ fontSize: 40 }} />, | |
description: "Frequently asked questions about hurricanes", | |
}, | |
{ | |
id: "the-bitter-lesson", | |
name: "The Bitter Lesson", | |
icon: <ArticleIcon sx={{ fontSize: 40 }} />, | |
description: "A seminal paper on AI development by Rich Sutton", | |
}, | |
]; | |
const handleCloseSnackbar = () => { | |
setOpenSnackbar(false); | |
}; | |
const handleViewDocument = async (doc) => { | |
setIsLoadingContent(true); | |
try { | |
let extension = ""; | |
if (doc.id === "the-bitter-lesson") { | |
extension = "html"; | |
} else if (doc.id === "hurricane-faq") { | |
extension = "md"; | |
} else { | |
extension = "txt"; | |
} | |
// Mettre à jour l'état du document pour la modale | |
setModalDocument(doc); | |
// Utiliser apiService au lieu de fetch | |
const text = await apiService.getDocumentContent(doc.id, extension); | |
setDocumentContent(text); | |
setOpenContentModal(true); | |
} catch (error) { | |
console.error("Error loading document content:", error); | |
setUploadStatus({ | |
success: false, | |
message: "Error loading document content", | |
}); | |
} finally { | |
setIsLoadingContent(false); | |
} | |
}; | |
const handleCloseContentModal = () => { | |
setOpenContentModal(false); | |
// Réinitialiser après la fermeture de la modale | |
setTimeout(() => { | |
setDocumentContent(""); | |
setModalDocument(null); | |
}, 300); | |
}; | |
const handleDownloadDocument = async (doc) => { | |
setIsDownloading(true); | |
try { | |
let extension = ""; | |
if (doc.id === "the-bitter-lesson") { | |
extension = "html"; | |
} else if (doc.id === "hurricane-faq") { | |
extension = "md"; | |
} else { | |
extension = "txt"; | |
} | |
// Utiliser le service API pour télécharger le document | |
await apiService.downloadDocument(doc.id, extension, doc.name); | |
} catch (error) { | |
console.error("Error downloading document:", error); | |
setUploadStatus({ | |
success: false, | |
message: "Error downloading document", | |
}); | |
} finally { | |
setIsDownloading(false); | |
} | |
}; | |
return ( | |
<Box sx={{ mt: -1 }}> | |
<Typography | |
variant="h6" | |
component="div" | |
align="center" | |
sx={{ color: "text.primary" }} | |
> | |
To create a benchmark, <b>choose a sample document</b> or{" "} | |
<b>upload your own file/URL</b>. | |
</Typography> | |
<Typography | |
variant="subtitle1" | |
component="div" | |
align="center" | |
sx={{ mb: 3, color: "text.secondary" }} | |
> | |
ideally a knowledge base, a FAQ, a news article, etc. | |
</Typography> | |
<Grid container spacing={2} sx={{ mb: 0 }}> | |
{defaultDocuments.map((doc) => ( | |
<Grid item xs={12} sm={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: `0px 3px 6px ${alpha( | |
theme.palette.common.black, | |
0.1 | |
)}`, | |
}, | |
}} | |
onClick={() => handleDefaultDocClick(doc)} | |
> | |
<Tooltip title="View content"> | |
<IconButton | |
onClick={(e) => { | |
e.stopPropagation(); | |
handleViewDocument(doc); | |
}} | |
sx={{ | |
position: "absolute", | |
top: 4, | |
right: 4, | |
color: "text.secondary", | |
opacity: 0.4, | |
"&:hover": { | |
opacity: 0.8, | |
backgroundColor: alpha(theme.palette.primary.main, 0.05), | |
}, | |
padding: 0.3, | |
"& .MuiSvgIcon-root": { | |
fontSize: 16, | |
}, | |
}} | |
disabled={isLoadingContent} | |
> | |
{isLoadingContent && selectedDocument?.id === doc.id ? ( | |
<CircularProgress size={14} /> | |
) : ( | |
<VisibilityIcon /> | |
)} | |
</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> | |
<Box | |
sx={{ | |
display: "flex", | |
flexDirection: { xs: "column", sm: "row" }, | |
gap: 2, | |
mb: 2, | |
}} | |
> | |
{/* Zone de glisser-déposer pour les fichiers */} | |
<Box | |
sx={{ | |
flex: { xs: "1 1 100%", sm: "1 1 50%" }, | |
width: { sm: "50%" }, | |
p: 4, | |
mt: 2, | |
borderRadius: 1.5, | |
border: | |
selectedDocument?.name && !isDefaultDocument && !urlSelected | |
? `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", | |
textAlign: "center", | |
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 && !urlSelected ? ( | |
<> | |
<InsertDriveFileIcon | |
sx={{ fontSize: 50, color: "primary.main", mb: 1 }} | |
/> | |
<Typography | |
variant="h6" | |
component="div" | |
gutterBottom | |
noWrap | |
sx={{ | |
maxWidth: "90%", | |
textOverflow: "ellipsis", | |
overflow: "hidden", | |
}} | |
> | |
{selectedDocument.name} | |
</Typography> | |
<Typography variant="body2" color="text.secondary"> | |
Click to upload a different file | |
</Typography> | |
</> | |
) : ( | |
<> | |
{isLoading && !urlSelected ? ( | |
<CircularProgress size={50} sx={{ mb: 1 }} /> | |
) : ( | |
<CloudUploadIcon | |
sx={{ fontSize: 50, color: "primary.main", mb: 1 }} | |
/> | |
)} | |
<Typography variant="h6" component="div" gutterBottom> | |
{isLoading && !urlSelected | |
? "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> | |
{/* Champ d'entrée URL */} | |
<Box | |
sx={{ | |
flex: { xs: "1 1 100%", sm: "1 1 50%" }, | |
width: { sm: "50%" }, | |
p: 4, | |
mt: { xs: 0, sm: 2 }, | |
borderRadius: 1.5, | |
border: urlSelected | |
? `2px solid ${theme.palette.primary.main}` | |
: `2px dashed ${theme.palette.divider}`, | |
display: "flex", | |
flexDirection: "column", | |
alignItems: "center", | |
justifyContent: "center", | |
textAlign: "center", | |
minHeight: 180, | |
transition: "all 0.3s ease", | |
}} | |
> | |
{selectedDocument?.name && urlSelected ? ( | |
<> | |
<PublicIcon sx={{ fontSize: 50, color: "primary.main", mb: 1 }} /> | |
<Typography | |
variant="h6" | |
component="div" | |
gutterBottom | |
noWrap | |
sx={{ | |
maxWidth: "90%", | |
textOverflow: "ellipsis", | |
overflow: "hidden", | |
}} | |
> | |
{selectedDocument?.domain || "URL processed"} | |
</Typography> | |
<TextField | |
fullWidth | |
variant="outlined" | |
label="Enter a new URL" | |
placeholder="e.g. https://en.wikipedia.org/wiki/Real_Madrid_CF" | |
value={urlInput} | |
onChange={handleUrlInputChange} | |
disabled={isLoading} | |
/> | |
</> | |
) : ( | |
<> | |
{isLoading && urlSelected ? ( | |
<CircularProgress size={50} sx={{ mb: 1 }} /> | |
) : ( | |
<PublicIcon | |
sx={{ fontSize: 50, color: "primary.main", mb: 1 }} | |
/> | |
)} | |
<Typography variant="h6" component="div" gutterBottom> | |
{isLoading && urlSelected | |
? "Processing URL..." | |
: "Enter website address"} | |
</Typography> | |
<TextField | |
fullWidth | |
variant="outlined" | |
label="Website URL..." | |
placeholder="e.g. https://en.wikipedia.org/wiki/Real_Madrid_CF" | |
value={urlInput} | |
onChange={handleUrlInputChange} | |
disabled={isLoading} | |
/> | |
</> | |
)} | |
</Box> | |
</Box> | |
<Box sx={{ display: "flex", justifyContent: "center" }}> | |
<Button | |
variant="contained" | |
size="large" | |
color="primary" | |
onClick={handleGenerateClick} | |
endIcon={<AutoFixHighIcon sx={{ ml: 0.5 }} />} | |
disabled={!sessionId} | |
sx={{ mt: 2 }} | |
> | |
{!sessionId | |
? "Generate benchmark" | |
: isDefaultDocument | |
? `Generate Benchmark from "${selectedDocument?.name}"` | |
: urlSelected | |
? "Generate Benchmark from URL" | |
: "Generate Benchmark from File"} | |
</Button> | |
</Box> | |
<Snackbar | |
open={openSnackbar} | |
autoHideDuration={5000} | |
onClose={handleCloseSnackbar} | |
anchorOrigin={{ vertical: "bottom", horizontal: "right" }} | |
> | |
<Alert | |
onClose={handleCloseSnackbar} | |
severity={uploadStatus?.success ? "success" : "error"} | |
variant="outlined" | |
elevation={1} | |
sx={{ width: "100%" }} | |
> | |
{uploadStatus?.message} | |
</Alert> | |
</Snackbar> | |
<Dialog | |
open={openContentModal} | |
onClose={handleCloseContentModal} | |
maxWidth="md" | |
fullWidth | |
aria-labelledby="document-content-dialog-title" | |
> | |
<DialogTitle id="document-content-dialog-title"> | |
<Box | |
sx={{ | |
display: "flex", | |
justifyContent: "space-between", | |
alignItems: "flex-start", | |
}} | |
> | |
<Box> | |
{modalDocument && ( | |
<Typography variant="h6" sx={{ fontWeight: 600 }}> | |
{modalDocument.name} | |
</Typography> | |
)} | |
<Typography variant="body2" color="text.secondary"> | |
{modalDocument && | |
(modalDocument.id === "the-bitter-lesson" | |
? "HTML" | |
: modalDocument.id === "hurricane-faq" | |
? "Markdown" | |
: "Text")} | |
</Typography> | |
</Box> | |
<Box sx={{ display: "flex", gap: 1 }}> | |
{modalDocument && ( | |
<Tooltip title="Download document"> | |
<IconButton | |
edge="end" | |
color="inherit" | |
onClick={() => handleDownloadDocument(modalDocument)} | |
disabled={isDownloading} | |
aria-label="download" | |
sx={{ | |
color: "text.secondary", | |
opacity: 0.4, | |
"&:hover": { | |
opacity: 0.8, | |
}, | |
}} | |
> | |
{isDownloading ? ( | |
<CircularProgress size={20} /> | |
) : ( | |
<DownloadIcon /> | |
)} | |
</IconButton> | |
</Tooltip> | |
)} | |
<IconButton | |
edge="end" | |
color="inherit" | |
onClick={handleCloseContentModal} | |
aria-label="close" | |
> | |
<CloseIcon /> | |
</IconButton> | |
</Box> | |
</Box> | |
</DialogTitle> | |
<DialogContent | |
dividers | |
sx={{ | |
padding: 0, | |
}} | |
> | |
{isLoadingContent ? ( | |
<Box sx={{ display: "flex", justifyContent: "center", my: 4 }}> | |
<CircularProgress /> | |
</Box> | |
) : ( | |
<Box | |
sx={{ | |
maxHeight: "60vh", | |
overflow: "auto", | |
whiteSpace: "pre-wrap", | |
fontFamily: "monospace", | |
fontSize: "0.875rem", | |
p: 2.5, | |
}} | |
> | |
{documentContent} | |
</Box> | |
)} | |
</DialogContent> | |
</Dialog> | |
</Box> | |
); | |
} | |
export default CreateForm; | |