# Project export: Proof Buster

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: An AI-powered educational tool that helps students master proof writing. It checks the validity of theorems, scores user proofs, and offers targeted feedback to guide students to the correct answers.
- Devpost: https://devpost.com/software/proof-buster
- GitHub: https://github.com/eliaswuberkeley/proofbuster
- Team: 1 GitHub contributor(s) — eliaswuberkeley (3 commits)

## Devpost submission (written by the team)

### Inspiration

As you may or may know as students or professionals in STEM, the jump in math education to primarily problem solving based learning to proof writing is a difficult switch for many students and professors alike. Based on our experiences taking and grading proof-focused courses at our universities and the lack of targeted practice available for learning this essential mathematical skill, we created Proof Buster. Description Proof Buster is an educational tool which provides students with an assisted gamified approach to proof writing inspired by similar platforms for programming and language learning. Through Proof Buster, students select a conjecture to prove and learn by doing. Unlike traditional tutors and teaching assistants who must carefully read through long paragraphs to identify errors, our AI tutoring agent which leverages Gemini’s speed at identifying common errors to provide accurate and immediate feedback to the user. Through Gemini’s ability to add additional model tuning and directly call functions within our project, we are able to leverage AI’s data processing strength to both save hours of labor for instructional teams and provide a much more intuitive and engaging learning experience for students. Development Proof Buster uses a standard frontend and a node.js based server for its backend. The backend utilizes Google's Gemini API to train the model on conjectures and ready-made proofs to fine tune it to our project needs and avoid errors with its assessment of students' proofs as well as Gemini's function calling to get non-String values for use throughout our project. Despite some unfamiliarity with JavaScript and a team size of just 2, we were able to create a fully functional website that accomplished all the basic features which we sought out to perform. Looking Forward Proof Buster is not just a throwaway project; it was specifically designed to help us with proof-based math courses and their education. With small tweaks and additions such as a web-based server, user login, conjecture submission, and regularly added conjectures, we believe Proof Buster will be instrumental in helping teach students in classes we grade or take ourselves.

## README (from the GitHub repository)

# proofbuster

## Detected evidence (automated analysis)

Indexed codebase: 6 recognized source files, 19 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- Google Gemini (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (9 of 9)

```
LICENSE
package.json
public/index.html
public/info.html
public/problems.json
public/script.js
public/style.css
README.md
server.js
```

### Dependencies

- package.json: @google/generative-ai@^0.21.0, body-parser@^1.19.0, dotenv@^16.4.5, express@^4.17.1, showdown@^2.1.0

### Recent commits (newest first)

- Rename index.html to public/index.html
- Rename public/index.html to index.html
- Rename problems.json to public/problems.json
- Rename style.css to public/style.css
- Rename script.js to public/script.js
- Rename info.png to public/assets/info.png
- Rename logo.png to public/assets/logo.png
- Rename home.png to public/assets/home.png
- Rename info.html to public/info.html
- Rename index.html to public/index.html
- Add files via upload
- Add files via upload
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "text-ai-generator",
  "version": "1.0.0",
  "description": "A simple app that helps guide math proof writing",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "@google/generative-ai": "^0.21.0",
    "body-parser": "^1.19.0",
    "dotenv": "^16.4.5",
    "express": "^4.17.1",
    "showdown": "^2.1.0"
  },
  "author": "",
  "license": "ISC"
}

```

### server.js

```javascript
const express = require("express");
const bodyParser = require("body-parser");
const { GoogleGenerativeAI } = require("@google/generative-ai");
require("dotenv").config();

// Create the Express app
const app = express();
const port = 3002;

// Middleware to parse JSON
app.use(bodyParser.json());

// Serve static files from the public directory
app.use(express.static("public"));

// Initialize Google Generative AI
const genAI = new GoogleGenerativeAI(process.env.API_KEY); // Replace with your actual API key
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });

