Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ee5e56f
Add mobile camera and webcam capturing
cunkin375 Sep 27, 2025
30b3139
Merge branch 'main' of https://github.com/KaziAmin110/Revision
cunkin375 Sep 27, 2025
b0cadc0
Added Upload Page changes
KaziAmin110 Sep 27, 2025
27ba5c5
Added TouchScreen functionality to Whiteboard page
KaziAmin110 Sep 27, 2025
dcbe6bc
Fixed Upload Page Error
KaziAmin110 Sep 27, 2025
ba6ac85
Removed Route.ts file
KaziAmin110 Sep 27, 2025
96b5eb3
Prevented Whiteboard Scrolling
KaziAmin110 Sep 27, 2025
e44394b
Removed Unnecessary code in upload page.tsx file
KaziAmin110 Sep 27, 2025
8d2c9b8
Fixed Page.tsx formatiting issue
KaziAmin110 Sep 27, 2025
9fd067b
Added Supabase File Upload Implementation
KaziAmin110 Sep 27, 2025
1ed6260
Remove Build Error Causing Route.ts Upload File
KaziAmin110 Sep 27, 2025
56c99af
Added Upload Page Text Changes
KaziAmin110 Sep 27, 2025
6e1fd58
Merge pull request #2 from KaziAmin110/AlphaKnight1701-A-patch-1
KaziAmin110 Sep 27, 2025
edcdc33
Pushed Latest Backend Changes
KaziAmin110 Sep 27, 2025
193339f
Latest Backend Main Function
KaziAmin110 Sep 27, 2025
fa7743f
Added Base Working version OCR with faults
KaziAmin110 Sep 27, 2025
31c6c6d
Added Whiteboard Analysis base http polling
KaziAmin110 Sep 27, 2025
7a08d19
Add math notation rendering for given questions.
cunkin375 Sep 27, 2025
7e525d0
Add dependencies
cunkin375 Sep 27, 2025
b7c916b
Added Base Working WhtieBoard solving a problem
KaziAmin110 Sep 27, 2025
dcb80ec
Update whiteboard to handle question math notation
cunkin375 Sep 27, 2025
a3f1df8
Merge branch 'main' of https://github.com/KaziAmin110/Revision
cunkin375 Sep 27, 2025
0f1ce83
Removed Error Main
KaziAmin110 Sep 27, 2025
8c8e19c
Remove questionsData.ts
cunkin375 Sep 27, 2025
d8a0ab4
Added Whiteboard Changes
KaziAmin110 Sep 27, 2025
d975d7d
Add gunicorn for product deployment
KaziAmin110 Sep 27, 2025
cb026a0
Added White Board Backend Hosting Logic
KaziAmin110 Sep 27, 2025
cbba350
Update sidebar UI; Add Question component.
cunkin375 Sep 27, 2025
2175644
Update Suggestions formatting
cunkin375 Sep 27, 2025
7cb9aa5
Merge branch 'main' into frontend
KaziAmin110 Sep 27, 2025
cc3a488
Update update page
cunkin375 Sep 27, 2025
039a422
Add functional PDF reading
cunkin375 Sep 28, 2025
c44b290
Added Question Extraction from Pdf Functioanality
KaziAmin110 Sep 28, 2025
e618d9e
Fixed Parsing Issue
KaziAmin110 Sep 28, 2025
a076d6e
Refined Upload Page UI
KaziAmin110 Sep 28, 2025
12c9b64
Added Whiteboard with dynamic data implementation
KaziAmin110 Sep 28, 2025
0f8ecf6
Added Latest White Board Changes
KaziAmin110 Sep 28, 2025
1100ffc
Update whiteboard page; Improve Gemini prompt
cunkin375 Sep 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
env
/keys
259 changes: 259 additions & 0 deletions backend/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
from dotenv import load_dotenv
import base64
import google.generativeai as genai
from google.cloud import vision
import os
import json
import uuid # Used to generate unique filenames
from supabase import create_client, Client # Import Supabase client
from werkzeug.utils import secure_filename
# Removed 'import magic' as it's no longer needed and caused the error.

