Spaces:
Runtime error
Runtime error
| import { Alert, Box, Typography, Backdrop } from "@mui/material"; | |
| import CircularProgress from '@mui/material/CircularProgress'; | |
| import axios from "axios"; | |
| import PropTypes from "prop-types"; | |
| import React, { createContext, useCallback, useEffect, useMemo, useState } from "react"; | |
| // Create the Context | |
| export const TopicsContext = createContext(); | |
| const { REACT_APP_API_ENDPOINT } = process.env; | |
| const TOPICS_ENDPOINT_PATH = `${REACT_APP_API_ENDPOINT}/topics/csv/`; | |
| const BOURDIEU_ENDPOINT_PATH = `${REACT_APP_API_ENDPOINT}/bourdieu/csv/`; | |
| const REFRESH_BOURDIEU_ENDPOINT_PATH = `${REACT_APP_API_ENDPOINT}/bourdieu/refresh/`; | |
| // Fetcher functions | |
| const postForm = (url, data) => | |
| axios | |
| .post(url, data, { | |
| headers: { | |
| "Content-Type": "multipart/form-data", | |
| }, | |
| }) | |
| .then((res) => res.data); | |
| const postJson = (url, data) => | |
| axios | |
| .post(url, data, { | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| }) | |
| .then((res) => res.data); | |
| // Provider Component | |
| export function TopicsProvider({ children, onSelectView, selectedView }) { | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [data, setData] = useState(); | |
| const [bourdieuData, setBourdieuData] = useState(); | |
| const [error, setError] = useState(); | |
| const [errorText, setErrorText] = useState(""); | |
| const [taskProgress, setTaskProgress] = useState(0); // TODO Add state for task progress when the backend is ready | |
| const [taskID, setTaskID] = useState(null); // Add state for task ID | |
| const [currentDatasetId, setCurrentDatasetId] = useState(null); // Current Dataset Id equals Task Id for the moment | |
| const monitorTaskProgress = async (selectedView, taskId) => { | |
| const evtSource = new EventSource(`${REACT_APP_API_ENDPOINT}/tasks/${selectedView === "map" ? "topics" : "bourdieu"}/${taskId}/progress`); | |
| evtSource.onmessage = function (event) { | |
| try { | |
| const data = JSON.parse(event.data); | |
| const progress = !isNaN(Math.ceil(data.progress)) ? Math.ceil(data.progress) : 0; | |
| console.log("Task Progress:", progress); | |
| setTaskProgress(progress); // Update progress in state | |
| if (data.state === "SUCCESS") { | |
| if (selectedView === "map") { | |
| setData({ | |
| docs: data.result.docs, | |
| topics: data.result.topics | |
| }); | |
| setBourdieuData(data.result.bourdieu_response); | |
| } else if (selectedView === "bourdieu") { | |
| setBourdieuData(data.result); | |
| } | |
| setTaskProgress(100); | |
| evtSource.close(); | |
| setIsLoading(false); | |
| setTaskID(null); | |
| if (onSelectView) onSelectView(selectedView); | |
| } else if (data.state === "FAILURE") { | |
| setError(data.error); | |
| setTaskProgress(0); | |
| evtSource.close(); | |
| setIsLoading(false); | |
| evtSource.close(); | |
| } | |
| } catch (error) { | |
| console.error("EventSource exception"); | |
| console.error(error); | |
| setError(error); | |
| evtSource.close(); | |
| setIsLoading(false); | |
| } | |
| }; | |
| }; | |
| // Handle File Upload and POST Request | |
| const uploadFile = useCallback( | |
| async (file, params) => { | |
| setIsLoading(true); | |
| setErrorText(""); | |
| const { nClusters, selectedColumn, selectedView, xLeftWord, xRightWord, yTopWord, yBottomWord, radiusSize } = params; | |
| const { nameLength, language, cleanTopics, minCountTerms } = params; | |
| try { | |
| // Generate SHA-256 hash of the file | |
| const formData = new FormData(); | |
| formData.append("file", file); | |
| formData.append("selected_column", selectedColumn); | |
| formData.append("n_clusters", nClusters); | |
| formData.append("name_length", nameLength); | |
| formData.append("language", language); | |
| formData.append("clean_topics", cleanTopics); | |
| formData.append("min_count_terms", minCountTerms); | |
| // Append bourdieu parameters, processing activated by defaut | |
| formData.append("process_bourdieu", true); | |
| formData.append("x_left_words", xLeftWord); | |
| formData.append("x_right_words", xRightWord); | |
| formData.append("y_top_words", yTopWord); | |
| formData.append("y_bottom_words", yBottomWord); | |
| formData.append("radius_size", radiusSize); | |
| const apiURI = `${selectedView === "map" ? TOPICS_ENDPOINT_PATH : BOURDIEU_ENDPOINT_PATH}`; | |
| // Perform the POST request | |
| const response = await postForm(apiURI, formData); | |
| setTaskID(response.task_id); | |
| setCurrentDatasetId(response.task_id); | |
| await monitorTaskProgress(selectedView, response.task_id); // Start monitoring task progress | |
| } catch (errorExc) { | |
| // Handle error | |
| setError(errorExc); | |
| setTaskID(null); | |
| setCurrentDatasetId(null); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| }, | |
| [monitorTaskProgress], | |
| ); | |
| const refreshBourdieuQuery = useCallback( | |
| async (params) => { | |
| setIsLoading(true); | |
| setErrorText(""); | |
| if (currentDatasetId !== null) { | |
| try { | |
| const apiURI = `${REFRESH_BOURDIEU_ENDPOINT_PATH}${currentDatasetId}`; | |
| // Perform the POST request | |
| const response = await postJson(apiURI, params); | |
| setBourdieuData(response); | |
| } catch (errorExc) { | |
| // Handle error | |
| setError(errorExc); | |
| } finally { | |
| setIsLoading(false); | |
| } | |
| } else { | |
| setIsLoading(false); | |
| setError("Please import a CSV from the Map view before querying"); | |
| } | |
| }, | |
| [monitorTaskProgress], | |
| ); | |
| /** | |
| * Handle request errors | |
| */ | |
| useEffect(() => { | |
| if (error) { | |
| const message = error.response?.data?.message || error.message || `${error}` || "An unknown error occurred"; | |
| setErrorText(`Error uploading file.\n${message}`); | |
| console.error("Error uploading file:", message); | |
| } | |
| }, [error]); | |
| /** | |
| * Shared functions and variables of this TopicsContext and TopicsProvider | |
| */ | |
| const providerValue = useMemo( | |
| () => ({ | |
| data, | |
| bourdieuData, | |
| uploadFile, | |
| isLoading, | |
| error, | |
| selectedView, | |
| refreshBourdieuQuery | |
| }), | |
| [data, uploadFile, isLoading, error, selectedView, refreshBourdieuQuery], | |
| ); | |
| // const normalisePercentage = (value) => Math.ceil((value * 100) / 100); | |
| return ( | |
| <TopicsContext.Provider value={providerValue}> | |
| <> | |
| {isLoading && <div className="loader" />} | |
| {/* Display a progress bar based on task progress */} | |
| {taskID && ( | |
| <Backdrop | |
| sx={{ zIndex: 99999 }} | |
| open={taskID !== undefined} | |
| > | |
| <Box display={"flex"} width="30%" alignItems={"center"} flexDirection={"column"} sx={{ backgrounColor: "#FFF", fontSize: 20, fontWeight: 'medium' }}> | |
| <Box minWidth={200}> | |
| <Typography variant="h4">Bunka is cooking your data, please wait few seconds</Typography> | |
| </Box> | |
| <CircularProgress /> | |
| {/* <Box minWidth={35}> | |
| <Typography variant="subtitle">{`${normalisePercentage(taskProgress)}%`}</Typography> | |
| </Box> */} | |
| </Box> | |
| </Backdrop> | |
| )} | |
| {errorText && ( | |
| <Alert severity="error" className="errorMessage"> | |
| {errorText} | |
| </Alert> | |
| )} | |
| {children} | |
| </> | |
| </TopicsContext.Provider> | |
| ); | |
| } | |
| TopicsProvider.propTypes = { | |
| children: PropTypes.func.isRequired, | |
| onSelectView: PropTypes.func.isRequired, | |
| }; | |