app.post("/give-feedback", async (req, res) => {
  const { conjecture, proof } = req.body;
  const prompt = `Conjecture: ${conjecture}\nProof: ${proof}\nProvide some feedback and tips to help guide the user to write a valid proof without giving the full solution.`;

  try {
    const result = await model.generateContent(prompt);
    var showdown = require("showdown"),
      converter = new showdown.Converter(),
      html = converter.makeHtml(result.response.text());
    res.json({ feedback: html });
  } catch (error) {
    console.error("Error generating feedback:", error);
    res.status(500).send("Failed to generate feedback.");
  }
});

// Endpoint to check proof with the AI model
app.post("/check-proof", async (req, res) => {
  const { conjecture, proof } = req.body;
  const prompt = `Conjecture: ${conjecture}\nProof: ${proof}\nDoes this proof solve the conjecture? Do not provide feedback, respond with \"YES\" or \"NO\" only. If there are only minor issues which do not impact the veracity and meaning of the proof, respond with "YES".`;

  try {
    const result = await model.generateContent(prompt);
    res.json({ generatedText: result.response.text() });
  } catch (error) {
    console.error("Error checking proof with AI:", error);
    res.status(500).send("Failed to check proof.");
  }
});
app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

app.post("/check-custom", async (req, res) => {
  const { conjecture } = req.body;
  const prompt = `Conjecture: ${conjecture}\nIs this conjecture a true conjecture? Do not provide feedback, respond with \"YES\" or \"NO\" only. If there are only a minor issue which does not impact the veracity or meaning of the conjecture, respond with "YES".`;

  try {
    const result = await model.generateContent(prompt);
    res.json({ generatedText: result.response.text() });
  } catch (error) {
    console.error("Error checking proof with AI:", error);
    res.status(500).send("Failed to check proof.");
  }
});

```

### public/info.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Proof Buster</title>
    <link rel="stylesheet" href="style.css" />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css?family=Quicksand"
    />
  </head>
  <body>
    <div class="menuBar">
      <a href="index.html"><button id="homeButton"><img src="./assets/home.png" id="homeButtonImage"></img></button></a>
      <img src="./assets/logo.png" id="logoImage"></img>
    </div>
    <h1 id="title">Proof Buster</h1>
    <div id="infobox">AI-driven proof-writing platform for math education. Designed by Elias Wu and Jayden Entenmann, CalHacks 2024.</div>
    </iframe>
  </body>
</html>

```

### public/style.css

