tfrere's picture
update benchmark createForm text ellipsis
c488314
raw
history blame
17.4 kB
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: "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 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: -2 }}>
<Typography
variant="subtitle1"
component="div"
align="center"
sx={{ mb: 2, color: "text.secondary" }}
>
To create a benchmark, choose a sample document or upload your own
file/URL
</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"
color="primary"
onClick={handleGenerateClick}
startIcon={<AutoFixHighIcon />}
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;