Spaces:
Build error
Build error
| 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; | |