```css
/* General page styling */
body {
  font-family: "Quicksand", sans-serif;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  height: 200vh;
  margin: 0;
  background-color: #252527;
  padding: 10px;
}

h1 {
  color: #ff7b00;
  margin-bottom: 20px;
  font-family: "Quicksand";
}

.menuBar {
  justify-content: space-between;
  display: flex;
  width: 100%;
  height: 1vh;
}

#infoButton {
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 10px;
  background-color: #252527;
}

#homeButton {
  display: flex;
  justify-content: space-around;
  align-items: center;
  padding: 10px;
  background-color: #252527;
}

#infoButtonImage {
  display: flex;
  width: 32px;
  height: 32px;
}

#logoImage {
  display: flex;
  width: 64px;
  height: 64px;
}

#homeButtonImage {
  display: flex;
  width: 32px;
  height: 32px;
}

#title {
  padding: 12px;
  font-size: 52px;
}

.top-row {
  display: flex;
  justify-content: space-around;
  align-items: center;
  width: 100%;
  max-width: 800px;
  padding: 20px;
}

button {
  padding: 12px 20px;
  background-color: #ff7b00;
  color: white;
  border: none;
  border-radius: 5px;
  font-size: 16px;
  font-family: inherit;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

#filterDropdown {
  display: flex;
  flex-direction: row;
  padding: 15px;
  margin-bottom: 10px;
  background-color: #000000;
  border: 1px solid #ccc;
  border-radius: 5px;
  width: 100%;
  max-width: 600px;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
}

#filterDropdown h3 {
  margin-top: 0px;
  margin-bottom: 10px;
  font-size: 18px;
  color: #ffffff;
}

#filterDropdown label {
  font-size: 16px;
  color: #ffffff;
}

.filter-container {
  display: flex;
  justify-content: space-between;
}

#customDropdown {
  display: flex;
  flex-direction: row;
  padding: 15px;
  margin-bottom: 10px;
  background-color: #000000;
  border: 1px solid #ccc;
  border-radius: 5px;
  width: 100%;
  max-width: 600px;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
}

.custom-container {
  display: flex;
  justify-content: space-between;
}

#conjectureDropdown {
  width: 50%;
  max-width: 300px;
  padding: 10px;
  font-size: 16px;
  font-family: inherit;
  color: white;
  border: 2px solid #ff7b00;
  background-color: #252527;
  border-radius: 5px;
}

/* Top button styling */
#topGenerateConjecture {
  background-color: #ff7b00;
  color: white;
  padding: 2px 6px;
  font-size: 32px;
  border: none;
  border-radius: 2px;
  cursor: pointer;
  margin-bottom: 20px;
}

#topGenerateConjecture:hover {
  background-color: #b86314;
}

/* Individual filter section (Difficulty and Tags) */
.filter-section {
  width: 45%;
}

/* Bottom button row styling */
.button-row {
  display: flex;
  gap: 20px;
}

button:hover {
  background-color: #b86314;
}

/* Styling for the output section */
#output {
  font-size: 24px;
  margin-bottom: 10px;
  font-weight: bold;
  color: #ffffff;
  text-align: center;
  font-family: "Quicksand";
  max-width: 100%;
}

.proof-container {
    display: flex;
    justify-content: space-around;
    width: 95%;
    height: 60vh;
}

.proof-container-child {
  display: flex;
  flex-direction: column;
  width: 45%;
  height: 50vh;
}

#textbox {
  width: 100%;
  height: 50vh;
  padding: 0px;
  font-size: 16px;
  font-family: inherit;
  margin-bottom: 0px;
  border: 2px solid #ff7b00;
  color: #ffffff;
  background-color: #19191a;
  border-radius: 5px;
  resize: none;
}

#customTextbox {
  font-size: 16px;
  font-family: inherit;
  margin-bottom: 0px;
  border: 2px solid #ff7b00;
  color: #ffffff;
  background-color: #19191a;
  border-radius: 5px;
}

#outputbox {
  scoll: true;
  width: 100%;
  height: 50vh;
  padding: 10px;
  font-size: 16px;
  margin-bottom: 20px;
  border: 2px solid #ff7b00;
  color: #ffffff;
  background-color: #19191a;
  border-radius: 5px;
  resize: none;
  overflow: auto;
  white-space: normal;
}
  
/* Specific button colors */
#giveFeedback {
  background-color: #ff7b00;
}

#giveFeedback:hover {
  background-color: #b86314;
}

#checkProof {
  background-color: #ff7b00;
}

#checkProof:hover {
  background-color: #b86314;
}

#infobox {
  color: #ffffff;
}

```

