diff --git a/eduaid_desktop/main.js b/eduaid_desktop/main.js index b0346138..2148cc22 100644 --- a/eduaid_desktop/main.js +++ b/eduaid_desktop/main.js @@ -1,28 +1,28 @@ const { app, BrowserWindow, Menu, shell, dialog, ipcMain } = require('electron'); const path = require('path'); -const fs = require('fs'); const config = require('./config'); const https = require('https'); const http = require('http'); -// Keep a global reference of the window object let mainWindow; function createWindow() { - // Create the browser window using configuration mainWindow = new BrowserWindow({ ...config.windowOptions, icon: config.window.icon, - webPreferences: config.window.webPreferences, - show: false, // Don't show until ready + + // πŸ”₯ FIX: Explicit preload path + webPreferences: { + ...config.window.webPreferences, + preload: path.join(__dirname, 'preload.js'), + }, + + show: false, titleBarStyle: config.window.titleBarStyle }); - // Determine which URL to load based on environment if (!app.isPackaged) { - // In development, load from the dev server mainWindow.loadURL(config.webUrl); - // Open DevTools in development if (config.devTools) { mainWindow.webContents.openDevTools(); } @@ -30,36 +30,29 @@ function createWindow() { mainWindow.loadFile(config.webPath); } - // Show window when ready to prevent visual flash mainWindow.once('ready-to-show', () => { mainWindow.show(); - - // Focus on the window if (config.isDevelopment) { mainWindow.focus(); } }); - // Handle window closed mainWindow.on('closed', () => { mainWindow = null; }); - // Handle external links mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); - // Prevent navigation to external URLs mainWindow.webContents.on('will-navigate', (event, navigationUrl) => { const parsedUrl = new URL(navigationUrl); - - // Check if the URL is in our allowed origins - const isAllowed = config.security.allowedOrigins.some(origin => + + const isAllowed = config.security.allowedOrigins.some(origin => parsedUrl.origin === origin || navigationUrl.startsWith(origin) ); - + if (!isAllowed) { event.preventDefault(); shell.openExternal(navigationUrl); @@ -67,7 +60,6 @@ function createWindow() { }); } -// Create application menu function createMenu() { const template = [ { @@ -86,9 +78,7 @@ function createMenu() { { label: 'Quit', accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q', - click: () => { - app.quit(); - } + click: () => app.quit() } ] }, @@ -149,7 +139,6 @@ function createMenu() { } ]; - // macOS specific menu adjustments if (process.platform === 'darwin') { template.unshift({ label: app.getName(), @@ -165,28 +154,17 @@ function createMenu() { { role: 'quit' } ] }); - - // Window menu - template[4].submenu = [ - { role: 'close' }, - { role: 'minimize' }, - { role: 'zoom' }, - { type: 'separator' }, - { role: 'front' } - ]; } const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); } -// App event handlers app.whenReady().then(() => { createWindow(); createMenu(); app.on('activate', () => { - // On macOS, re-create window when dock icon is clicked if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } @@ -194,13 +172,11 @@ app.whenReady().then(() => { }); app.on('window-all-closed', () => { - // On macOS, keep app running even when all windows are closed if (process.platform !== 'darwin') { app.quit(); } }); -// Security: Prevent new window creation app.on('web-contents-created', (event, contents) => { contents.on('new-window', (event, navigationUrl) => { event.preventDefault(); @@ -208,10 +184,8 @@ app.on('web-contents-created', (event, contents) => { }); }); -// Handle certificate errors app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { if (url.startsWith('http://localhost:')) { - // Allow localhost in development event.preventDefault(); callback(true); } else { @@ -219,10 +193,14 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba } }); -// API request handler for the renderer process -ipcMain.handle('api-request', async (event, { endpoint, options }) => { + +// ================================ +// πŸ”₯ API REQUEST HANDLER (Timeout Fix) +// ================================ +ipcMain.handle('api-request', async (event, { endpoint, options = {} }) => { return new Promise((resolve, reject) => { const apiUrl = new URL(endpoint, config.apiUrl); + const requestOptions = { method: options.method || 'GET', headers: { @@ -232,32 +210,40 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => { }; const protocol = apiUrl.protocol === 'https:' ? https : http; - + const req = protocol.request(apiUrl, requestOptions, (res) => { let data = ''; - + res.on('data', (chunk) => { data += chunk; }); - + res.on('end', () => { try { - const jsonData = JSON.parse(data); + const parsed = data ? JSON.parse(data) : null; + resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode, - data: jsonData + data: parsed }); - } catch (error) { + } catch (err) { resolve({ - ok: res.statusCode >= 200 && res.statusCode < 300, + ok: false, status: res.statusCode, - data: data + data: null, + error: 'Invalid JSON response' }); } }); }); + // πŸ”₯ Timeout after 15 seconds + req.setTimeout(15000, () => { + req.destroy(); + reject(new Error('Request timed out after 15 seconds')); + }); + req.on('error', (error) => { reject(error); }); @@ -268,4 +254,4 @@ ipcMain.handle('api-request', async (event, { endpoint, options }) => { req.end(); }); -}); +}); \ No newline at end of file diff --git a/eduaid_desktop/preload.js b/eduaid_desktop/preload.js index 080f3dd8..f43a91ff 100644 --- a/eduaid_desktop/preload.js +++ b/eduaid_desktop/preload.js @@ -1,19 +1,21 @@ const { contextBridge, ipcRenderer } = require("electron"); -const config = require("./config"); +const path = require("path"); -// Expose protected methods that allow the renderer process to use -// the ipcRenderer without exposing the entire object +// πŸ”₯ FIXED: Absolute path for config +const config = require(path.join(__dirname, "config.js")); + +// Expose protected methods contextBridge.exposeInMainWorld("electronAPI", { // Platform information platform: process.platform, - // API Configuration (NEW) + // API Configuration getApiConfig: () => ({ baseUrl: config.apiUrl, env: config.env, }), - // Secure API Proxy (NEW) + // Secure API Proxy makeApiRequest: async (endpoint, options) => { return ipcRenderer.invoke("api-request", { endpoint, @@ -36,7 +38,7 @@ contextBridge.exposeInMainWorld("electronAPI", { return process.env.npm_package_version || "1.0.0"; }, - // File operations (if needed in the future) + // File operations openFile: () => { return ipcRenderer.invoke("dialog:openFile"); }, @@ -46,26 +48,24 @@ contextBridge.exposeInMainWorld("electronAPI", { }, }); -// DOM Content Loaded event +// DOM Ready window.addEventListener("DOMContentLoaded", () => { - // Add any initialization code here console.log("EduAid Desktop App loaded"); - // Add desktop-specific styling or behavior document.body.classList.add("electron-app"); - // Handle keyboard shortcuts document.addEventListener("keydown", (event) => { - // Prevent default browser shortcuts that don't make sense in desktop app const isMod = event.ctrlKey || event.metaKey; if (!isMod) return; + const key = event.key.toLowerCase(); const blockShortcuts = { r: !event.shiftKey, w: true, }; + if (blockShortcuts[key]) { event.preventDefault(); } }); -}); +}); \ No newline at end of file diff --git a/eduaid_web/src/pages/Home.jsx b/eduaid_web/src/pages/Home.jsx index e00a0d23..832a5eb6 100644 --- a/eduaid_web/src/pages/Home.jsx +++ b/eduaid_web/src/pages/Home.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import "../index.css"; -import logo_trans from "../assets/aossie_logo_transparent.png" +import logo_trans from "../assets/aossie_logo_transparent.png"; import starsImg from "../assets/stars.png"; import arrow from "../assets/arrow.png"; import gitStar from "../assets/gitStar.png"; @@ -12,9 +12,21 @@ const Home = () => { const [error, setError] = useState(""); async function fetchGitHubStars() { - const response = await fetch("https://api.github.com/repos/AOSSIE-Org/EduAid"); - if (!response.ok) throw new Error("Failed to fetch stars"); + const response = await fetch( + "https://api.github.com/repos/AOSSIE-Org/EduAid" + ); + const data = await response.json(); + + if ( + !response.ok || + !data || + typeof data.stargazers_count !== "number" + ) { + console.error("Invalid GitHub API response:", data); + throw new Error("Invalid GitHub response"); + } + return data.stargazers_count; } @@ -27,24 +39,36 @@ const Home = () => { const storedStars = localStorage.getItem("stars"); const storedTime = localStorage.getItem("fetchTime"); - if (storedStars && storedTime && !isMoreThanOneDayOld(parseInt(storedTime))) { - setStars(parseInt(storedStars)); - } else { - fetchGitHubStars() - .then((starCount) => { - setStars(starCount); - localStorage.setItem("stars", starCount); - localStorage.setItem("fetchTime", Date.now().toString()); - }) - .catch(() => setError("Failed to fetch stars")); + if (storedStars !== null && storedTime !== null) { + const cachedStars = Number(storedStars); + const cachedTime = Number(storedTime); + + if ( + Number.isFinite(cachedStars) && + Number.isFinite(cachedTime) && + !isMoreThanOneDayOld(cachedTime) + ) { + setStars(cachedStars); + return; + } } + + fetchGitHubStars() + .then((starCount) => { + setStars(starCount); + localStorage.setItem("stars", String(starCount)); + localStorage.setItem("fetchTime", Date.now().toString()); + }) + .catch(() => setError("Failed to fetch stars")); }, []); return ( -
+
-
- logo +
+ + {/* Logo */} + EduAid Logo {/* Heading */}