# --- Configuration ---
load_dotenv()

app = Flask(__name__)
CORS(app)

# A good practice to define allowed extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'pdf'}

def allowed_file(filename):
"""Checks if a file's extension is allowed."""
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

# --- API Client Initialization ---

# Google API Clients (Vision and Gemini)
gcp_keyfile = os.getenv("CGP_KEYFILE")
if gcp_keyfile and os.path.exists(gcp_keyfile):
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = gcp_keyfile
else:
print("WARNING: CGP_KEYFILE environment variable not set or file not found. Vision API may not work.")

gemini_api_key = os.getenv("GEMINI_API_KEY")
if gemini_api_key:
genai.configure(api_key=gemini_api_key)
else:
print("FATAL: GEMINI_API_KEY environment variable not set. Application cannot start.")
exit()

try:
vision_client = vision.ImageAnnotatorClient()
gemini_model = genai.GenerativeModel("gemini-2.5-pro") # Using gemini-2.5-pro as flash-lite is not a standard model name
except Exception as e:
print(f"FATAL: Could not initialize Google API clients: {e}")
exit()

# Supabase Client Initialization
try:
supabase_url = os.environ.get("SUPABASE_URL")
supabase_key = os.environ.get("SUPABASE_KEY")
if not supabase_url or not supabase_key:
raise ValueError("Supabase URL or Key not found in environment variables.")
supabase: Client = create_client(supabase_url, supabase_key)
print("Successfully connected to Supabase.")
except Exception as e:
print(f"FATAL: Could not initialize Supabase client: {e}")
exit()


# --- Core Functions ---

def image_ocr(image_bytes):
"""Performs OCR on the given image bytes using Google Cloud Vision."""
print("OCR function called.")
image = vision.Image(content=image_bytes)
response = vision_client.text_detection(image=image)
if response.error.message:
print(f"Vision API Error: {response.error.message}")
return ""
texts = response.text_annotations
if texts:
print("Extracted text successfully.")
return texts[0].description
print("No text found by OCR.")
return ""

def get_ai_feedback(solution_text, problem_context="a math problem"):
"""Gets feedback on the solution text using the Gemini LLM."""
prompt = f"""
Act as an expert AI math tutor. A student is working on: "{problem_context}".
Analyze their handwritten work provided as text. Your task is to provide a single, concise piece of feedback.
- If the work is correct so far, praise them and suggest the next logical step.
- If there is a mistake, gently point it out and provide a hint to correct it. Do not give the full answer.
- Use the following format for LaTeX commands: \\frac, \\sqrt, \\int, \\sum, and similar commands.


- Keep your feedback to one or two sentences.

The student's work is:
---
{solution_text}
---

Respond ONLY with a JSON object in the following format:
{{
"isCorrect": <boolean, true if the work is correct so far, otherwise false>,
"suggestion": "<string, your feedback/suggestion for the student>"
}}
"""
try:
response = gemini_model.generate_content(prompt)
cleaned_response = response.text.strip().replace("```json", "").replace("```", "")
return json.loads(cleaned_response)
except Exception as e:
print(f"Error in get_ai_feedback: {e}")
return {
"isCorrect": False,
"suggestion": "Sorry, I couldn't analyze the solution right now. Please try again."
}


def generate_structured_questions(text_content):
"""
Uses the Gemini model to identify questions in the text and format them
with suggestions into a specific JSON structure.
"""
prompt = f"""
Based on the following text extracted from a document, identify all the questions.
For each question, format it into a JSON object with "id", "title", and "suggestions".

1. **id**: A unique integer, starting from 1.
2. **title**: The question text. Use LaTeX for all math/scientific notations (e.g., $x^2$, \\text{{H}}_2\\text{{O}}).
3. **suggestions**: An array of exactly three objects, each with a "type", "title", and "content".
- **type**: Must be one of "info", "logic", or "feedback".
- **"info"**: Provides context or identifies the type of problem.
- **"logic"**: Suggests a method or logical steps for solving.
- **"feedback"**: Offers a way to check the answer.

Your response must be ONLY a valid JSON array of these objects. Do not include markdown formatting like ```json or any other explanatory text.

Example of desired output format:
[
{{
"id": 1,
"title": "Solve for $x$: $x + 5 = 12$",
"suggestions": [
{{ "type": "info", "title": "Equation Type", "content": "This is a simple linear equation with one variable." }},
{{ "type": "logic", "title": "Solution Method", "content": "Isolate the variable by performing the same operation on both sides of the equation." }},
{{ "type": "feedback", "title": "Check Your Work", "content": "Substitute your answer back into the original equation to verify it's correct." }}
]
}}
]

Now, process the following text:
---
{text_content}
---
"""

