vishwakayireddi1234's picture
Update app.py
e2f0f0e verified
Raw
History Blame Contribute Delete
14.9 kB
import gradio as gr
import os
import requests
import json
import base64
import re
from PIL import Image, ImageOps
from io import BytesIO
GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions"
SPACE_GROQ_KEY = os.environ.get("GROQ_API_KEY", "")
TEXT_MODEL = "llama-3.3-70b-versatile"
VISION_MODEL = "meta-llama/llama-4-scout-17b-16e-instruct"
SYSTEM_PROMPT = """You are a warm, patient math tutor for students from Grade 1 through High School.
Rules you ALWAYS follow:
1. Never give just the answer. Guide the student step by step.
2. Before each step, explain WHAT you are doing and WHY in simple language.
3. Match your language to the grade level given.
4. Use proper math notation (fractions, exponents, equations).
5. End with a clearly marked Final Answer and one sentence of encouragement.
6. For any calculation, write out every arithmetic operation explicitly before evaluating it. Never skip steps or compute mentally.
7. For trigonometry, always state the formula, substitute values, then compute. Show sin/cos/tan values to 4 decimal places.
8. Never use LaTeX notation. Use plain text only. Write fractions as a/b, exponents as x^2, and put each equation on its own line.
Always format your response exactly like this:
πŸ“˜ **Problem:** [restate the problem clearly]
---
**Step 1 β€” [Short title]**
*Why:* [reason in one sentence]
[Show the calculation or concept]
**Step 2 β€” [Short title]**
*Why:* [reason in one sentence]
[Show the calculation or concept]
(continue for all steps needed)
---
βœ… **Final Answer:** [clear result]
πŸ’‘ [One encouraging sentence]"""
def strip_latex(text):
text = re.sub(r'\$\$(.+?)\$\$', r'\1', text, flags=re.DOTALL)
text = re.sub(r'\$(.+?)\$', r'\1', text)
text = re.sub(r'\\frac\{(.+?)\}\{(.+?)\}', r'\1/\2', text)
text = re.sub(r'\\text\{(.+?)\}', r'\1', text)
text = text.replace(r'\cos', 'cos')
text = text.replace(r'\sin', 'sin')
text = text.replace(r'\tan', 'tan')
text = text.replace(r'\times', 'Γ—')
text = text.replace(r'\div', 'Γ·')
text = text.replace(r'\approx', 'β‰ˆ')
text = text.replace(r'\cdot', 'Β·')
text = text.replace(r'\left', '').replace(r'\right', '')
text = text.replace(r'\sqrt', 'sqrt')
text = text.replace(r'\pi', 'Ο€')
text = re.sub(r'\{|\}', '', text)
return text
def image_to_base64(pil_image):
# Flip horizontally to correct webcam mirror effect
pil_image = ImageOps.mirror(pil_image)
buffered = BytesIO()
pil_image.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode("utf-8")
def solve_math(image_input, text_input, grade_level, user_key):
key = user_key.strip() if user_key and user_key.strip() else SPACE_GROQ_KEY
if not key:
yield (
"πŸ”‘ **A free Groq API key is required.**\n\n"
"Get one in 60 seconds β€” no credit card needed:\n"
"1. Go to [console.groq.com/keys](https://console.groq.com/keys)\n"
"2. Sign up free β†’ click **Create API Key** β†’ copy it\n"
"3. Paste it in the **Groq API Key** box on the left\n\n"
"Your key is never stored β€” session only."
)
return
has_image = image_input is not None
has_text = text_input and text_input.strip()
if not has_image and not has_text:
yield "⚠️ Please upload a worksheet image **or** type a math problem."
return
grade_note = f"Grade level: {grade_level}."
yield "⚑ Solving step by step..."
try:
if has_image:
img_b64 = image_to_base64(image_input)
text_note = f"\nStudent's additional note: {text_input.strip()}" if has_text else ""
user_content = [
{
"type": "image_url",
"image_url": {
"url": f"data:image/png;base64,{img_b64}"
}
},
{
"type": "text",
"text": f"{grade_note}\n\nPlease read this math problem from the image carefully, identify all numbers and labels, then teach me how to solve it step by step.{text_note}"
}
]
model = VISION_MODEL
else:
user_content = f"{grade_note}\n\nProblem: {text_input.strip()}\n\nPlease teach me how to solve this step by step."
model = TEXT_MODEL
response = requests.post(
GROQ_API_URL,
headers={
"Authorization": f"Bearer {key}",
"Content-Type": "application/json",
},
json={
"model": model,
"messages": [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_content},
],
"max_tokens": 1200,
"temperature": 0,
"stream": True,
},
stream=True,
timeout=60,
)
if response.status_code == 401:
yield "❌ **Invalid API key.** Please check your Groq key and try again."
return
if response.status_code == 429:
yield "⚠️ Rate limit hit. Please wait a few seconds and try again."
return
if response.status_code != 200:
yield f"❌ API error {response.status_code}: {response.text[:200]}"
return
output = ""
for raw in response.iter_lines():
if not raw:
continue
line = raw.decode("utf-8", errors="ignore")
if line.startswith("data: "):
line = line[6:].strip()
if line == "[DONE]":
break
try:
token = json.loads(line)["choices"][0]["delta"].get("content", "")
if token:
output += token
yield strip_latex(output)
except Exception:
continue
if not output:
yield "⚠️ No response received. Please try again."
except requests.exceptions.Timeout:
yield "❌ Request timed out. Please try again."
except Exception as e:
yield f"❌ Error: {str(e)}"
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&family=JetBrains+Mono:wght@400;600&display=swap');
:root {
--bg:#0d1117; --surface:#161b22; --surface2:#1f2937;
--accent:#58a6ff; --accent2:#bc8cff; --green:#3fb950;
--text:#e6edf3; --muted:#7d8590; --border:#30363d; --radius:14px;
}
body, .gradio-container { background:var(--bg) !important; font-family:'Nunito',sans-serif !important; color:var(--text) !important; }
.math-header { text-align:center; padding:32px 20px 18px; background:linear-gradient(160deg,#161b22,#0d1117); border-bottom:1px solid var(--border); border-radius:var(--radius); margin-bottom:20px; }
.math-header h1 { font-size:2.2rem; font-weight:900; background:linear-gradient(90deg,var(--accent),var(--accent2),var(--green)); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; margin:0 0 6px; }
.math-header p { color:var(--muted); font-size:.95rem; margin:0; }
.badge-row { display:flex; gap:8px; flex-wrap:wrap; justify-content:center; margin-top:12px; }
.badge { background:var(--surface2); border:1px solid var(--border); border-radius:20px; padding:3px 12px; font-size:.76rem; color:var(--muted); }
.panel-label { font-size:.72rem; font-weight:800; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin-bottom:6px; }
.token-box textarea, .token-box input { background:var(--surface2) !important; border:1px solid var(--border) !important; border-radius:10px !important; color:var(--text) !important; font-family:'JetBrains Mono',monospace !important; font-size:.85rem !important; }
.gr-textbox textarea, .gr-textbox input { background:var(--surface2) !important; border:1px solid var(--border) !important; border-radius:10px !important; color:var(--text) !important; font-family:'Nunito',sans-serif !important; font-size:.95rem !important; }
.solve-btn { background:linear-gradient(135deg,var(--accent),var(--accent2)) !important; color:#fff !important; font-family:'Nunito',sans-serif !important; font-weight:900 !important; font-size:1.05rem !important; border:none !important; border-radius:12px !important; padding:14px 0 !important; width:100% !important; cursor:pointer !important; transition:all .2s !important; }
.solve-btn:hover { transform:translateY(-2px) !important; box-shadow:0 8px 24px rgba(88,166,255,.35) !important; }
.clear-btn { background:var(--surface2) !important; color:var(--muted) !important; border:1px solid var(--border) !important; border-radius:12px !important; font-family:'Nunito',sans-serif !important; font-weight:700 !important; transition:all .2s !important; }
.clear-btn:hover { border-color:var(--accent) !important; color:var(--text) !important; }
.output-box { background:var(--surface) !important; border:1px solid var(--border) !important; border-radius:var(--radius) !important; padding:24px !important; min-height:420px !important; font-family:'Nunito',sans-serif !important; font-size:.97rem !important; line-height:1.8 !important; color:var(--text) !important; }
.output-box code, .output-box pre { font-family:'JetBrains Mono',monospace !important; background:var(--surface2) !important; border-radius:6px !important; padding:2px 8px !important; }
.key-info { background:var(--surface2); border:1px solid var(--border); border-left:3px solid var(--accent); border-radius:10px; padding:12px 16px; margin-top:8px; font-size:.82rem; color:var(--muted); line-height:1.7; }
.key-info a { color:var(--accent); text-decoration:none; }
.key-info strong { color:var(--text); }
.info-box { background:var(--surface2); border:1px solid var(--border); border-left:3px solid var(--green); border-radius:10px; padding:12px 16px; margin-top:8px; font-size:.82rem; color:var(--muted); line-height:1.6; }
.info-box strong { color:var(--green); }
footer { display:none !important; }
"""
GRADE_CHOICES = [
"Auto-detect from problem",
"Grade 1–2 (Basic Addition & Subtraction)",
"Grade 3–4 (Multiplication, Division, Fractions Intro)",
"Grade 5–6 (Fractions, Decimals, Geometry Basics)",
"Grade 7–8 (Algebra, Ratios, Percentages)",
"Grade 9–10 (Algebra II, Geometry, Trigonometry)",
"Grade 11–12 (Pre-Calculus, Statistics, Calculus)",
]
EXAMPLES = [
[None, "What is 24 Γ· 6 + 3 Γ— 2?", "Grade 3–4 (Multiplication, Division, Fractions Intro)", ""],
[None, "If I have 3/4 of a pizza and eat 1/3, how much is left?", "Grade 5–6 (Fractions, Decimals, Geometry Basics)", ""],
[None, "Solve for x: 2x + 5 = 17", "Grade 7–8 (Algebra, Ratios, Percentages)", ""],
[None, "Find the area of a circle with radius 7 cm", "Grade 9–10 (Algebra II, Geometry, Trigonometry)", ""],
[None, "Differentiate f(x) = 3xΒ³ βˆ’ 2x + 7", "Grade 11–12 (Pre-Calculus, Statistics, Calculus)", ""],
]
OWNER_KEY_SET = bool(SPACE_GROQ_KEY)
with gr.Blocks(css=CSS, title="Interactive Math Tutor") as demo:
gr.HTML("""
<div class="math-header">
<h1>πŸ“ Interactive Math Tutor</h1>
<p>Step-by-step guidance Β· Grade 1 to High School Β· Upload a worksheet or type any problem</p>
<div class="badge-row">
<span class="badge">✏️ Arithmetic</span><span class="badge">½ Fractions</span>
<span class="badge">πŸ“Š Algebra</span><span class="badge">πŸ“ Geometry</span>
<span class="badge">πŸ“ˆ Trigonometry</span><span class="badge">∫ Calculus</span>
</div>
</div>
""")
with gr.Row(equal_height=False):
with gr.Column(scale=4, min_width=300):
if not OWNER_KEY_SET:
gr.HTML('<div class="panel-label">πŸ”‘ Groq API Key (free, no card)</div>')
user_key = gr.Textbox(
label="", placeholder="gsk_xxxxxxxxxxxxxxxxxxxx",
type="password", lines=1, elem_classes=["token-box"],
)
gr.HTML("""
<div class="key-info">
<strong>Get your free key in 60 seconds:</strong><br>
1. Go to <a href="https://console.groq.com/keys" target="_blank">console.groq.com/keys</a><br>
2. Sign up free (no credit card) β†’ <strong>Create API Key</strong> β†’ Copy<br>
3. Paste above Β· Never stored, session-only
</div>
""")
else:
user_key = gr.Textbox(value="", visible=False)
gr.HTML('<div class="info-box"><strong>βœ… Ready to use β€” no setup needed!</strong></div>')
gr.HTML('<div class="panel-label" style="margin-top:16px">πŸ“Ž Upload Worksheet</div>')
image_input = gr.Image(type="pil", label="", height=180)
gr.HTML('<div style="font-size:.78rem;color:var(--muted);margin-top:4px">πŸ“Έ Images are read directly by AI β€” diagrams, triangles, and graphs all work!</div>')
gr.HTML('<div class="panel-label" style="margin-top:14px">✏️ Or Type Your Problem</div>')
text_input = gr.Textbox(label="", placeholder="e.g. Solve for x: 5x βˆ’ 3 = 22", lines=3)
gr.HTML('<div class="panel-label" style="margin-top:14px">πŸŽ“ Grade Level</div>')
grade_dropdown = gr.Dropdown(choices=GRADE_CHOICES, value="Auto-detect from problem", label="")
with gr.Row():
solve_btn = gr.Button("πŸš€ Solve & Explain", elem_classes=["solve-btn"])
clear_btn = gr.Button("πŸ—‘ Clear", elem_classes=["clear-btn"])
gr.HTML('<div class="panel-label" style="margin-top:16px">πŸ’‘ Try an example</div>')
gr.Examples(examples=EXAMPLES, inputs=[image_input, text_input, grade_dropdown, user_key], label="")
with gr.Column(scale=6, min_width=360):
gr.HTML('<div class="panel-label">πŸŽ“ Step-by-Step Solution</div>')
output_display = gr.Markdown(
value="*Your solution will appear here. Upload a worksheet or type a problem, then click **Solve & Explain**.*",
elem_classes=["output-box"],
)
solve_btn.click(
fn=solve_math,
inputs=[image_input, text_input, grade_dropdown, user_key],
outputs=output_display,
api_name=False,
)
clear_btn.click(
fn=lambda: (None, "", "Auto-detect from problem", "", "*Your solution will appear here.*"),
inputs=[],
outputs=[image_input, text_input, grade_dropdown, user_key, output_display],
api_name=False,
)
if __name__ == "__main__":
demo.queue().launch()