vishwakayireddi1234 commited on
Commit
4c1fbe0
Β·
verified Β·
1 Parent(s): c584596

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +273 -113
app.py CHANGED
@@ -2,146 +2,306 @@ import gradio as gr
2
  import requests
3
  import json
4
  import base64
 
5
  from io import BytesIO
6
- from PIL import Image
7
 
8
- API_URL = "http://127.0.0.1:8080/v1/chat/completions"
 
9
 
10
- def fix_and_convert_image(pil_image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  try:
12
  buffered = BytesIO()
13
- rgb_image = pil_image.convert("RGB")
14
- # Optimized size for native MiniCPM visual patch matching
15
- rgb_image.thumbnail((448, 448))
16
- rgb_image.save(
17
- buffered,
18
- format="JPEG",
19
- quality=70,
20
- optimize=True
21
- )
22
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
23
  except Exception:
24
  return None
25
 
26
- def process_math_request(image_input, text_input):
27
- if image_input is None and (not text_input or not text_input.strip()):
28
- yield "⚠️ Please upload an image or enter a math problem."
 
 
 
 
 
 
 
 
 
 
 
29
  return
30
-
31
- yield "⚑ Calculation in progress..."
32
- try:
33
- if image_input is not None:
34
- base64_image = fix_and_convert_image(image_input)
35
- payload = {
36
- "messages": [
37
- {
38
- "role": "system",
39
- "content": "You are a concise mathematical tool. Output only technical steps and final answers. Skip introductory text completely."
40
- },
41
- {
42
- "role": "user",
43
- "content": [
44
- {
45
- "type": "text",
46
- "text": (
47
- "Transcribe and solve this math problem using strict markdown formatting:\n\n"
48
- "### Problem\n"
49
- "[Briefly state the problem]\n\n"
50
- "### Steps\n"
51
- "[List mathematical step calculation entries directly]\n\n"
52
- "### Final Answer\n"
53
- "[Final result code block]"
54
- )
55
- },
56
- {
57
- "type": "image_url",
58
- "image_url": {
59
- "url": f"data:image/jpeg;base64,{base64_image}"
60
- }
61
- }
62
- ]
63
- }
64
- ],
65
- "stream": True,
66
- "temperature": 0,
67
- "max_tokens": 300
68
- }
69
- else:
70
- payload = {
71
- "messages": [
72
- {
73
- "role": "system",
74
- "content": "You are a direct mathematical calculator."
75
- },
76
- {
77
- "role": "user",
78
- "content": f"Solve this problem step by step with zero conversational fluff:\n\n{text_input}"
79
- }
80
  ],
81
- "stream": True,
82
- "temperature": 0,
83
- "max_tokens": 300
84
- }
85
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  response = requests.post(
87
- API_URL,
 
88
  json=payload,
89
  stream=True,
90
- timeout=120
91
  )
92
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  output = ""
94
- for line in response.iter_lines():
95
- if not line:
96
  continue
97
- line = line.decode("utf-8")
98
  if line.startswith("data: "):
99
- line = line[6:]
100
- if line.strip() == "[DONE]":
101
- break
102
  try:
103
  data = json.loads(line)
104
- token = data["choices"][0]["delta"].get("content", "")
 
105
  if token:
106
  output += token
107
  yield output
108
- except Exception:
109
  continue
 
 
 
 
 
 
110
  except Exception as e:
111
- yield f"❌ Error: {str(e)}"
112
-
113
- # Restored clean user interface styling matches
114
- with gr.Blocks() as demo:
115
- gr.Markdown(
116
- """
117
- # πŸ“ Fast Local Math Tutor
118
- Upload a worksheet image or enter a math problem.
119
- """
120
- )
121
- with gr.Row():
122
- with gr.Column():
123
- image_input = gr.Image(
124
- type="pil",
125
- label="Worksheet Image"
126
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  text_input = gr.Textbox(
128
- label="Math Problem",
129
- placeholder="Type a math problem..."
130
  )
131
- submit_btn = gr.Button("Solve")
132
- with gr.Column():
133
- # Assigned cleanly within its own layout block to protect UI alignment
134
- gr.Markdown("### πŸŽ“ Solution Analysis")
135
- output_display = gr.Markdown("Waiting for execution input...")
136
-
137
- submit_btn.click(
138
- process_math_request,
139
- inputs=[image_input, text_input],
140
- outputs=output_display
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  )
142
 
143
  if __name__ == "__main__":
144
- demo.queue().launch(
145
- server_name="0.0.0.0",
146
- server_port=7860
147
- )
 
2
  import requests
3
  import json
4
  import base64
5
+ import os
6
  from io import BytesIO
 
7
 
8
+ # HuggingFace provides this automatically in every Space β€” no setup needed
9
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
10
 
11
+ # Text-only math model: Qwen2.5-Math is purpose-built for math reasoning
12
+ TEXT_MODEL = "Qwen/Qwen2.5-Math-72B-Instruct"
13
+ TEXT_API = f"https://api-inference.huggingface.co/models/{TEXT_MODEL}/v1/chat/completions"
14
+
15
+ # Vision model for worksheet images
16
+ VISION_MODEL = "Qwen/Qwen2-VL-7B-Instruct"
17
+ VISION_API = f"https://api-inference.huggingface.co/models/{VISION_MODEL}/v1/chat/completions"
18
+
19
+ SYSTEM_PROMPT = """You are a warm, patient math tutor for students from Grade 1 through High School.
20
+
21
+ Rules you ALWAYS follow:
22
+ 1. Never give just the answer. Guide the student step by step.
23
+ 2. Before each step, explain WHAT you are doing and WHY in simple language.
24
+ 3. Match your language to the grade level given.
25
+ 4. Use proper math notation (fractions, exponents, equations).
26
+ 5. End with a clearly marked Final Answer and one sentence of encouragement.
27
+
28
+ Always format your response exactly like this:
29
+
30
+ πŸ“˜ **Problem:** [restate the problem clearly]
31
+
32
+ ---
33
+
34
+ **Step 1 β€” [Short title]**
35
+ *Why:* [reason in one sentence]
36
+ [Show the calculation or concept]
37
+
38
+ **Step 2 β€” [Short title]**
39
+ *Why:* [reason in one sentence]
40
+ [Show the calculation or concept]
41
+
42
+ (continue for all steps needed)
43
+
44
+ ---
45
+
46
+ βœ… **Final Answer:** [clear result]
47
+
48
+ πŸ’‘ [One encouraging sentence]"""
49
+
50
+
51
+ def image_to_base64(pil_image):
52
  try:
53
  buffered = BytesIO()
54
+ pil_image.convert("RGB").thumbnail((896, 896))
55
+ pil_image.save(buffered, format="JPEG", quality=85)
 
 
 
 
 
 
 
56
  return base64.b64encode(buffered.getvalue()).decode("utf-8")
57
  except Exception:
58
  return None
59
 
60
+
61
+ def get_headers():
62
+ headers = {"Content-Type": "application/json"}
63
+ if HF_TOKEN:
64
+ headers["Authorization"] = f"Bearer {HF_TOKEN}"
65
+ return headers
66
+
67
+
68
+ def solve_math(image_input, text_input, grade_level):
69
+ has_image = image_input is not None
70
+ has_text = text_input and text_input.strip()
71
+
72
+ if not has_image and not has_text:
73
+ yield "⚠️ Please upload a worksheet image **or** type a math problem."
74
  return
75
+
76
+ yield "πŸ” Reading your problem..."
77
+
78
+ grade_note = f"Grade level: {grade_level}."
79
+
80
+ if has_image:
81
+ b64 = image_to_base64(image_input)
82
+ if b64 is None:
83
+ yield "❌ Could not process the image. Please try a clearer photo."
84
+ return
85
+
86
+ extra = f"\nExtra note from student: {text_input.strip()}" if has_text else ""
87
+ user_text = (
88
+ f"{grade_note}\n"
89
+ f"Look at this math problem from my worksheet and teach me how to solve it step by step.{extra}"
90
+ )
91
+
92
+ messages = [
93
+ {"role": "system", "content": SYSTEM_PROMPT},
94
+ {
95
+ "role": "user",
96
+ "content": [
97
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
98
+ {"type": "text", "text": user_text},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  ],
100
+ },
101
+ ]
102
+ api_url = VISION_API
103
+ model_id = VISION_MODEL
104
+
105
+ else:
106
+ messages = [
107
+ {"role": "system", "content": SYSTEM_PROMPT},
108
+ {"role": "user", "content": f"{grade_note}\n\nProblem: {text_input.strip()}"},
109
+ ]
110
+ api_url = TEXT_API
111
+ model_id = TEXT_MODEL
112
+
113
+ payload = {
114
+ "model": model_id,
115
+ "messages": messages,
116
+ "max_tokens": 1200,
117
+ "temperature": 0.3,
118
+ "stream": True,
119
+ }
120
+
121
+ yield "⚑ Solving step by step..."
122
+
123
+ try:
124
  response = requests.post(
125
+ api_url,
126
+ headers=get_headers(),
127
  json=payload,
128
  stream=True,
129
+ timeout=120,
130
  )
131
+
132
+ if response.status_code == 503:
133
+ yield (
134
+ "⏳ **Model is warming up** β€” this happens after a period of inactivity.\n\n"
135
+ "Please wait about **30 seconds** and click **Solve & Explain** again."
136
+ )
137
+ return
138
+
139
+ if response.status_code == 429:
140
+ yield "⚠️ HuggingFace servers are busy right now. Please wait a few seconds and try again."
141
+ return
142
+
143
+ if response.status_code not in (200, 201):
144
+ yield f"❌ API error {response.status_code}. Please try again in a moment.\n\nDetails: {response.text[:300]}"
145
+ return
146
+
147
  output = ""
148
+ for raw_line in response.iter_lines():
149
+ if not raw_line:
150
  continue
151
+ line = raw_line.decode("utf-8", errors="ignore")
152
  if line.startswith("data: "):
153
+ line = line[6:].strip()
154
+ if line in ("[DONE]", ""):
155
+ continue
156
  try:
157
  data = json.loads(line)
158
+ delta = data.get("choices", [{}])[0].get("delta", {})
159
+ token = delta.get("content", "")
160
  if token:
161
  output += token
162
  yield output
163
+ except (json.JSONDecodeError, KeyError, IndexError):
164
  continue
165
+
166
+ if not output:
167
+ yield "⚠️ No response received. The model may still be loading β€” please try again in 30 seconds."
168
+
169
+ except requests.exceptions.Timeout:
170
+ yield "❌ Request timed out. Please try again."
171
  except Exception as e:
172
+ yield f"❌ Unexpected error: {str(e)}"
173
+
174
+
175
+ CSS = """
176
+ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&family=JetBrains+Mono:wght@400;600&display=swap');
177
+
178
+ :root {
179
+ --bg: #0d1117;
180
+ --surface: #161b22;
181
+ --surface2: #1f2937;
182
+ --accent: #58a6ff;
183
+ --accent2: #bc8cff;
184
+ --green: #3fb950;
185
+ --text: #e6edf3;
186
+ --muted: #7d8590;
187
+ --border: #30363d;
188
+ --radius: 14px;
189
+ }
190
+
191
+ body, .gradio-container { background: var(--bg) !important; font-family: 'Nunito', sans-serif !important; color: var(--text) !important; }
192
+
193
+ .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; }
194
+ .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; }
195
+ .math-header p { color:var(--muted); font-size:.95rem; margin:0; }
196
+
197
+ .badge-row { display:flex; gap:8px; flex-wrap:wrap; justify-content:center; margin-top:12px; }
198
+ .badge { background:var(--surface2); border:1px solid var(--border); border-radius:20px; padding:3px 12px; font-size:.76rem; color:var(--muted); }
199
+
200
+ .panel-label { font-size:.72rem; font-weight:800; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin-bottom:6px; }
201
+
202
+ .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; }
203
+ .gr-textbox textarea:focus { border-color:var(--accent) !important; box-shadow:0 0 0 3px rgba(88,166,255,.15) !important; }
204
+
205
+ .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; }
206
+ .solve-btn:hover { transform:translateY(-2px) !important; box-shadow:0 8px 24px rgba(88,166,255,.35) !important; }
207
+
208
+ .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; }
209
+ .clear-btn:hover { border-color:var(--accent) !important; color:var(--text) !important; }
210
+
211
+ .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; }
212
+ .output-box code { font-family:'JetBrains Mono',monospace !important; background:var(--surface2) !important; border-radius:6px !important; padding:2px 8px !important; }
213
+
214
+ .info-box { background:var(--surface2); border:1px solid var(--border); border-left:3px solid var(--green); border-radius:10px; padding:12px 16px; margin-top:16px; font-size:.85rem; color:var(--muted); line-height:1.6; }
215
+ .info-box strong { color:var(--green); }
216
+
217
+ footer { display:none !important; }
218
+ """
219
+
220
+ GRADE_CHOICES = [
221
+ "Auto-detect from problem",
222
+ "Grade 1–2 (Basic Addition & Subtraction)",
223
+ "Grade 3–4 (Multiplication, Division, Fractions Intro)",
224
+ "Grade 5–6 (Fractions, Decimals, Geometry Basics)",
225
+ "Grade 7–8 (Algebra, Ratios, Percentages)",
226
+ "Grade 9–10 (Algebra II, Geometry, Trigonometry)",
227
+ "Grade 11–12 (Pre-Calculus, Statistics, Calculus)",
228
+ ]
229
+
230
+ EXAMPLES = [
231
+ [None, "What is 24 Γ· 6 + 3 Γ— 2?", "Grade 3–4 (Multiplication, Division, Fractions Intro)"],
232
+ [None, "If I have 3/4 of a pizza and eat 1/3, how much is left?", "Grade 5–6 (Fractions, Decimals, Geometry Basics)"],
233
+ [None, "Solve for x: 2x + 5 = 17", "Grade 7–8 (Algebra, Ratios, Percentages)"],
234
+ [None, "Find the area of a circle with radius 7 cm", "Grade 9–10 (Algebra II, Geometry, Trigonometry)"],
235
+ [None, "Differentiate f(x) = 3xΒ³ βˆ’ 2x + 7", "Grade 11–12 (Pre-Calculus, Statistics, Calculus)"],
236
+ ]
237
+
238
+ with gr.Blocks(css=CSS, title="Interactive Math Tutor") as demo:
239
+
240
+ gr.HTML("""
241
+ <div class="math-header">
242
+ <h1>πŸ“ Interactive Math Tutor</h1>
243
+ <p>Free Β· No sign-up Β· Step-by-step guidance from Grade 1 to High School</p>
244
+ <div class="badge-row">
245
+ <span class="badge">✏️ Arithmetic</span>
246
+ <span class="badge">Β½ Fractions</span>
247
+ <span class="badge">πŸ“Š Algebra</span>
248
+ <span class="badge">πŸ“ Geometry</span>
249
+ <span class="badge">πŸ“ˆ Trigonometry</span>
250
+ <span class="badge">∫ Calculus</span>
251
+ </div>
252
+ </div>
253
+ """)
254
+
255
+ with gr.Row(equal_height=False):
256
+ with gr.Column(scale=4, min_width=300):
257
+ gr.HTML('<div class="panel-label">πŸ“Ž Upload Worksheet (optional)</div>')
258
+ image_input = gr.Image(type="pil", label="", height=200)
259
+
260
+ gr.HTML('<div class="panel-label" style="margin-top:14px">✏️ Or Type Your Problem</div>')
261
  text_input = gr.Textbox(
262
+ label="", placeholder="e.g. Solve for x: 5x βˆ’ 3 = 22", lines=3
 
263
  )
264
+
265
+ gr.HTML('<div class="panel-label" style="margin-top:14px">πŸŽ“ Grade Level</div>')
266
+ grade_dropdown = gr.Dropdown(
267
+ choices=GRADE_CHOICES, value="Auto-detect from problem", label=""
268
+ )
269
+
270
+ with gr.Row():
271
+ solve_btn = gr.Button("πŸš€ Solve & Explain", elem_classes=["solve-btn"])
272
+ clear_btn = gr.Button("πŸ—‘ Clear", elem_classes=["clear-btn"])
273
+
274
+ gr.HTML("""
275
+ <div class="info-box">
276
+ <strong>βœ… 100% Free β€” no API key, no sign-up</strong><br>
277
+ Powered by open-source AI on HuggingFace servers.<br>
278
+ Share this page with anyone!<br><br>
279
+ <strong>⏳ First response</strong> may take ~20–30s if the model just woke up.
280
+ After that, answers stream in quickly.
281
+ </div>
282
+ """)
283
+
284
+ gr.HTML('<div class="panel-label" style="margin-top:16px">πŸ’‘ Try an example</div>')
285
+ gr.Examples(examples=EXAMPLES, inputs=[image_input, text_input, grade_dropdown], label="")
286
+
287
+ with gr.Column(scale=6, min_width=360):
288
+ gr.HTML('<div class="panel-label">πŸŽ“ Step-by-Step Solution</div>')
289
+ output_display = gr.Markdown(
290
+ value="*Your solution will appear here.\nUpload a worksheet or type a problem, then click **Solve & Explain**.*",
291
+ elem_classes=["output-box"],
292
+ )
293
+
294
+ solve_btn.click(
295
+ fn=solve_math,
296
+ inputs=[image_input, text_input, grade_dropdown],
297
+ outputs=output_display,
298
+ )
299
+ clear_btn.click(
300
+ fn=lambda: (None, "", "Auto-detect from problem",
301
+ "*Your solution will appear here.*"),
302
+ inputs=[],
303
+ outputs=[image_input, text_input, grade_dropdown, output_display],
304
  )
305
 
306
  if __name__ == "__main__":
307
+ demo.queue().launch(server_name="0.0.0.0", server_port=7860)