### public/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Proof Buster</title>
    <link rel="stylesheet" href="style.css" />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css?family=Quicksand"
    />
  </head>
  <body>
    <div class="menuBar">
      <a href="info.html"><button id="infoButton"><img src="./assets/info.png" id="infoButtonImage"></img></button></a>
      <img src="./assets/logo.png" id="logoImage"></img>
    </div>
    <h1 id="title">Proof Buster</h1>
    <div class="top-row">
      <!-- Filter button on the left -->
      <button id="filterButton">Filter</button>

      <!-- Dropdown menu for conjectures on the right -->
      <select id="conjectureDropdown">
        <option disabled selected>Select a conjecture...</option>
      </select>

      <!-- Button to generate a random conjecture -->
      <button id="generateConjecture">Random</button>
      <button id="customButton">Custom</button>
    </div>

    <!-- Dropdown for filters (hidden initially) -->
    <div id="filterDropdown" style="display: none">
      <div class="filter-container">
        <div class="filter-section">
          <h3>Select Difficulty:</h3>
          <label
            ><input type="checkbox" name="difficulty" value="easy" />
            Easy</label
          ><br />
          <label
            ><input type="checkbox" name="difficulty" value="medium" />
            Medium</label
          ><br />
          <label
            ><input type="checkbox" name="difficulty" value="hard" />
            Hard</label
          ><br />
        </div>
        <div class="filter-section">
          <h3>Select Tags:</h3>
          <label
            ><input type="checkbox" name="tags" value="direct" />Direct</label
          ><br />
          <label
            ><input
              type="checkbox"
              name="tags"
              value="contrapositive"
            />Contrapositive</label
          ><br />
          <label
            ><input
              type="checkbox"
              name="tags"
              value="contradiction"
            />Contradiction</label
          ><br />
          <label><input type="checkbox" name="tags" value="cases" />Cases</label
          ><br /><label
            ><input
              type="checkbox"
              name="tags"
              value="induction"
            />Induction</label
          ><br /><label
            ><input
              type="checkbox"
              name="tags"
              value="irrationals"
            />Irrationals</label
          ><br />
          <label
            ><input type="checkbox" name="tags" value="integers" />
            Integers</label
          ><br />
          <label
            ><input
              type="checkbox"
              name="tags"
              value="rationals"
            />Rationals</label
          ><br />
          <label
            ><input
              type="checkbox"
              name="tags"
              value="irrationals"
            />Irrationals</label
          ><br /><label
            ><input type="checkbox" name="tags" value="graphs" />Graphs</label
          ><br /><label
            ><input type="checkbox" name="tags" value="sets" />Sets</label
          ><br />
          <label
            ><input type="checkbox" name="tags" value="classic" />Classic</label
          ><br />
        </div>
      </div>
    </div>

    <div id="customDropdown" style="display: none">
      <div class="custom-container">
        <div class="custom-section">
          <textarea id="customTextbox" placeholder="Type your statement here..."></textarea>
        </div>
        <div class="custom-section">
          <button id="generateCustom">Generate</button>
        </div>
      </div>
    </div>
    <!-- Textbox for writing the proof -->
    <div class="proof-container">
      <div class="proof-container-child">
        <!-- Display the conjecture here -->
        <div id="output"></div>
        <textarea id="textbox" placeholder="Type your proof here..."></textarea>
      </div>
      <div class="proof-container-child">
        <div id="outputbox" scrolling="yes"></div>
        <!-- Buttons aligned side by side -->
        <div class="button-row">
          <!-- Button for giving feedback (guidance) on the proof -->
          <button id="giveFeedback" style="display: none">
            Give Feedback on Proof
          </button>

          <!-- Button for checking the proof (AI full solution) -->
          <button id="checkProof" style="display: none">Check Proof</button>
        </div>
      </div>
    </div>
    <script src="script.js"></script>
  </body>
</html>

```

### public/script.js

```javascript
// Predefined list of conjectures
let conjectures = [];
let filteredConjectures = [];
let selectedConjecture = "";

// Load conjectures from the JSON file
fetch("problems.json")
  .then((response) => response.json())
  .then((data) => {
    conjectures = data;
    populateDropdown(conjectures, true);
  })
  .catch((error) => console.error("Error loading JSON:", error));

// Populate the dropdown with all conjectures
function populateDropdown(conjecturesToShow, deselect = false) {
  const dropdown = document.getElementById("conjectureDropdown");
  dropdown.innerHTML = ""; // Clear existing options

  if (deselect) {
    const placeholderOption = document.createElement("option");
    placeholderOption.textContent = "Choose a conjecture...";
    placeholderOption.value = ""; // Empty value for placeholder
    placeholderOption.disabled = true; // Disable selection of the placeholder
    placeholderOption.selected = true; // Set as default selected option
    dropdown.appendChild(placeholderOption);
  }
  // Populate with filtered or all conjectures
  conjecturesToShow.forEach((conjectureObj, index) => {
    const option = document.createElement("option");
    option.value = index;
    // Set the text content as: "index. conjecture text"
    option.textContent = `${index + 1}. ${conjectureObj.conjecture}`;
    dropdown.appendChild(option);
  });
}

