diff --git a/.gitignore b/.gitignore index 04d23fc8..78076677 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,5 @@ backend/token.json backend/service_account_key.json venv backend/Eduaid -.DS_Store \ No newline at end of file +.DS_Store +backend/s2v_reddit_2015_md.tar.gz diff --git a/eduaid_web/src/App.js b/eduaid_web/src/App.js index e9eef0a0..49717d34 100644 --- a/eduaid_web/src/App.js +++ b/eduaid_web/src/App.js @@ -3,6 +3,7 @@ import { Routes, Route, HashRouter } from "react-router-dom"; import Home from "./pages/Home"; import Question_Type from "./pages/Question_Type"; import Text_Input from "./pages/Text_Input"; +import Review from "./pages/Review"; import Output from "./pages/Output"; import Previous from "./pages/Previous"; import NotFound from "./pages/PageNotFound"; @@ -14,6 +15,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/eduaid_web/src/pages/Review.jsx b/eduaid_web/src/pages/Review.jsx new file mode 100644 index 00000000..bee9f252 --- /dev/null +++ b/eduaid_web/src/pages/Review.jsx @@ -0,0 +1,233 @@ +import React, { useState, useEffect } from "react"; +import "../index.css"; +import logo_trans from "../assets/aossie_logo_transparent.png"; +import { Link, useNavigate } from "react-router-dom"; +import apiClient from "../utils/apiClient"; + +const Review = () => { + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [reviewData, setReviewData] = useState({ + text: "", + difficulty: "", + numQuestions: 0, + questionType: "", + useWikipedia: false, + inputSource: "Text" + }); + + useEffect(() => { + const text = localStorage.getItem("textContent") || ""; + const difficulty = localStorage.getItem("difficulty") || "Easy Difficulty"; + const savedNumQuestions = localStorage.getItem("numQuestions"); + const parsedNumQuestions = Number.parseInt(savedNumQuestions ?? "", 10); + const numQuestions = Number.isInteger(parsedNumQuestions) && parsedNumQuestions > 0 ? parsedNumQuestions : 10; + const questionType = localStorage.getItem("selectedQuestionType") || ""; + const useWikipedia = localStorage.getItem("useWikipedia") === "1"; + const savedInputSource = localStorage.getItem("inputSource"); + + let inputSource = savedInputSource || "text"; + if (!savedInputSource) { + if (text.includes("uploaded file") || text.includes("Error uploading")) { + inputSource = "file"; + } else if (text.includes("Google Doc")) { + inputSource = "url"; + } + } + + setReviewData({ + text, + difficulty, + numQuestions, + questionType, + useWikipedia, + inputSource + }); + + if (!text || !questionType) { + navigate("/input"); + } + }, [navigate]); + + const getInputSourceLabel = (source) => { + const labels = { + text: "Text", + file: "File Upload", + url: "Google Doc URL" + }; + return labels[source] || "Text"; + }; + + const getQuestionTypeLabel = (type) => { + const types = { + get_shortq: "Short-Answer Type Questions", + get_mcq: "Multiple Choice Questions", + get_boolq: "True/False Questions", + get_problems: "All Questions" + }; + return types[type] || type; + }; + + const getEndpoint = (difficulty, questionType) => { + if (difficulty !== "Easy Difficulty") { + if (questionType === "get_shortq") { + return "get_shortq_hard"; + } else if (questionType === "get_mcq") { + return "get_mcq_hard"; + } + } + return questionType; + }; + + const handleConfirmGenerate = async () => { + setLoading(true); + const endpoint = getEndpoint(reviewData.difficulty, reviewData.questionType); + + const allowedEndpoints = ["get_shortq", "get_mcq", "get_boolq", "get_problems", "get_shortq_hard", "get_mcq_hard"]; + if (!allowedEndpoints.includes(endpoint)) { + console.error("Invalid endpoint:", endpoint); + setLoading(false); + return; + } + + const trimmedText = reviewData.text.trim(); + if (!trimmedText || !Number.isInteger(reviewData.numQuestions) || reviewData.numQuestions <= 0) { + console.error("Invalid generation payload"); + setLoading(false); + return; + } + + try { + const requestData = { + input_text: trimmedText, + max_questions: reviewData.numQuestions, + use_mediawiki: reviewData.useWikipedia ? 1 : 0, + }; + + const responseData = await apiClient.post(`/${endpoint}`, requestData); + localStorage.setItem("qaPairs", JSON.stringify(responseData)); + + const quizDetails = { + difficulty: reviewData.difficulty, + numQuestions: reviewData.numQuestions, + date: new Date().toLocaleDateString(), + qaPair: responseData, + }; + + let last5Quizzes = []; + try { + const stored = localStorage.getItem("last5Quizzes"); + if (stored) { + const parsed = JSON.parse(stored); + if (Array.isArray(parsed)) { + last5Quizzes = parsed; + } + } + } catch (parseError) { + console.error("Failed to parse last5Quizzes:", parseError); + } + + last5Quizzes.push(quizDetails); + if (last5Quizzes.length > 5) { + last5Quizzes.shift(); + } + localStorage.setItem("last5Quizzes", JSON.stringify(last5Quizzes)); + + navigate("/output"); + } catch (error) { + console.error("Error:", error); + } finally { + setLoading(false); + } + }; + + return ( +
+ {loading && ( +
+
+
+ )} + +
+ +
+ logo +
+ Edu + Aid +
+
+ + +
+
Review Your Configuration
+

Please confirm the details before generating questions

+
+ +
+
+
+
Input Source
+
{getInputSourceLabel(reviewData.inputSource)}
+
+ +
+
Question Type
+
+ {getQuestionTypeLabel(reviewData.questionType)} +
+
+ +
+
Number of Questions
+
{reviewData.numQuestions}
+
+ +
+
Difficulty Level
+
{reviewData.difficulty}
+
+ +
+
Use Wikipedia
+
+ {reviewData.useWikipedia ? "Yes" : "No"} +
+
+ + {reviewData.text && ( +
+
Content Preview
+
+ {reviewData.text.substring(0, 200)} + {reviewData.text.length > 200 && "..."} +
+
+ )} +
+ +
+ + + + +
+
+
+
+ ); +}; + +export default Review; diff --git a/eduaid_web/src/pages/Text_Input.jsx b/eduaid_web/src/pages/Text_Input.jsx index e341d331..59ed6302 100644 --- a/eduaid_web/src/pages/Text_Input.jsx +++ b/eduaid_web/src/pages/Text_Input.jsx @@ -1,11 +1,11 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useEffect } from "react"; import "../index.css"; import logo_trans from "../assets/aossie_logo_transparent.png" import stars from "../assets/stars.png"; import cloud from "../assets/cloud.png"; import { FaClipboard } from "react-icons/fa"; import Switch from "react-switch"; -import { Link,useNavigate } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import apiClient from "../utils/apiClient"; const Text_Input = () => { @@ -15,9 +15,38 @@ const Text_Input = () => { const [numQuestions, setNumQuestions] = useState(10); const [loading, setLoading] = useState(false); const fileInputRef = useRef(null); - const [fileContent, setFileContent] = useState(""); const [docUrl, setDocUrl] = useState(""); const [isToggleOn, setIsToggleOn] = useState(0); + const [inputSource, setInputSource] = useState("text"); + const [error, setError] = useState(""); + + useEffect(() => { + const savedText = localStorage.getItem("textContent"); + const savedDifficulty = localStorage.getItem("difficulty"); + const savedNumQuestions = localStorage.getItem("numQuestions"); + const savedWikipedia = localStorage.getItem("useWikipedia"); + const savedInputSource = localStorage.getItem("inputSource"); + + if (savedText) setText(savedText); + if (savedDifficulty) setDifficulty(savedDifficulty); + if (savedNumQuestions !== null) { + const parsedNumQuestions = Number.parseInt(savedNumQuestions, 10); + + setNumQuestions( + Number.isInteger(parsedNumQuestions) && parsedNumQuestions > 0 + ? parsedNumQuestions + : 10 + ); + } + if (savedWikipedia !== null) { + const normalizedWikipedia = + savedWikipedia === "1" || savedWikipedia === "true" + ? 1 + : 0; + setIsToggleOn(normalizedWikipedia); + } + if (savedInputSource) setInputSource(savedInputSource); + }, []); const toggleSwitch = () => { setIsToggleOn((isToggleOn + 1) % 2); @@ -31,10 +60,16 @@ const Text_Input = () => { try { const data = await apiClient.postFormData("/upload", formData); - setText(data.content || data.error); + + if (data && typeof data.content === "string" && data.content.trim()) { + setText(data.content.trim()); + setInputSource("file"); + } else { + setError("Invalid file content received."); + } } catch (error) { console.error("Error uploading file:", error); - setText("Error uploading file"); + setError("Error uploading file. Please try again."); } } }; @@ -48,31 +83,49 @@ const Text_Input = () => { }; const handleSaveToLocalStorage = async () => { - setLoading(true); - - // Check if a Google Doc URL is provided - if (docUrl) { + const trimmedUrl = docUrl.trim(); + const trimmedText = text.trim(); + const validQuestionCount = Number.isInteger(numQuestions) && numQuestions > 0; + if (!validQuestionCount) { + console.error("Number of questions must be a positive integer"); + setError("Number of questions must be at least 1."); + return; + } + if (trimmedUrl) { + setError(""); + setLoading(true); try { - const data = await apiClient.post("/get_content", { document_url: docUrl }); + const data = await apiClient.post("/get_content", { document_url: trimmedUrl }); + if (!data || typeof data !== "string" || !data.trim()) { + setError("could not retrieve content from the provided URL."); + setLoading(false); + return; + } + + const fetchedText = data.trim(); setDocUrl(""); - setText(data || "Error in retrieving"); + setText(fetchedText); + setInputSource("url"); + + localStorage.setItem("textContent", fetchedText); + localStorage.setItem("difficulty", difficulty); + localStorage.setItem("numQuestions", numQuestions.toString()); + localStorage.setItem("useWikipedia", isToggleOn.toString()); + localStorage.setItem("inputSource", "url"); + navigate("/review"); } catch (error) { console.error("Error:", error); - setText("Error retrieving Google Doc content"); + setError("failed to fetch document content. Please try again.") } finally { setLoading(false); } - } else if (text) { - // Proceed with existing functionality for local storage - localStorage.setItem("textContent", text); + } else if (trimmedText) { + localStorage.setItem("textContent", trimmedText); localStorage.setItem("difficulty", difficulty); - localStorage.setItem("numQuestions", numQuestions); - - await sendToBackend( - text, - difficulty, - localStorage.getItem("selectedQuestionType") - ); + localStorage.setItem("numQuestions", numQuestions.toString()); + localStorage.setItem("useWikipedia", isToggleOn.toString()); + localStorage.setItem("inputSource", inputSource); + navigate("/review"); } }; @@ -88,53 +141,6 @@ const Text_Input = () => { setNumQuestions((prev) => (prev > 0 ? prev - 1 : 0)); }; - const getEndpoint = (difficulty, questionType) => { - if (difficulty !== "Easy Difficulty") { - if (questionType === "get_shortq") { - return "get_shortq_hard"; - } else if (questionType === "get_mcq") { - return "get_mcq_hard"; - } - } - return questionType; - }; - - const sendToBackend = async (data, difficulty, questionType) => { - const endpoint = getEndpoint(difficulty, questionType); - try { - const requestData = { - input_text: data, - max_questions: numQuestions, - use_mediawiki: isToggleOn, - }; - - const responseData = await apiClient.post(`/${endpoint}`, requestData); - localStorage.setItem("qaPairs", JSON.stringify(responseData)); - - // Save quiz details to local storage - const quizDetails = { - difficulty, - numQuestions, - date: new Date().toLocaleDateString(), - qaPair: responseData, - }; - - let last5Quizzes = - JSON.parse(localStorage.getItem("last5Quizzes")) || []; - last5Quizzes.push(quizDetails); - if (last5Quizzes.length > 5) { - last5Quizzes.shift(); // Keep only the last 5 quizzes - } - localStorage.setItem("last5Quizzes", JSON.stringify(last5Quizzes)); - - navigate("/output"); - } catch (error) { - console.error("Error:", error); - } finally { - setLoading(false); - } - }; - return (
{loading && ( @@ -174,7 +180,10 @@ const Text_Input = () => { className="absolute inset-0 p-8 pt-6 bg-[#83b6cc40] text-lg sm:text-xl rounded-2xl outline-none resize-none h-full overflow-y-auto text-white caret-white" style={{ scrollbarWidth: "none", msOverflowStyle: "none" }} value={text} - onChange={(e) => setText(e.target.value)} + onChange={(e) => { + setText(e.target.value); + setInputSource("text"); + }} />
@@ -202,6 +211,11 @@ const Text_Input = () => { value={docUrl} onChange={(e) => setDocUrl(e.target.value)} /> + {error && ( +
+ {error} +
+ )} {/* Controls Section */}