diff --git a/app.js b/app.js index ed3232e..f3b9eb8 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,7 @@ const express = require('express'); const fs = require('fs'); const path = require('path'); +const markdown = require('markdown-it')(); const { GoogleGenerativeAI } = require('@google/generative-ai'); const axios = require('axios'); const rateLimit = require('express-rate-limit'); @@ -8,7 +9,7 @@ const validator = require('validator'); require('dotenv').config(); const app = express(); -const PORT = 80; // Change the port if needed +const PORT = 80; // Initialize Google Generative AI const genAI = new GoogleGenerativeAI(process.env.API_KEY); @@ -19,25 +20,49 @@ app.use(express.static('public')); // Parse URL-encoded bodies (form submissions) app.use(express.urlencoded({ extended: true })); -app.use(express.json()); // To parse JSON in POST requests - -// Rate limiter to prevent too many requests -const limiter = rateLimit({ - windowMs: 1 * 60 * 1000, // 1 minute window - max: 10, // limit each IP to 10 requests per minute - message: "Too many requests, please try again later.", -}); // Serve homepage app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'views/index.html')); }); -// Serve the Algebra Calculator page -app.get('/algebra', (req, res) => { - res.sendFile(path.join(__dirname, 'views/algebra.html')); +app.get('/pacman', (req, res) => { + res.sendFile(path.join(__dirname, 'views/pacman.html')); }); + +// Serve homepage +app.get('/snake', (req, res) => { + res.sendFile(path.join(__dirname, 'views/snake.html')); +}); + +// In-memory cache for search results +const searchCache = new Map(); + +// Function to delete all files in the "articles" directory +const deleteArticlesFolder = () => { + const articlesDir = path.join(__dirname, 'public/articles'); + fs.readdir(articlesDir, (err, files) => { + if (err) { + console.error(`Error reading the articles directory: ${err}`); + return; + } + files.forEach((file) => { + const filePath = path.join(articlesDir, file); + fs.unlink(filePath, (err) => { + if (err) { + console.error(`Error deleting file ${file}: ${err}`); + } else { + console.log(`Deleted file: ${file}`); + } + }); + }); + }); +}; + +// Schedule the deleteArticlesFolder function to run every 24 hours +setInterval(deleteArticlesFolder, 24 * 60 * 60 * 1000); // 24 hours in milliseconds + // Function to sanitize scraped data const sanitizeScrapedData = (text) => { return text.replace(/[\n\r]/g, ' ').trim(); // Remove newlines, trim whitespace @@ -45,175 +70,226 @@ const sanitizeScrapedData = (text) => { // Function to scrape search results from SerpAPI const scrapeSerpApiSearch = async (query) => { - const apiKey = process.env.SERPAPI_API_KEY; // Add your SerpAPI key in the .env file + if (searchCache.has(query)) { + console.log("Serving from cache"); + return searchCache.get(query); + } + + const apiKey = process.env.SERPAPI_API_KEY; const formattedQuery = encodeURIComponent(query); const url = `https://serpapi.com/search.json?q=${formattedQuery}&api_key=${apiKey}`; try { const { data } = await axios.get(url); - // Check if the response contains organic_results if (!data.organic_results || !Array.isArray(data.organic_results)) { console.error("No organic results found in the response."); return []; } const links = data.organic_results.map(result => result.link).filter(link => link && link.startsWith('http')); + console.log("Collected URLs:", links); + + // Cache the result for 24 hours + searchCache.set(query, links); + setTimeout(() => searchCache.delete(query), 24 * 60 * 60 * 1000); - return links; // Return an array of links + return links; } catch (error) { console.error("Error scraping SerpAPI:", error); - return []; // Return an empty array in case of error + return []; } }; -// Handle Algebra calculation requests (AJAX) -app.post('/algebra-calculate', async (req, res) => { - let query = req.body.query; +// Function to scrape images from SerpAPI +const scrapeSerpApiImages = async (query) => { + if (searchCache.has(query)) { + console.log("Serving images from cache"); + return searchCache.get(query); + } - // Sanitize user input - query = validator.escape(query); + const apiKey = process.env.SERPAPI_API_KEY; + const url = `https://serpapi.com/search.json?engine=google_images&q=${query}&api_key=${apiKey}`; try { - // Modify the prompt for Algebra problem solving - const prompt = `Solve the following algebra problem step-by-step and simplify completely: ${query}`; + const { data } = await axios.get(url); + const images = data.images_results.slice(0, 10).map(img => ({ + thumbnail: img.thumbnail, + original: img.original + })); - // Generate AI content using Google Generative AI model - const result = await model.generateContent(prompt); + // Cache the result for 24 hours + searchCache.set(query, images); + setTimeout(() => searchCache.delete(query), 24 * 60 * 60 * 1000); - // Send the AI-generated content directly back to the client - res.send(`