// Apply filters and return filtered conjectures
function applyFilters() {
  const selectedDifficulties = Array.from(
    document.querySelectorAll('input[name="difficulty"]:checked'),
  ).map((input) => input.value);
  const selectedTags = Array.from(
    document.querySelectorAll('input[name="tags"]:checked'),
  ).map((input) => input.value);

  // Filter the conjectures
  filteredConjectures = conjectures.filter((conjecture) => {
    const matchesDifficulty =
      selectedDifficulties.length === 0 ||
      selectedDifficulties.includes(conjecture.difficulty);
    const matchesTags =
      selectedTags.length === 0 ||
      selectedTags.every((tag) => conjecture.tags.includes(tag));
    return matchesDifficulty && matchesTags;
  });

  // Repopulate the dropdown with the filtered results
  populateDropdown(filteredConjectures, true);
}

document.getElementById("filterButton").addEventListener("click", function () {
  const filterDropdown = document.getElementById("filterDropdown");
  filterDropdown.style.display =
    filterDropdown.style.display === "none" ? "block" : "none";

  // Apply the filters and repopulate the dropdown
  applyFilters();
});

document.getElementById("customButton").addEventListener("click", function () {
  const filterDropdown = document.getElementById("customDropdown");
  filterDropdown.style.display =
    filterDropdown.style.display === "none" ? "block" : "none";

  // Apply the filters and repopulate the dropdown
  applyFilters();
});

// Handle dropdown selection
document
  .getElementById("conjectureDropdown")
  .addEventListener("change", function () {
    const selectedIndex = this.value;
    selectedConjecture = conjectures[selectedIndex];
    document.getElementById("output").textContent =
      `${parseInt(selectedIndex) + 1}. ${selectedConjecture.conjecture}`;
    showProofButtons();
  });

// Show the feedback and check proof buttons
function showProofButtons() {
  document.getElementById("giveFeedback").style.display = "inline";
  document.getElementById("checkProof").style.display = "inline";
}

function showProofButtons() {
  document.getElementById("giveFeedback").style.display = "inline";
  document.getElementById("checkProof").style.display = "inline";
}

// Generate a random conjecture based on filters
document
  .getElementById("generateConjecture")
  .addEventListener("click", function () {
    applyFilters(); // Ensure filters are applied
    if (filteredConjectures.length > 0) {
      const randomIndex = Math.floor(
        Math.random() * filteredConjectures.length,
      );
      selectedConjecture = filteredConjectures[randomIndex];
      document.getElementById("output").textContent =
        `${parseInt(randomIndex) + 1}. ${selectedConjecture.conjecture}`;
      showProofButtons();
    } else {
      document.getElementById("output").textContent =
        "No conjectures match the selected filters.";
    }
  });

document
  .getElementById("giveFeedback")
  .addEventListener("click", async function () {
    document.getElementById("outputbox").textContent = "...";
    const proof = document.getElementById("textbox").value;

    // Send the conjecture and proof to the server for feedback (not full solution)
    const response = await fetch("/give-feedback", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        conjecture: selectedConjecture["conjecture"],
        proof: proof,
      }),
    });
    const data = await response.json();
    document.getElementById("outputbox").innerHTML = data.feedback;
  });

// Check the proof with full AI solution
document
  .getElementById("checkProof")
  .addEventListener("click", async function () {
    document.getElementById("outputbox").textContent = "...";
    const proof = document.getElementById("textbox").value;

    // Send both the conjecture and proof to the server for AI full solution
    const response = await fetch("/check-proof", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ conjecture: selectedConjecture, proof: proof }),
    });
    const data = await response.json();
    if (data.generatedText.trim() == "YES") {
      document.getElementById("outputbox").textContent = `✅ Correct!`;
    } else {
      document.getElementById("outputbox").textContent = `❌ Incorrect!`;
    }
  });

document
  .getElementById("generateCustom")
  .addEventListener("click", async function () {
    document.getElementById("outputbox").textContent = "...";
    const conjecture = document.getElementById("customTextbox").value;

    // Send the
[truncated — 808 more characters]
```