try:
response = gemini_model.generate_content(prompt)
cleaned_response = response.text.strip().replace("```json", "").replace("```", "")
return json.loads(cleaned_response)
except Exception as e:
print(f"Error generating or parsing JSON from LLM: {e}")
print(f"LLM Raw Response Text: {response.text if 'response' in locals() else 'No response'}")
return None

# --- API Routes ---

@app.route('/api/extract-questions', methods=['POST'])
def extract_questions_route():
"""
Handles file upload, extracts questions via OCR, and returns them in a
structured JSON format with generated suggestions.
"""
if 'file' not in request.files:
print(1)
return jsonify({"error": "No file part in the request"}), 400

file = request.files['file']

if file.filename == '':
print(2)
return jsonify({"error": "No file selected"}), 400

if not (file and allowed_file(file.filename)):
print(3)
return jsonify({"error": "File type not allowed"}), 400

try:
filename = secure_filename(file.filename)
unique_filename = f"{uuid.uuid4()}-{filename}"

file_bytes = file.read()

# --- FIX ---
# Removed the problematic 'magic' library.
# The file object from Flask already knows its own content type.
mime_type = file.mimetype
if mime_type not in ['image/png', 'image/jpeg', 'application/pdf']:
return jsonify({"error": f"Unsupported file content type: {mime_type}"}), 400

supabase.storage.from_("PDFBucket").upload(
file=file_bytes,
path=unique_filename,
file_options={"content-type": mime_type}
)

# --- FIX ---
# The image_ocr function call was passing too many arguments. Corrected it.
extracted_text = image_ocr(file_bytes)
if not extracted_text:
return jsonify({"error": "Could not extract text from the document."}), 500

structured_questions = generate_structured_questions(extracted_text)
print(structured_questions)
if structured_questions is None:
return jsonify({"error": "Failed to generate structured questions from the text."}), 500

return jsonify(structured_questions), 200

except Exception as e:
print(f"An unexpected error occurred in extract-questions: {e}")
return jsonify({"error": "An internal server error occurred.", "details": str(e)}), 500

@app.route("/api/analyze-work", methods=["POST"])
def analyze_whiteboard():
data = request.get_json()
if not data or "image" not in data:
return jsonify({"error": "No image data provided"}), 400

try:
image_bytes = base64.b64decode(data["image"])
except Exception as e:
return jsonify({"error": f"Invalid Base64 image data: {e}"}), 400

try:
bucket_name = "WhiteBoardImages"
file_name = f"solution-{uuid.uuid4()}.png"
supabase.storage.from_(bucket_name).upload(file_name, image_bytes, {
"content-type": "image/png"
})
print(f"Image uploaded to Supabase as: {file_name}")
except Exception as e:
print(f"Error uploading to Supabase: {e}")

extracted_text = image_ocr(image_bytes)
if extracted_text is None:
return jsonify({"error": "Failed to perform OCR on the image"}), 500

if not extracted_text.strip():
return jsonify({
"isCorrect": False,
"suggestion": "I couldn't find any text in your drawing. Please write your solution on the whiteboard."
})

problem_context = data.get("problemContext", "a math problem")
ai_feedback = get_ai_feedback(extracted_text, problem_context)
return jsonify(ai_feedback)


if __name__ == "__main__":
app.run(debug=True, port=5001)

70 changes: 0 additions & 70 deletions backend/backend.py

This file was deleted.

Empty file removed backend/backend.txt
Empty file.
Loading