@@ -57,44 +81,110 @@ const Home = () => {

{/* Subtitle */} -
-

A tool that can auto-generate short quizzes

-
-

based on user input

- stars -
+
+

Turn your notes into interactive quizzes in seconds.

+

+ Upload your PDF or DOC file and start practicing instantly. +

- {/* Features */} -
+ {/* Feature Highlights */} +
{[ - "Doc/Audio Input", - "In-depth questions gen", - "Dynamic Google Form Integration", + "PDF / DOC Upload", + "AI-powered Quiz Generation", + "Instant Practice Mode", ].map((feature, i) => (
-
{feature}
+
+ {feature} +
))}
- {/* Buttons */} -
- - + {/* Primary Buttons */} +
+ + {/* βœ… Changed to /input */} + + Upload & Generate Quiz + - - + + + View Previous Work + + + +
+ + {/* How It Works */} +
+

+ How It Works +

+ +
+ {[ + "Upload your PDF or DOC file", + "AI generates quiz questions instantly", + "Start practicing and test your knowledge", + ].map((step, i) => ( +
+

+ Step {i + 1} +

+

{step}

+
+ ))} +
+
+ + {/* Why EduAid */} +
+

+ Why EduAid? +

+ +
+ {[ + "Saves hours of manual question creation", + "Encourages active recall learning", + "Instant feedback improves retention", + "Perfect for exams and revision", + ].map((point, i) => ( +
+

{point}

+
+ ))} +
+
+ + {/* Final CTA */} +
+ {/* βœ… Changed to /input */} + + Turn Your Notes Into a Quiz Now
@@ -103,7 +193,7 @@ const Home = () => { href="https://github.com/AOSSIE-Org/EduAid" target="_blank" rel="noopener noreferrer" - className="group block mt-10" + className="group block mt-16" >
GitHub Star @@ -119,10 +209,11 @@ const Home = () => {
+
); }; -export default Home; +export default Home; \ No newline at end of file diff --git a/eduaid_web/src/pages/Text_Input.jsx b/eduaid_web/src/pages/Text_Input.jsx index e341d331..ba211e07 100644 --- a/eduaid_web/src/pages/Text_Input.jsx +++ b/eduaid_web/src/pages/Text_Input.jsx @@ -1,69 +1,98 @@ import React, { useState, useRef } from "react"; import "../index.css"; -import logo_trans from "../assets/aossie_logo_transparent.png" +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 = () => { const navigate = useNavigate(); + const [text, setText] = useState(""); const [difficulty, setDifficulty] = useState("Easy Difficulty"); const [numQuestions, setNumQuestions] = useState(10); const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + const fileInputRef = useRef(null); - const [fileContent, setFileContent] = useState(""); const [docUrl, setDocUrl] = useState(""); const [isToggleOn, setIsToggleOn] = useState(0); const toggleSwitch = () => { - setIsToggleOn((isToggleOn + 1) % 2); + setIsToggleOn((prev) => (prev + 1) % 2); }; + // βœ… FIX 1: Clear stale error on success const handleFileUpload = async (event) => { const file = event.target.files[0]; - if (file) { - const formData = new FormData(); - formData.append("file", file); - - try { - const data = await apiClient.postFormData("/upload", formData); - setText(data.content || data.error); - } catch (error) { - console.error("Error uploading file:", error); - setText("Error uploading file"); + if (!file) return; + + const formData = new FormData(); + formData.append("file", file); + + try { + setErrorMessage(""); // clear old errors + + const data = await apiClient.postFormData("/upload", formData); + + if (data.error) { + setErrorMessage(data.error); + } else { + setErrorMessage(""); // ensure cleared + setText(data.content || ""); } + } catch (error) { + console.error("File upload failed:", error); + setErrorMessage("File upload failed."); } }; const handleClick = (event) => { - event.preventDefault(); // Prevent default behavior - event.stopPropagation(); // Stop event propagation - - // Open file input dialog - fileInputRef.current.click(); + event.preventDefault(); + event.stopPropagation(); + fileInputRef.current?.click(); // βœ… FIX 2: safe ref access }; const handleSaveToLocalStorage = async () => { setLoading(true); + setErrorMessage(""); + + try { + if (docUrl) { + const data = await apiClient.post("/get_content", { + document_url: docUrl, + }); - // Check if a Google Doc URL is provided - if (docUrl) { - try { - const data = await apiClient.post("/get_content", { document_url: docUrl }); setDocUrl(""); - setText(data || "Error in retrieving"); - } catch (error) { - console.error("Error:", error); - setText("Error retrieving Google Doc content"); - } finally { + + // βœ… FIX 3: Proper response handling + if (!data) { + setErrorMessage("Error retrieving content from Google Doc."); + } else if (data.error) { + setErrorMessage(data.error); + } else if (data.content) { + setText(data.content); + } else if (typeof data === "string") { + setText(data); + } else { + setErrorMessage( + "Unexpected response from Google Doc endpoint." + ); + } + + setLoading(false); + return; + } + + if (!text) { + setErrorMessage("Please enter text before proceeding."); setLoading(false); + return; } - } else if (text) { - // Proceed with existing functionality for local storage + localStorage.setItem("textContent", text); localStorage.setItem("difficulty", difficulty); localStorage.setItem("numQuestions", numQuestions); @@ -73,45 +102,38 @@ const Text_Input = () => { difficulty, localStorage.getItem("selectedQuestionType") ); + } catch (error) { + console.error("Error:", error); + setErrorMessage("Backend unavailable or request timed out."); + setLoading(false); } }; - const handleDifficultyChange = (e) => { - setDifficulty(e.target.value); - }; - - const incrementQuestions = () => { - setNumQuestions((prev) => prev + 1); - }; - - const decrementQuestions = () => { - 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"; - } + if (questionType === "get_shortq") return "get_shortq_hard"; + if (questionType === "get_mcq") return "get_mcq_hard"; } return questionType; }; const sendToBackend = async (data, difficulty, questionType) => { - const endpoint = getEndpoint(difficulty, questionType); try { + const endpoint = getEndpoint(difficulty, questionType); + const requestData = { input_text: data, max_questions: numQuestions, use_mediawiki: isToggleOn, }; - const responseData = await apiClient.post(`/${endpoint}`, requestData); + const responseData = await apiClient.post( + `/${endpoint}`, + requestData + ); + localStorage.setItem("qaPairs", JSON.stringify(responseData)); - // Save quiz details to local storage const quizDetails = { difficulty, numQuestions, @@ -121,15 +143,21 @@ const Text_Input = () => { let last5Quizzes = JSON.parse(localStorage.getItem("last5Quizzes")) || []; + last5Quizzes.push(quizDetails); if (last5Quizzes.length > 5) { - last5Quizzes.shift(); // Keep only the last 5 quizzes + last5Quizzes.shift(); } - localStorage.setItem("last5Quizzes", JSON.stringify(last5Quizzes)); + + localStorage.setItem( + "last5Quizzes", + JSON.stringify(last5Quizzes) + ); navigate("/output"); } catch (error) { - console.error("Error:", error); + console.error("Backend error:", error); + setErrorMessage("Backend unavailable or request timed out."); } finally { setLoading(false); } @@ -143,51 +171,55 @@ const Text_Input = () => {
)} -
- {/* Header */} +
logo
- Edu - Aid + + Edu + + + Aid +
- {/* Headline */} -
-
Enter the Content
-
- to Generate{" "} - Questionaries - stars + {errorMessage && ( +
+ {errorMessage}
-
+ )} - {/* Textarea */} -
- +