Solution

${result.response.text()}

`); + return images; } catch (error) { - console.error("Error during algebra processing:", error.message); - res.status(500).send("An unexpected error occurred while solving the algebra problem."); + console.error("Error scraping SerpAPI images:", error); + return []; } -}); - -// In-memory cache for search results -const searchCache = new Map(); - -// Function to delete all files in the "articles" directory -const deleteArticlesFolder = () => { - const articlesDir = path.join(__dirname, 'public/articles'); - fs.readdir(articlesDir, (err, files) => { - if (err) { - console.error(`Error reading the articles directory: ${err}`); - return; - } - files.forEach((file) => { - const filePath = path.join(articlesDir, file); - fs.unlink(filePath, (err) => { - if (err) { - console.error(`Error deleting file ${file}: ${err}`); - } else { - console.log(`Deleted file: ${file}`); - } - }); - }); - }); }; -// Schedule the deleteArticlesFolder function to run every 24 hours -setInterval(deleteArticlesFolder, 24 * 60 * 60 * 1000); // 24 hours in milliseconds +// Rate limiter to prevent too many requests +const limiter = rateLimit({ + windowMs: 1 * 60 * 1000, // 1 minute window + max: 10, // limit each IP to 10 requests per minute + message: "Too many requests, please try again later.", +}); // Handle search form submissions app.post('/search', limiter, async (req, res) => { let query = req.body.query; - // Sanitize user input using validator query = validator.escape(query); const sanitizedQuery = query.toLowerCase().replace(/[^a-z0-9 ]/g, ' ').trim().replace(/\s+/g, '-'); const filePath = path.join(__dirname, 'public/articles', `${sanitizedQuery}.html`); - // Check if the article already exists if (fs.existsSync(filePath)) { return res.redirect(`/articles/${sanitizedQuery}`); } try { - // Scrape information from SerpAPI const lookupResult = await scrapeSerpApiSearch(query); + console.log("Scraped URLs:", lookupResult); - // Ensure lookupResult is an array if (!Array.isArray(lookupResult) || lookupResult.length === 0) { - // Provide an error response and include a message const errorMsg = "No results found from SerpAPI. Please try a different query."; const articleHtml = fs.readFileSync(path.join(__dirname, 'views/template.html'), 'utf8') .replace(/{{title}}/g, query) .replace(/{{content}}/g, "No content generated as there were no URLs.") - .replace(/{{urls}}/g, `
  • ${errorMsg}
  • `); // Display error message in place of URLs + .replace(/{{urls}}/g, `
  • ${errorMsg}
  • `); - fs.writeFileSync(filePath, articleHtml); // Save the HTML with error message + fs.writeFileSync(filePath, articleHtml); return res.redirect(`/articles/${sanitizedQuery}`); } - // Modify the prompt to instruct the AI const prompt = `You are Infintium. You have two purposes. If the user prompt is a math problem, solve it until it is COMPLETELY simplified. If it is a question, answer it with your own knowledge. If it is an item, such as a toaster, song, or anything that is a statement, act like Wikipedia and provide as much information as possible. USER PROMPT: ${query}`; - // Generate AI content using Google Generative AI model const result = await model.generateContent(prompt); - const markdownContent = result.response.text(); // Use AI response as the content + const markdownContent = markdown.render(result.response.text()); - // Load the HTML template let articleHtml = fs.readFileSync(path.join(__dirname, 'views/template.html'), 'utf8'); - - // Replace placeholders with the search query and AI content articleHtml = articleHtml.replace(/{{title}}/g, query); articleHtml = articleHtml.replace(/{{content}}/g, markdownContent); - - // Create a list of URLs for the article + const urlList = lookupResult.map(url => `
  • ${url}
  • `).join(''); + console.log("Generated URL List:", urlList); articleHtml = articleHtml.replace(/{{urls}}/g, urlList); - // Save the generated HTML file - fs.writeFileSync(filePath, articleHtml); + try { + const images = await scrapeSerpApiImages(query); + const imageGallery = images.length > 0 + ? images.map(img => `${query} image`).join('') + : "No images available"; - // Redirect to the new article page - res.redirect(`/articles/${sanitizedQuery}`); + articleHtml = articleHtml.replace(/{{imageGallery}}/g, imageGallery); + + fs.writeFileSync(filePath, articleHtml); + res.redirect(`/articles/${sanitizedQuery}`); + } catch (imageError) { + console.error("Error generating the image gallery:", imageError); + res.status(500).send("Error generating the image gallery."); + } } catch (error) { console.error("Error during the search process:", error.message); res.status(500).send("An unexpected error occurred: " + error.message); } }); -// Serve the generated article pages -app.get('/articles/:article', (req, res) => { - const article = req.params.article; - const filePath = path.join(__dirname, 'public/articles', `${article}.html`); - - // Check if the file exists - if (fs.existsSync(filePath)) { - res.sendFile(filePath); - } else { - res.status(404).send("Article not found."); - } -}); - // Serve suggestions for the autocomplete feature app.get('/suggest', (req, res) => { - const query = req.query.q.toLowerCase().replace(/-/g, ' '); // Treat dashes as spaces + const query = req.query.q.toLowerCase().replace(/-/g, ' '); const articlesDir = path.join(__dirname, 'public/articles'); - // Read all files in the ARTICLES directory fs.readdir(articlesDir, (err, files) => { if (err) { return res.status(500).send([]); } - // Filter files that match the query const suggestions = files .filter(file => { const filename = file.replace('.html', '').toLowerCase(); - return filename.includes(query); // Check against filename + return filename.includes(query); }) - .map(file => file.replace('.html', '')); // Remove .html extension + .map(file => file.replace('.html', '')); res.send(suggestions); }); }); -// Start the server +// Serve the generated article pages or create them if they don't exist +// Serve the generated article pages or create them if they don't exist +app.get('/articles/:article', async (req, res) => { + const article = req.params.article; + const filePath = path.join(__dirname, 'public/articles', `${article}.html`); + + // Check if the file exists + if (fs.existsSync(filePath)) { + return res.sendFile(filePath); + } + + try { + // Convert the article name back to a readable format + const query = article.replace(/-/g, ' '); + + // Scrape information from SerpAPI + const lookupResult = await scrapeSerpApiSearch(query); + + // Check if any results were found + if (!Array.isArray(lookupResult) || lookupResult.length === 0) { + const errorMsg = "No content found for this article."; + const articleHtml = fs.readFileSync(path.join(__dirname, 'views/template.html'), 'utf8') + .replace(/{{title}}/g, query) + .replace(/{{content}}/g, "No content generated as there were no URLs.") + .replace(/{{urls}}/g, `
  • ${errorMsg}
  • `); + fs.writeFileSync(filePath, articleHtml); + return res.sendFile(filePath); + } + + // Generate a prompt for the AI content generation + const prompt = `You are Infintium. You have two purposes. If the user prompt is a math problem, solve it until it is COMPLETELY simplified. If it is a question, answer it with your own knowledge. If it is an item, such as a toaster, song, or anything that is a statement, act like Wikipedia and provide as much information as possible. USER PROMPT: ${query}`; + + // Generate AI content using the prompt + const result = await model.generateContent(prompt); + const markdownContent = markdown.render(result.response.text()); + + // Load the HTML template + let articleHtml = fs.readFileSync(path.join(__dirname, 'views/template.html'), 'utf8'); + + // Replace placeholders with the search query and AI content + articleHtml = articleHtml.replace(/{{title}}/g, query); + articleHtml = articleHtml.replace(/{{content}}/g, markdownContent); + + // Create a list of URLs for the article + const urlList = lookupResult.map(url => `
  • ${url}
  • `).join(''); + articleHtml = articleHtml.replace(/{{urls}}/g, urlList); + + // Generate the image gallery in the article HTML + try { + const images = await scrapeSerpApiImages(query); + + // Check if images were fetched successfully + const imageGallery = images.length > 0 + ? images.map(img => `${query} image`).join('') + : '

    No images available

    '; + + articleHtml = articleHtml.replace(/{{imageGallery}}/g, imageGallery); + + // Save the generated HTML file + fs.writeFileSync(filePath, articleHtml); + res.sendFile(filePath); + } catch (imageError) { + console.error("Error generating the image gallery:", imageError); + res.status(500).send("Error generating the image gallery."); + } + } catch (error) { + console.error("Error generating the article:", error); + res.status(500).send("An unexpected error occurred: " + error.message); + } +}); + + app.listen(PORT, () => { - console.log(`Server is running on http://localhost:${PORT}`); + console.log(`Server is running on port ${PORT}`); }); diff --git a/public/css/styles.css b/public/css/styles.css index 814205d..c21979a 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,127 +1,88 @@ -/* General styles */ +/* Existing styles */ body { font-family: Arial, sans-serif; text-align: center; background-color: #f8f9fa; - margin: 0; - padding: 0; } .container { - margin-top: 50px; + margin-top: 100px; display: flex; flex-direction: column; align-items: center; - padding: 20px; } -/* Styling for the math logo */ -.math-logo { - width: 200px; - height: auto; - margin-bottom: 30px; -} - -/* Input area styles */ -textarea { - width: 80%; - max-width: 600px; - padding: 10px; - font-size: 16px; - border: 1px solid #ccc; - border-radius: 5px; +.logo { + width: 350px; margin-bottom: 20px; } -/* Calculator pad styles */ -.calculator-pad { - display: flex; - flex-wrap: wrap; - justify-content: center; - margin-bottom: 20px; -} - -.calculator-pad button { - width: 50px; - height: 50px; - margin: 5px; - font-size: 18px; - background-color: #007bff; - color: white; - border: none; - border-radius: 5px; - cursor: pointer; +.search-container { display: flex; - justify-content: center; align-items: center; + position: relative; } -.calculator-pad button:hover { - background-color: #0056b3; +input[type="text"] { + width: 60%; + max-width: 600px; + padding: 10px; + font-size: 16px; + border: 1px solid #ccc; + border-radius: 5px; + margin-right: 10px; } -/* Submit button styles */ -button[type="submit"] { +button { padding: 10px 20px; font-size: 16px; - background-color: #28a745; + background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; - margin-bottom: 20px; -} - -button[type="submit"]:hover { - background-color: #218838; } -/* Loading island */ -.loading-island { - display: flex; - flex-direction: column; - align-items: center; - margin-bottom: 20px; +button:hover { + background-color: #0056b3; } -.loading-spinner { - border: 4px solid rgba(0, 0, 0, 0.1); - border-left-color: #007bff; - border-radius: 50%; - width: 30px; - height: 30px; - animation: spin 1s linear infinite; +.suggestions-list { + list-style-type: none; + padding: 0; + margin: 5px 0 0 0; + border: 1px solid #ccc; + max-width: 400px; + background-color: white; + position: relative; + z-index: 1000; + width: 60%; + display: none; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); } -@keyframes spin { - to { transform: rotate(360deg); } +.suggestion-item { + padding: 10px; + cursor: pointer; } -/* Result container */ -.result-container { +.suggestion-item:hover { background-color: #f1f1f1; - border-radius: 5px; - padding: 20px; - max-width: 600px; - width: 80%; - margin-bottom: 20px; - font-size: 18px; } -/* Footer text */ -p { - font-size: 14px; - color: #666; - margin-bottom: 20px; +/* Show suggestions when there are items */ +#searchInput:focus + .suggestions-list, +.suggestions-list:has(.suggestion-item) { + display: block; } -/* Dropdown styles */ +/* New styles for the Math Symbols dropdown */ #mathSymbolsDropdown { - padding: 10px; + padding: 5px; font-size: 15px; border: 1px solid #ccc; border-radius: 5px; - margin: 10px 0; + margin-left: 5px; background-color: white; cursor: pointer; } @@ -136,23 +97,69 @@ p { font-size: 15px; } -/* Responsive design */ -@media (max-width: 768px) { - textarea { - width: 90%; - } +/* New styles for image gallery */ +.image-gallery { + display: flex; + justify-content: center; + flex-wrap: wrap; + margin: 20px 0; +} - .calculator-pad button { - width: 40px; - height: 40px; - font-size: 16px; - } +.image-gallery img { + width: 200px; + height: 150px; + margin: 10px; + border-radius: 8px; + transition: transform 0.3s ease, box-shadow 0.3s ease; + cursor: pointer; +} + +.image-gallery img:hover { + transform: scale(1.05); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.image-gallery img:active { + transform: scale(1.1); +} + +/* Enlarged image modal */ +#imageModal { + display: none; + position: fixed; + z-index: 1001; + padding-top: 60px; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.8); +} - button[type="submit"] { - width: 80%; - } +#imageModal img { + display: block; + margin: auto; + max-width: 90%; + max-height: 80%; + border-radius: 8px; +} + +#imageModal .close { + position: absolute; + top: 15px; + right: 25px; + color: white; + font-size: 35px; + font-weight: bold; + cursor: pointer; +} + +#imageModal .close:hover { + color: #ccc; +} - .result-container { - width: 90%; - } +/* Image download on double-click */ +.image-gallery img:active { + cursor: pointer; } diff --git a/public/mathLogo.png b/public/mathLogo.png deleted file mode 100644 index 7084d43..0000000 Binary files a/public/mathLogo.png and /dev/null differ diff --git a/views/algebra.html b/views/algebra.html deleted file mode 100644 index 87c826c..0000000 --- a/views/algebra.html +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - Algebra Calculator - Infintium - - - - - - - - -
    - - - -
    - - - -
    - - - - - - - - - - - - - - - - - - -
    - - -
    - - -
    -
    -

    Loading, please wait...

    -
    - - - - -

    Infinite Algebra Calculations with Infintium.

    -
    - - - - diff --git a/views/index.html b/views/index.html index e398a0c..cc2e697 100644 --- a/views/index.html +++ b/views/index.html @@ -19,13 +19,41 @@ - tions" class="suggestions-list"> + + + + +

    Infinite Searches, Infinite Learning.

    -

    Ver. 0.91.7

    +

    Ver. 0.99.8

    GitHub About -

    P.S. Also try Infintium Algebra for quick solving!

    +

    P.S. Also try entering algebra problems for quick solving!