Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@ import re
|
|
| 3 |
import json
|
| 4 |
import subprocess
|
| 5 |
import time
|
|
|
|
| 6 |
import img2pdf
|
| 7 |
import gradio as gr
|
| 8 |
from google import genai # NEW SDK
|
|
@@ -11,15 +12,110 @@ from PIL import Image, ImageDraw, ImageFont
|
|
| 11 |
import cv2
|
| 12 |
import numpy as np
|
| 13 |
from PyPDF2 import PdfReader, PdfWriter
|
| 14 |
-
from prompts import QP_MS_TRANSCRIPTION_PROMPT, get_grading_prompt
|
|
|
|
| 15 |
|
| 16 |
# ---------------- CONFIG ----------------
|
| 17 |
# Create client with new SDK
|
| 18 |
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
|
| 19 |
-
GRID_ROWS, GRID_COLS = 20, 14
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
# ---------------- PROMPTS ----------------
|
| 22 |
-
# Prompts are now imported from prompts.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
|
|
@@ -1050,19 +1146,37 @@ def align_and_grade_pipeline(qp_path, ms_path, ans_path, subject="Maths", imprin
|
|
| 1050 |
imprinted_pdf_path = imprint_marks_using_mapping(ans_path, grading_json, imprinted_pdf_path, extracted_ids)
|
| 1051 |
print("β
Imprinting finished. Imprinted PDF at:", imprinted_pdf_path)
|
| 1052 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
print("π Pipeline finished successfully.")
|
| 1054 |
-
return qpms_text, as_text, grading_text, grading_pdf_path, imprinted_pdf_path
|
| 1055 |
|
| 1056 |
except Exception as e:
|
| 1057 |
print("β Pipeline error:", e)
|
| 1058 |
import traceback
|
| 1059 |
traceback.print_exc()
|
| 1060 |
-
return f"β Error: {e}", None, None, None, None
|
| 1061 |
|
| 1062 |
# ---------------- GRADIO UI ----------------
|
| 1063 |
with gr.Blocks(title="AI Grading (Pandoc + pdflatex)") as demo:
|
| 1064 |
gr.Markdown("## π AI Grading β Using Pandoc + pdflatex for PDF Generation")
|
| 1065 |
gr.Markdown("**β
Now using Pandoc with pdflatex for professional-quality PDF outputs!**")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1066 |
|
| 1067 |
with gr.Row():
|
| 1068 |
qp_file = gr.File(label="π Upload Question Paper (PDF)")
|
|
@@ -1080,6 +1194,11 @@ with gr.Blocks(title="AI Grading (Pandoc + pdflatex)") as demo:
|
|
| 1080 |
|
| 1081 |
run_button = gr.Button("π Run Pipeline")
|
| 1082 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1083 |
with gr.Row():
|
| 1084 |
qpms_box = gr.Textbox(label="π QP+MS Transcript", lines=12)
|
| 1085 |
as_box = gr.Textbox(label="π AS Transcript", lines=12)
|
|
@@ -1090,23 +1209,74 @@ with gr.Blocks(title="AI Grading (Pandoc + pdflatex)") as demo:
|
|
| 1090 |
|
| 1091 |
def run_pipeline(qp_file_obj, ms_file_obj, ans_file_obj, subject_choice, imprint_flag):
|
| 1092 |
if not qp_file_obj or not ms_file_obj or not ans_file_obj:
|
| 1093 |
-
|
| 1094 |
-
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1098 |
|
| 1099 |
-
|
|
|
|
| 1100 |
qp_path, ms_path, ans_path, subject=subject_choice, imprint=imprint_flag
|
| 1101 |
-
)
|
| 1102 |
-
|
| 1103 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1104 |
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1110 |
|
| 1111 |
if __name__ == "__main__":
|
| 1112 |
demo.launch()
|
|
|
|
| 3 |
import json
|
| 4 |
import subprocess
|
| 5 |
import time
|
| 6 |
+
import shutil
|
| 7 |
import img2pdf
|
| 8 |
import gradio as gr
|
| 9 |
from google import genai # NEW SDK
|
|
|
|
| 12 |
import cv2
|
| 13 |
import numpy as np
|
| 14 |
from PyPDF2 import PdfReader, PdfWriter
|
| 15 |
+
from prompts import QP_MS_TRANSCRIPTION_PROMPT, get_grading_prompt
|
| 16 |
+
from supabase import create_client, Client
|
| 17 |
|
| 18 |
# ---------------- CONFIG ----------------
|
| 19 |
# Create client with new SDK
|
| 20 |
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
|
| 21 |
+
GRID_ROWS, GRID_COLS = 20, 14
|
| 22 |
+
|
| 23 |
+
# Supabase configuration
|
| 24 |
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
| 25 |
+
SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY")
|
| 26 |
+
SUPABASE_BUCKET = "examfiles"
|
| 27 |
+
|
| 28 |
+
# Initialize Supabase client (only if credentials are available)
|
| 29 |
+
supabase_client = None
|
| 30 |
+
if SUPABASE_URL and SUPABASE_SERVICE_KEY:
|
| 31 |
+
try:
|
| 32 |
+
supabase_client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
|
| 33 |
+
print("β
Supabase client initialized successfully")
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"β οΈ Supabase initialization failed: {e}")
|
| 36 |
+
else:
|
| 37 |
+
print("β οΈ Supabase credentials not found - file upload to storage disabled")
|
| 38 |
|
| 39 |
# ---------------- PROMPTS ----------------
|
| 40 |
+
# Prompts are now imported from prompts.py
|
| 41 |
+
|
| 42 |
+
# ---------------- SUPABASE HELPERS ----------------
|
| 43 |
+
def upload_file_to_supabase(local_path, file_type="unknown"):
|
| 44 |
+
"""
|
| 45 |
+
Upload a file to Supabase Storage.
|
| 46 |
+
|
| 47 |
+
Args:
|
| 48 |
+
local_path (str): Local file path
|
| 49 |
+
file_type (str): Type of file (qp, ms, ans, graded, imprinted)
|
| 50 |
+
|
| 51 |
+
Returns:
|
| 52 |
+
str: Public URL of uploaded file or None if upload failed
|
| 53 |
+
"""
|
| 54 |
+
if not supabase_client:
|
| 55 |
+
print("β οΈ Supabase not configured - skipping upload")
|
| 56 |
+
return None
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
timestamp = str(int(time.time()))
|
| 60 |
+
original_name = os.path.basename(local_path)
|
| 61 |
+
remote_path = f"{timestamp}/{file_type}_{original_name}"
|
| 62 |
+
|
| 63 |
+
print(f"π€ Uploading {file_type} to Supabase: {remote_path}")
|
| 64 |
+
|
| 65 |
+
with open(local_path, "rb") as f:
|
| 66 |
+
supabase_client.storage.from_(SUPABASE_BUCKET).upload(
|
| 67 |
+
remote_path,
|
| 68 |
+
f,
|
| 69 |
+
file_options={"upsert": "true"}
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
public_url = f"{SUPABASE_URL}/storage/v1/object/public/{SUPABASE_BUCKET}/{remote_path}"
|
| 73 |
+
print(f"β
Uploaded successfully: {public_url}")
|
| 74 |
+
return public_url
|
| 75 |
+
|
| 76 |
+
except Exception as e:
|
| 77 |
+
print(f"β Supabase upload failed for {file_type}: {e}")
|
| 78 |
+
return None
|
| 79 |
+
|
| 80 |
+
def process_and_upload_input_files(qp_file_obj, ms_file_obj, ans_file_obj):
|
| 81 |
+
"""
|
| 82 |
+
Process uploaded files and upload them to Supabase.
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
qp_file_obj: Gradio file object for Question Paper
|
| 86 |
+
ms_file_obj: Gradio file object for Markscheme
|
| 87 |
+
ans_file_obj: Gradio file object for Answer Sheet
|
| 88 |
+
|
| 89 |
+
Returns:
|
| 90 |
+
tuple: (qp_path, ms_path, ans_path, upload_urls_dict)
|
| 91 |
+
"""
|
| 92 |
+
print("\n" + "="*60)
|
| 93 |
+
print("π PROCESSING INPUT FILES")
|
| 94 |
+
print("="*60)
|
| 95 |
+
|
| 96 |
+
upload_urls = {
|
| 97 |
+
"qp_url": None,
|
| 98 |
+
"ms_url": None,
|
| 99 |
+
"ans_url": None
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
# Get local paths from Gradio file objects
|
| 103 |
+
qp_path = qp_file_obj.name if qp_file_obj else None
|
| 104 |
+
ms_path = ms_file_obj.name if ms_file_obj else None
|
| 105 |
+
ans_path = ans_file_obj.name if ans_file_obj else None
|
| 106 |
+
|
| 107 |
+
# Upload to Supabase if configured
|
| 108 |
+
if supabase_client:
|
| 109 |
+
if qp_path:
|
| 110 |
+
upload_urls["qp_url"] = upload_file_to_supabase(qp_path, "qp")
|
| 111 |
+
if ms_path:
|
| 112 |
+
upload_urls["ms_url"] = upload_file_to_supabase(ms_path, "ms")
|
| 113 |
+
if ans_path:
|
| 114 |
+
upload_urls["ans_url"] = upload_file_to_supabase(ans_path, "ans")
|
| 115 |
+
|
| 116 |
+
print("="*60 + "\n")
|
| 117 |
+
|
| 118 |
+
return qp_path, ms_path, ans_path, upload_urls
|
| 119 |
|
| 120 |
|
| 121 |
|
|
|
|
| 1146 |
imprinted_pdf_path = imprint_marks_using_mapping(ans_path, grading_json, imprinted_pdf_path, extracted_ids)
|
| 1147 |
print("β
Imprinting finished. Imprinted PDF at:", imprinted_pdf_path)
|
| 1148 |
|
| 1149 |
+
# Upload output files to Supabase
|
| 1150 |
+
output_urls = {
|
| 1151 |
+
"graded_pdf_url": None,
|
| 1152 |
+
"imprinted_pdf_url": None
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
if supabase_client:
|
| 1156 |
+
print("\nπ€ Uploading output files to Supabase...")
|
| 1157 |
+
if grading_pdf_path:
|
| 1158 |
+
output_urls["graded_pdf_url"] = upload_file_to_supabase(grading_pdf_path, "graded")
|
| 1159 |
+
if imprinted_pdf_path:
|
| 1160 |
+
output_urls["imprinted_pdf_url"] = upload_file_to_supabase(imprinted_pdf_path, "imprinted")
|
| 1161 |
+
|
| 1162 |
print("π Pipeline finished successfully.")
|
| 1163 |
+
return qpms_text, as_text, grading_text, grading_pdf_path, imprinted_pdf_path, output_urls
|
| 1164 |
|
| 1165 |
except Exception as e:
|
| 1166 |
print("β Pipeline error:", e)
|
| 1167 |
import traceback
|
| 1168 |
traceback.print_exc()
|
| 1169 |
+
return f"β Error: {e}", None, None, None, None, {}
|
| 1170 |
|
| 1171 |
# ---------------- GRADIO UI ----------------
|
| 1172 |
with gr.Blocks(title="AI Grading (Pandoc + pdflatex)") as demo:
|
| 1173 |
gr.Markdown("## π AI Grading β Using Pandoc + pdflatex for PDF Generation")
|
| 1174 |
gr.Markdown("**β
Now using Pandoc with pdflatex for professional-quality PDF outputs!**")
|
| 1175 |
+
|
| 1176 |
+
if supabase_client:
|
| 1177 |
+
gr.Markdown("**βοΈ Supabase Storage: Enabled** - All files will be uploaded to cloud storage")
|
| 1178 |
+
else:
|
| 1179 |
+
gr.Markdown("**β οΈ Supabase Storage: Disabled** - Files will only be processed locally")
|
| 1180 |
|
| 1181 |
with gr.Row():
|
| 1182 |
qp_file = gr.File(label="π Upload Question Paper (PDF)")
|
|
|
|
| 1194 |
|
| 1195 |
run_button = gr.Button("π Run Pipeline")
|
| 1196 |
|
| 1197 |
+
# File URLs section (only shown if Supabase is enabled)
|
| 1198 |
+
if supabase_client:
|
| 1199 |
+
with gr.Accordion("βοΈ Uploaded File URLs", open=False):
|
| 1200 |
+
file_urls_box = gr.Textbox(label="Cloud Storage URLs", lines=8, interactive=False)
|
| 1201 |
+
|
| 1202 |
with gr.Row():
|
| 1203 |
qpms_box = gr.Textbox(label="π QP+MS Transcript", lines=12)
|
| 1204 |
as_box = gr.Textbox(label="π AS Transcript", lines=12)
|
|
|
|
| 1209 |
|
| 1210 |
def run_pipeline(qp_file_obj, ms_file_obj, ans_file_obj, subject_choice, imprint_flag):
|
| 1211 |
if not qp_file_obj or not ms_file_obj or not ans_file_obj:
|
| 1212 |
+
error_msg = "β Please upload all three files"
|
| 1213 |
+
if supabase_client:
|
| 1214 |
+
return error_msg, "", "", None, None, ""
|
| 1215 |
+
else:
|
| 1216 |
+
return error_msg, "", "", None, None
|
| 1217 |
+
|
| 1218 |
+
# Process and upload input files
|
| 1219 |
+
qp_path, ms_path, ans_path, input_urls = process_and_upload_input_files(
|
| 1220 |
+
qp_file_obj, ms_file_obj, ans_file_obj
|
| 1221 |
+
)
|
| 1222 |
|
| 1223 |
+
# Run the grading pipeline
|
| 1224 |
+
qpms_text, as_text, grading_text, grading_pdf_path, imprinted_pdf_path, output_urls = align_and_grade_pipeline(
|
| 1225 |
qp_path, ms_path, ans_path, subject=subject_choice, imprint=imprint_flag
|
| 1226 |
+
)
|
| 1227 |
+
|
| 1228 |
+
# Build URLs summary
|
| 1229 |
+
urls_summary = ""
|
| 1230 |
+
if supabase_client:
|
| 1231 |
+
urls_summary = "π€ UPLOADED FILES:\n\n"
|
| 1232 |
+
urls_summary += "INPUT FILES:\n"
|
| 1233 |
+
if input_urls.get("qp_url"):
|
| 1234 |
+
urls_summary += f"β’ Question Paper: {input_urls['qp_url']}\n"
|
| 1235 |
+
if input_urls.get("ms_url"):
|
| 1236 |
+
urls_summary += f"β’ Markscheme: {input_urls['ms_url']}\n"
|
| 1237 |
+
if input_urls.get("ans_url"):
|
| 1238 |
+
urls_summary += f"β’ Answer Sheet: {input_urls['ans_url']}\n"
|
| 1239 |
+
|
| 1240 |
+
urls_summary += "\nOUTPUT FILES:\n"
|
| 1241 |
+
if output_urls.get("graded_pdf_url"):
|
| 1242 |
+
urls_summary += f"β’ Graded PDF: {output_urls['graded_pdf_url']}\n"
|
| 1243 |
+
if output_urls.get("imprinted_pdf_url"):
|
| 1244 |
+
urls_summary += f"β’ Imprinted PDF: {output_urls['imprinted_pdf_url']}\n"
|
| 1245 |
+
|
| 1246 |
+
if not any(input_urls.values()) and not any(output_urls.values()):
|
| 1247 |
+
urls_summary += "\nβ οΈ No files were uploaded to Supabase"
|
| 1248 |
+
|
| 1249 |
+
if supabase_client:
|
| 1250 |
+
return (
|
| 1251 |
+
qpms_text or "",
|
| 1252 |
+
as_text or "",
|
| 1253 |
+
grading_text or "",
|
| 1254 |
+
grading_pdf_path,
|
| 1255 |
+
imprinted_pdf_path,
|
| 1256 |
+
urls_summary
|
| 1257 |
+
)
|
| 1258 |
+
else:
|
| 1259 |
+
return (
|
| 1260 |
+
qpms_text or "",
|
| 1261 |
+
as_text or "",
|
| 1262 |
+
grading_text or "",
|
| 1263 |
+
grading_pdf_path,
|
| 1264 |
+
imprinted_pdf_path
|
| 1265 |
+
)
|
| 1266 |
|
| 1267 |
+
# Set up the click handler based on whether Supabase is enabled
|
| 1268 |
+
if supabase_client:
|
| 1269 |
+
run_button.click(
|
| 1270 |
+
fn=run_pipeline,
|
| 1271 |
+
inputs=[qp_file, ms_file, ans_file, subject_dropdown, imprint_toggle],
|
| 1272 |
+
outputs=[qpms_box, as_box, grading_output_box, grading_pdf_file, imprint_pdf_file, file_urls_box]
|
| 1273 |
+
)
|
| 1274 |
+
else:
|
| 1275 |
+
run_button.click(
|
| 1276 |
+
fn=run_pipeline,
|
| 1277 |
+
inputs=[qp_file, ms_file, ans_file, subject_dropdown, imprint_toggle],
|
| 1278 |
+
outputs=[qpms_box, as_box, grading_output_box, grading_pdf_file, imprint_pdf_file]
|
| 1279 |
+
)
|
| 1280 |
|
| 1281 |
if __name__ == "__main__":
|
| 1282 |
demo.launch()
|