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 ( <> {isLoading &&
} {/* Display a progress bar based on task progress */} {taskID && ( Bunka is cooking your data, please wait few seconds {/* {`${normalisePercentage(taskProgress)}%`} */} )} {errorText && ( {errorText} )} {children} ); } TopicsProvider.propTypes = { children: PropTypes.func.isRequired, onSelectView: PropTypes.func.isRequired, };