bunkalab/Argilla-Prompt-Exploration
5
1import { Alert, Box, Typography, Backdrop } from "@mui/material";2import CircularProgress from '@mui/material/CircularProgress';3import axios from "axios";4import PropTypes from "prop-types";5import React, { createContext, useCallback, useEffect, useMemo, useState } from "react";6 7// Create the Context8export const TopicsContext = createContext();9 10const { REACT_APP_API_ENDPOINT } = process.env;11 12const TOPICS_ENDPOINT_PATH = `${REACT_APP_API_ENDPOINT}/topics/csv/`;13const BOURDIEU_ENDPOINT_PATH = `${REACT_APP_API_ENDPOINT}/bourdieu/csv/`;14const REFRESH_BOURDIEU_ENDPOINT_PATH = `${REACT_APP_API_ENDPOINT}/bourdieu/refresh/`;15 16// Fetcher functions17const postForm = (url, data) =>18 axios19 .post(url, data, {20 headers: {21 "Content-Type": "multipart/form-data",22 },23 })24 .then((res) => res.data);25 26const postJson = (url, data) =>27 axios28 .post(url, data, {29 headers: {30 "Content-Type": "application/json",31 },32 })33 .then((res) => res.data);34 35// Provider Component36export function TopicsProvider({ children, onSelectView, selectedView }) {37 const [isLoading, setIsLoading] = useState(false);38 const [data, setData] = useState();39 const [bourdieuData, setBourdieuData] = useState();40 const [error, setError] = useState();41 const [errorText, setErrorText] = useState("");42 const [taskProgress, setTaskProgress] = useState(0); // TODO Add state for task progress when the backend is ready43 const [taskID, setTaskID] = useState(null); // Add state for task ID44 const [currentDatasetId, setCurrentDatasetId] = useState(null); // Current Dataset Id equals Task Id for the moment45 46 const monitorTaskProgress = async (selectedView, taskId) => {47 const evtSource = new EventSource(`${REACT_APP_API_ENDPOINT}/tasks/${selectedView === "map" ? "topics" : "bourdieu"}/${taskId}/progress`);48 evtSource.onmessage = function (event) {49 try {50 const data = JSON.parse(event.data);51 const progress = !isNaN(Math.ceil(data.progress)) ? Math.ceil(data.progress) : 0;52 console.log("Task Progress:", progress);53 setTaskProgress(progress); // Update progress in state54 if (data.state === "SUCCESS") {55 if (selectedView === "map") {56 setData({57 docs: data.result.docs,58 topics: data.result.topics59 });60 setBourdieuData(data.result.bourdieu_response);61 } else if (selectedView === "bourdieu") {62 setBourdieuData(data.result);63 }64 setTaskProgress(100);65 evtSource.close();66 setIsLoading(false);67 setTaskID(null);68 if (onSelectView) onSelectView(selectedView);69 } else if (data.state === "FAILURE") {70 setError(data.error);71 setTaskProgress(0);72 evtSource.close();73 setIsLoading(false);74 evtSource.close();75 }76 } catch (error) {77 console.error("EventSource exception");78 console.error(error);79 setError(error);80 evtSource.close();81 setIsLoading(false);82 }83 };84 };85 86 // Handle File Upload and POST Request87 const uploadFile = useCallback(88 async (file, params) => {89 setIsLoading(true);90 setErrorText("");91 const { nClusters, selectedColumn, selectedView, xLeftWord, xRightWord, yTopWord, yBottomWord, radiusSize } = params;92 const { nameLength, language, cleanTopics, minCountTerms } = params;93 94 try {95 // Generate SHA-256 hash of the file96 const formData = new FormData();97 formData.append("file", file);98 formData.append("selected_column", selectedColumn);99 formData.append("n_clusters", nClusters);100 formData.append("name_length", nameLength);101 formData.append("language", language);102 formData.append("clean_topics", cleanTopics);103 formData.append("min_count_terms", minCountTerms);104 // Append bourdieu parameters, processing activated by defaut105 formData.append("process_bourdieu", true);106 formData.append("x_left_words", xLeftWord);107 formData.append("x_right_words", xRightWord);108 formData.append("y_top_words", yTopWord);109 formData.append("y_bottom_words", yBottomWord);110 formData.append("radius_size", radiusSize);111 112 const apiURI = `${selectedView === "map" ? TOPICS_ENDPOINT_PATH : BOURDIEU_ENDPOINT_PATH}`;113 // Perform the POST request114 const response = await postForm(apiURI, formData);115 setTaskID(response.task_id);116 setCurrentDatasetId(response.task_id);117 await monitorTaskProgress(selectedView, response.task_id); // Start monitoring task progress118 } catch (errorExc) {119 // Handle error120 setError(errorExc);121 setTaskID(null);122 setCurrentDatasetId(null);123 } finally {124 setIsLoading(false);125 }126 },127 [monitorTaskProgress],128 );129 130 const refreshBourdieuQuery = useCallback(131 async (params) => {132 setIsLoading(true);133 setErrorText("");134 if (currentDatasetId !== null) {135 try {136 const apiURI = `${REFRESH_BOURDIEU_ENDPOINT_PATH}${currentDatasetId}`;137 // Perform the POST request138 const response = await postJson(apiURI, params);139 setBourdieuData(response);140 } catch (errorExc) {141 // Handle error142 setError(errorExc);143 } finally {144 setIsLoading(false);145 }146 } else {147 setIsLoading(false);148 setError("Please import a CSV from the Map view before querying");149 }150 },151 [monitorTaskProgress],152 );153 154 /**155 * Handle request errors156 */157 useEffect(() => {158 if (error) {159 const message = error.response?.data?.message || error.message || `${error}` || "An unknown error occurred";160 setErrorText(`Error uploading file.\n${message}`);161 console.error("Error uploading file:", message);162 }163 }, [error]);164 165 /**166 * Shared functions and variables of this TopicsContext and TopicsProvider167 */168 const providerValue = useMemo(169 () => ({170 data,171 bourdieuData,172 uploadFile,173 isLoading,174 error,175 selectedView,176 refreshBourdieuQuery177 }),178 [data, uploadFile, isLoading, error, selectedView, refreshBourdieuQuery],179 );180 181 // const normalisePercentage = (value) => Math.ceil((value * 100) / 100);182 183 return (184 <TopicsContext.Provider value={providerValue}>185 <>186 {isLoading && <div className="loader" />}187 {/* Display a progress bar based on task progress */}188 {taskID && (189 <Backdrop190 sx={{ zIndex: 99999 }}191 open={taskID !== undefined}192 >193 <Box display={"flex"} width="30%" alignItems={"center"} flexDirection={"column"} sx={{ backgrounColor: "#FFF", fontSize: 20, fontWeight: 'medium' }}>194 <Box minWidth={200}>195 <Typography variant="h4">Bunka is cooking your data, please wait few seconds</Typography>196 </Box>197 <CircularProgress />198 {/* <Box minWidth={35}>199 <Typography variant="subtitle">{`${normalisePercentage(taskProgress)}%`}</Typography>200 </Box> */}201 </Box>202 </Backdrop>203 )}204 205 {errorText && (206 <Alert severity="error" className="errorMessage">207 {errorText}208 </Alert>209 )}210 {children}211 </>212 </TopicsContext.Provider>213 );214}215 216TopicsProvider.propTypes = {217 children: PropTypes.func.isRequired,218 onSelectView: PropTypes.func.isRequired,219};220 