shukdevdattaEX commited on
Commit
6561dd2
·
verified ·
1 Parent(s): 470679c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +188 -569
app.py CHANGED
@@ -1,7 +1,5 @@
1
  import gradio as gr
2
  import os
3
- import re
4
- import time
5
  import base64
6
  from openai import OpenAI
7
  from together import Together
@@ -11,19 +9,12 @@ import markdown
11
  from datetime import datetime
12
  import tempfile
13
  import weasyprint
14
- from pathlib import Path
15
 
16
- # Function to convert markdown to HTML with styling
17
  def markdown_to_html(markdown_text, problem_text="", include_problem=True):
18
  """Convert markdown to styled HTML"""
19
-
20
- # Convert markdown to HTML
21
  html_content = markdown.markdown(markdown_text, extensions=['tables', 'fenced_code'])
22
-
23
- # Get current timestamp
24
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
25
-
26
- # Create styled HTML document
27
  styled_html = f"""
28
  <!DOCTYPE html>
29
  <html lang="en">
@@ -32,181 +23,34 @@ def markdown_to_html(markdown_text, problem_text="", include_problem=True):
32
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
  <title>Math Solution - Advanced Math Tutor</title>
34
  <style>
35
- body {{
36
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
37
- line-height: 1.6;
38
- color: #333;
39
- max-width: 800px;
40
- margin: 0 auto;
41
- padding: 20px;
42
- background-color: #f9f9f9;
43
- }}
44
- .container {{
45
- background-color: white;
46
- padding: 40px;
47
- border-radius: 10px;
48
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
49
- }}
50
- .header {{
51
- text-align: center;
52
- border-bottom: 3px solid #4CAF50;
53
- padding-bottom: 20px;
54
- margin-bottom: 30px;
55
- }}
56
- .header h1 {{
57
- color: #2c3e50;
58
- margin: 0;
59
- font-size: 2.5em;
60
- }}
61
- .header .subtitle {{
62
- color: #7f8c8d;
63
- font-style: italic;
64
- margin-top: 10px;
65
- }}
66
- .problem-section {{
67
- background-color: #e8f5e8;
68
- padding: 20px;
69
- border-radius: 8px;
70
- margin-bottom: 30px;
71
- border-left: 5px solid #4CAF50;
72
- }}
73
- .problem-section h2 {{
74
- color: #2c3e50;
75
- margin-top: 0;
76
- }}
77
- .solution-content {{
78
- background-color: #f8f9fa;
79
- padding: 25px;
80
- border-radius: 8px;
81
- border-left: 5px solid #007bff;
82
- }}
83
- h1, h2, h3, h4, h5, h6 {{
84
- color: #2c3e50;
85
- margin-top: 25px;
86
- margin-bottom: 15px;
87
- }}
88
- h2 {{
89
- border-bottom: 2px solid #eee;
90
- padding-bottom: 10px;
91
- }}
92
- code {{
93
- background-color: #f1f1f1;
94
- padding: 2px 6px;
95
- border-radius: 3px;
96
- font-family: 'Courier New', monospace;
97
- color: #d63384;
98
- }}
99
- pre {{
100
- background-color: #f8f8f8;
101
- padding: 15px;
102
- border-radius: 5px;
103
- overflow-x: auto;
104
- border: 1px solid #ddd;
105
- }}
106
- pre code {{
107
- background-color: transparent;
108
- padding: 0;
109
- color: inherit;
110
- }}
111
- .math-expression {{
112
- background-color: #fff3cd;
113
- padding: 10px;
114
- border-radius: 5px;
115
- border: 1px solid #ffeaa7;
116
- margin: 10px 0;
117
- font-family: 'Times New Roman', serif;
118
- font-size: 1.1em;
119
- }}
120
- .step {{
121
- margin: 20px 0;
122
- padding: 15px;
123
- background-color: #ffffff;
124
- border-radius: 8px;
125
- border: 1px solid #dee2e6;
126
- }}
127
- .final-answer {{
128
- background-color: #d4edda;
129
- border: 2px solid #4CAF50;
130
- padding: 20px;
131
- border-radius: 8px;
132
- margin-top: 30px;
133
- text-align: center;
134
- font-weight: bold;
135
- font-size: 1.2em;
136
- }}
137
- .timestamp {{
138
- text-align: right;
139
- color: #6c757d;
140
- font-size: 0.9em;
141
- margin-top: 30px;
142
- padding-top: 20px;
143
- border-top: 1px solid #eee;
144
- }}
145
- ul, ol {{
146
- padding-left: 25px;
147
- }}
148
- li {{
149
- margin: 8px 0;
150
- }}
151
- table {{
152
- border-collapse: collapse;
153
- width: 100%;
154
- margin: 20px 0;
155
- }}
156
- th, td {{
157
- border: 1px solid #ddd;
158
- padding: 12px;
159
- text-align: left;
160
- }}
161
- th {{
162
- background-color: #f8f9fa;
163
- font-weight: bold;
164
- }}
165
- .print-button {{
166
- background-color: #007bff;
167
- color: white;
168
- border: none;
169
- padding: 10px 20px;
170
- border-radius: 5px;
171
- cursor: pointer;
172
- font-size: 16px;
173
- margin: 10px 5px;
174
- display: inline-block;
175
- text-decoration: none;
176
- }}
177
- .print-button:hover {{
178
- background-color: #0056b3;
179
- }}
180
- .thinking {{
181
- font-style: italic;
182
- color: #007bff;
183
- font-size: 1.2em;
184
- text-align: center;
185
- padding: 20px;
186
- background-color: #e9ecef;
187
- border-radius: 8px;
188
- min-height: 100px;
189
- }}
190
- @media print {{
191
- body {{
192
- background-color: white;
193
- }}
194
- .container {{
195
- box-shadow: none;
196
- border: none;
197
- }}
198
- .print-button {{
199
- display: none;
200
- }}
201
- .thinking {{
202
- display: none;
203
- }}
204
- }}
205
  </style>
206
  <script>
207
- function printPage() {{
208
- window.print();
209
- }}
210
  </script>
211
  </head>
212
  <body>
@@ -215,21 +59,17 @@ def markdown_to_html(markdown_text, problem_text="", include_problem=True):
215
  <h1>📚 Advanced Math Tutor</h1>
216
  <div class="subtitle">Step-by-Step Mathematical Solution</div>
217
  </div>
218
-
219
  <button class="print-button" onclick="printPage()">🖨️ Print to PDF</button>
220
-
221
  {f'''
222
  <div class="problem-section">
223
  <h2>📝 Problem Statement</h2>
224
  <p><strong>{problem_text}</strong></p>
225
  </div>
226
  ''' if include_problem and problem_text.strip() else ''}
227
-
228
- <div class="solution-content" id="solution-content">
229
  <h2>🔍 Solution</h2>
230
  {html_content}
231
  </div>
232
-
233
  <div class="timestamp">
234
  Generated on: {timestamp}
235
  </div>
@@ -237,72 +77,42 @@ def markdown_to_html(markdown_text, problem_text="", include_problem=True):
237
  </body>
238
  </html>
239
  """
240
-
241
  return styled_html
242
 
243
- # Function to generate thinking animation HTML
244
- def thinking_animation_html():
245
- return """
246
- <div class="thinking" id="thinking-animation">
247
- Thinking<span id="dots"></span>
248
- <script>
249
- let dots = 0;
250
- const dotsElement = document.getElementById('dots');
251
- const interval = setInterval(() => {
252
- dots = (dots + 1) % 4;
253
- dotsElement.textContent = '.'.repeat(dots);
254
- }, 500);
255
- </script>
256
- </div>
257
- """
258
-
259
- # Function to save HTML to file
260
  def save_html_to_file(html_content, filename_prefix="math_solution"):
261
- """Save HTML content to a temporary file and return the file path"""
262
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
263
  filename = f"{filename_prefix}_{timestamp}.html"
264
-
265
- # Create a temporary file
266
  temp_dir = tempfile.gettempdir()
267
  file_path = os.path.join(temp_dir, filename)
268
-
269
  with open(file_path, 'w', encoding='utf-8') as f:
270
  f.write(html_content)
271
-
272
  return file_path
273
 
274
- # Function to convert HTML to PDF
275
  def html_to_pdf(html_content, filename_prefix="math_solution"):
276
- """Convert HTML content to PDF and return the file path"""
277
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
278
  filename = f"{filename_prefix}_{timestamp}.pdf"
279
-
280
- # Create a temporary file for PDF
281
  temp_dir = tempfile.gettempdir()
282
  pdf_path = os.path.join(temp_dir, filename)
283
-
284
  try:
285
- # Convert HTML to PDF using WeasyPrint
286
  weasyprint.HTML(string=html_content).write_pdf(pdf_path)
287
  return pdf_path
288
  except Exception as e:
289
  print(f"Error converting to PDF: {str(e)}")
290
  return None
291
 
292
- # Enhanced function to generate math solution using OpenRouter with HTML output
293
  def generate_math_solution_openrouter(api_key, problem_text, history=None):
294
  if not api_key.strip():
295
- return "Please enter your OpenRouter API key.", None, None, history
296
-
297
  if not problem_text.strip():
298
- return "Please enter a math problem.", None, None, history
299
-
 
300
  try:
301
- client = OpenAI(
302
- base_url="https://openrouter.ai/api/v1",
303
- api_key=api_key,
304
- )
305
-
306
  messages = [
307
  {"role": "system", "content":
308
  """You are an expert math tutor who explains concepts clearly and thoroughly.
@@ -311,177 +121,133 @@ def generate_math_solution_openrouter(api_key, problem_text, history=None):
311
  1. Show the mathematical operation
312
  2. Explain why this step is necessary
313
  3. Connect it to relevant mathematical concepts
314
-
315
  Format your response using markdown with clear section headers.
316
  Begin with an "Initial Analysis" section, follow with numbered steps,
317
  and conclude with a "Final Answer" section.
318
-
319
  Use proper markdown formatting including:
320
  - Headers (##, ###)
321
  - **Bold text** for important points
322
  - `Code blocks` for mathematical expressions
323
  - Lists and numbered steps
324
- - Tables if needed for comparisons or data"""},
325
  ]
326
-
327
- # Add conversation history if it exists
328
  if history:
329
  for exchange in history:
330
  messages.append({"role": "user", "content": exchange[0]})
331
  if len(exchange) > 1 and exchange[1]:
332
  messages.append({"role": "assistant", "content": exchange[1]})
333
-
334
- # Add the current problem
335
  messages.append({"role": "user", "content": f"Solve this math problem step-by-step: {problem_text}"})
336
-
337
- # Show thinking animation
338
- yield thinking_animation_html(), None, None, history
339
-
340
- # Create the completion
341
  completion = client.chat.completions.create(
342
  model="deepseek/deepseek-r1-0528:free",
343
  messages=messages,
344
- extra_headers={
345
- "HTTP-Referer": "https://advancedmathtutor.edu",
346
- "X-Title": "Advanced Math Tutor",
347
- }
348
  )
349
-
350
- markdown_solution = completion.choices[0].message.content
351
-
352
- # Convert to HTML
353
- html_solution = markdown_to_html(markdown_solution, problem_text)
354
-
355
- # Save HTML file
356
- html_file_path = save_html_to_file(html_solution, "openrouter_solution")
357
-
358
- # Convert to PDF
359
- pdf_file_path = html_to_pdf(html_solution, "openrouter_solution")
360
-
361
- # Update history
 
 
 
 
362
  if history is None:
363
  history = []
364
- history.append((problem_text, markdown_solution))
365
-
366
- yield html_solution, html_file_path, pdf_file_path, history
367
-
368
  except Exception as e:
369
- error_message = f"Error: {str(e)}"
370
- yield error_message, None, None, history
371
 
372
- # Enhanced function to generate math solution using Together AI with HTML output
373
  def generate_math_solution_together(api_key, problem_text, image_path=None, history=None):
374
  if not api_key.strip():
375
- return "Please enter your Together AI API key.", None, None, history
376
-
377
  if not problem_text.strip() and image_path is None:
378
- return "Please enter a math problem or upload an image of a math problem.", None, None, history
379
-
 
380
  try:
381
  client = Together(api_key=api_key)
382
-
383
- # Create the base message structure
384
  messages = [
385
- {
386
- "role": "system",
387
- "content": """You are an expert math tutor who explains concepts clearly and thoroughly.
388
- Analyze the given math problem and provide a detailed step-by-step solution.
389
- For each step:
390
- 1. Show the mathematical operation
391
- 2. Explain why this step is necessary
392
- 3. Connect it to relevant mathematical concepts
393
-
394
- Format your response using markdown with clear section headers.
395
- Begin with an "Initial Analysis" section, follow with numbered steps,
396
- and conclude with a "Final Answer" section.
397
-
398
- Use proper markdown formatting including:
399
- - Headers (##, ###)
400
- - **Bold text** for important points
401
- - `Code blocks` for mathematical expressions
402
- - Lists and numbered steps
403
- - Tables if needed for comparisons or data"""
404
- }
405
  ]
406
-
407
- # Add conversation history if it exists
408
  if history:
409
  for exchange in history:
410
  messages.append({"role": "user", "content": exchange[0]})
411
  if len(exchange) > 1 and exchange[1]:
412
  messages.append({"role": "assistant", "content": exchange[1]})
413
-
414
- # Prepare the user message content
415
  user_message_content = []
416
-
417
- # Add text content if provided
418
  if problem_text.strip():
419
- user_message_content.append({
420
- "type": "text",
421
- "text": f"Solve this math problem: {problem_text}"
422
- })
423
  else:
424
- user_message_content.append({
425
- "type": "text",
426
- "text": "Solve this math problem from the image:"
427
- })
428
-
429
- # Add image if provided
430
  if image_path:
431
- # Convert image to base64
432
  base64_image = image_to_base64(image_path)
433
  if base64_image:
434
- user_message_content.append({
435
- "type": "image_url",
436
- "image_url": {
437
- "url": f"data:image/jpeg;base64,{base64_image}"
438
- }
439
- })
440
-
441
- # Add the user message with content
442
- messages.append({
443
- "role": "user",
444
- "content": user_message_content
445
- })
446
-
447
- # Show thinking animation
448
- yield thinking_animation_html(), None, None, history
449
-
450
- # Create the completion
451
- response = client.chat.completions.create(
452
- model="meta-llama/Llama-Vision-Free",
453
- messages=messages,
454
- stream=False
455
- )
456
-
457
- markdown_solution = response.choices[0].message.content
458
-
459
- # Convert to HTML
460
- problem_display = problem_text if problem_text.strip() else "Image-based problem"
461
- html_solution = markdown_to_html(markdown_solution, problem_display)
462
-
463
- # Save HTML file
464
- html_file_path = save_html_to_file(html_solution, "together_solution")
465
-
466
- # Convert to PDF
467
- pdf_file_path = html_to_pdf(html_solution, "together_solution")
468
-
469
- # Update history
470
  if history is None:
471
  history = []
472
- history.append((problem_display, markdown_solution))
473
-
474
- yield html_solution, html_file_path, pdf_file_path, history
475
-
476
  except Exception as e:
477
- error_message = f"Error: {str(e)}"
478
- yield error_message, None, None, history
479
 
480
- # Function to convert image to base64
481
  def image_to_base64(image_path):
482
  if image_path is None:
483
  return None
484
-
485
  try:
486
  with open(image_path, "rb") as img_file:
487
  return base64.b64encode(img_file.read()).decode("utf-8")
@@ -489,269 +255,122 @@ def image_to_base64(image_path):
489
  print(f"Error converting image to base64: {str(e)}")
490
  return None
491
 
492
- # Define the Gradio interface
493
  def create_demo():
494
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
495
  gr.Markdown("# 📚 Advanced Math Tutor")
496
  gr.Markdown("""
497
  This application provides step-by-step solutions to math problems using advanced AI models.
498
- Solutions are generated in **HTML format** with download and print-to-PDF capabilities.
499
- Choose between OpenRouter's Phi-4-reasoning-plus for text-based problems or Together AI's
500
- Llama-Vision for problems with images. A thinking animation is shown while the solution is being generated.
501
  """)
502
-
503
- # Main tabs
504
  with gr.Tabs():
505
- # Text-based problem solver (OpenRouter)
506
  with gr.TabItem("Text Problem Solver (OpenRouter)"):
507
  with gr.Row():
508
  with gr.Column(scale=1):
509
- openrouter_api_key = gr.Textbox(
510
- label="OpenRouter API Key",
511
- placeholder="Enter your OpenRouter API key (starts with sk-or-)",
512
- type="password"
513
- )
514
- text_problem_input = gr.Textbox(
515
- label="Math Problem",
516
- placeholder="Enter your math problem here...",
517
- lines=5
518
- )
519
- example_problems = gr.Examples(
520
- examples=[
521
- ["Solve the quadratic equation: 3x² + 5x - 2 = 0"],
522
- ["Find the derivative of f(x) = x³ln(x)"],
523
- ["Calculate the area of a circle with radius 5 cm"],
524
- ["Find all values of x that satisfy the equation: log₂(x-1) + log₂(x+3) = 5"]
525
- ],
526
- inputs=[text_problem_input],
527
- label="Example Problems"
528
- )
529
  with gr.Row():
530
  openrouter_submit_btn = gr.Button("Solve Problem", variant="primary")
531
  openrouter_clear_btn = gr.Button("Clear")
532
-
533
- with gr.Column(scale=2):
534
- openrouter_solution_output = gr.HTML(label="Solution (HTML Format)")
535
-
536
- with gr.Row():
537
- openrouter_html_download = gr.File(
538
- label="📄 Download HTML Solution",
539
- visible=False
540
- )
541
- openrouter_pdf_download = gr.File(
542
- label="📄 Download PDF Solution",
543
- visible=False
544
- )
545
-
546
- # Store conversation history (invisible to user)
547
  openrouter_conversation_history = gr.State(value=None)
548
-
549
- # Button actions
550
  def handle_openrouter_submit(api_key, problem_text, history):
551
- for output in generate_math_solution_openrouter(api_key, problem_text, history):
552
- html_solution, html_file, pdf_file, updated_history = output
553
- yield (
554
- html_solution,
555
- updated_history,
556
- gr.update(value=html_file, visible=html_file is not None),
557
- gr.update(value=pdf_file, visible=pdf_file is not None)
558
- )
559
-
560
  openrouter_submit_btn.click(
561
  fn=handle_openrouter_submit,
562
  inputs=[openrouter_api_key, text_problem_input, openrouter_conversation_history],
563
- outputs=[
564
- openrouter_solution_output,
565
- openrouter_conversation_history,
566
- openrouter_html_download,
567
- openrouter_pdf_download
568
- ]
569
  )
570
-
571
  def clear_openrouter():
572
- return (
573
- "",
574
- None,
575
- gr.update(value=None, visible=False),
576
- gr.update(value=None, visible=False)
577
- )
578
-
579
- openrouter_clear_btn.click(
580
- fn=clear_openrouter,
581
- inputs=[],
582
- outputs=[
583
- openrouter_solution_output,
584
- openrouter_conversation_history,
585
- openrouter_html_download,
586
- openrouter_pdf_download
587
- ]
588
- )
589
-
590
- # Image-based problem solver (Together AI)
591
  with gr.TabItem("Image Problem Solver (Together AI)"):
592
  with gr.Row():
593
  with gr.Column(scale=1):
594
- together_api_key = gr.Textbox(
595
- label="Together AI API Key",
596
- placeholder="Enter your Together AI API key",
597
- type="password"
598
- )
599
- together_problem_input = gr.Textbox(
600
- label="Problem Description (Optional)",
601
- placeholder="Enter additional context for the image problem...",
602
- lines=3
603
- )
604
- together_image_input = gr.Image(
605
- label="Upload Math Problem Image",
606
- type="filepath"
607
- )
608
  with gr.Row():
609
  together_submit_btn = gr.Button("Solve Problem", variant="primary")
610
  together_clear_btn = gr.Button("Clear")
611
-
612
- with gr.Column(scale=2):
613
- together_solution_output = gr.HTML(label="Solution (HTML Format)")
614
-
615
- with gr.Row():
616
- together_html_download = gr.File(
617
- label="📄 Download HTML Solution",
618
- visible=False
619
- )
620
- together_pdf_download = gr.File(
621
- label="📄 Download PDF Solution",
622
- visible=False
623
- )
624
-
625
- # Store conversation history (invisible to user)
626
  together_conversation_history = gr.State(value=None)
627
-
628
- # Button actions
629
  def handle_together_submit(api_key, problem_text, image_path, history):
630
- for output in generate_math_solution_together(api_key, problem_text, image_path, history):
631
- html_solution, html_file, pdf_file, updated_history = output
632
- yield (
633
- html_solution,
634
- updated_history,
635
- gr.update(value=html_file, visible=html_file is not None),
636
- gr.update(value=pdf_file, visible=pdf_file is not None)
637
- )
638
-
639
  together_submit_btn.click(
640
  fn=handle_together_submit,
641
  inputs=[together_api_key, together_problem_input, together_image_input, together_conversation_history],
642
- outputs=[
643
- together_solution_output,
644
- together_conversation_history,
645
- together_html_download,
646
- together_pdf_download
647
- ]
648
  )
649
-
650
  def clear_together():
651
- return (
652
- "",
653
- None,
654
- gr.update(value=None, visible=False),
655
- gr.update(value=None, visible=False)
656
- )
657
-
658
- together_clear_btn.click(
659
- fn=clear_together,
660
- inputs=[],
661
- outputs=[
662
- together_solution_output,
663
- together_conversation_history,
664
- together_html_download,
665
- together_pdf_download
666
- ]
667
- )
668
-
669
- # Help tab
670
  with gr.TabItem("Help"):
671
  gr.Markdown("""
672
  ## How to Use the Advanced Math Tutor
673
-
674
- ### New Features 🎉
675
- - **Thinking Animation**: Displays a dynamic "Thinking..." animation while the solution is being generated
676
- - **HTML-formatted solutions**: All responses are generated in beautiful HTML format
677
- - **Download HTML**: Download the complete solution as an HTML file
678
- - **Download PDF**: Convert and download solutions as PDF files
679
- - **Print functionality**: Use the "Print to PDF" button in the HTML output to print directly
680
-
681
  ### Getting Started
682
-
683
- #### For Text-Based Problems (OpenRouter)
684
- 1. You'll need an API key from OpenRouter
685
- 2. Sign up at [OpenRouter](https://openrouter.ai/) to get your API key
686
- 3. Enter your API key in the designated field in the "Text Problem Solver" tab
687
-
688
- #### For Image-Based Problems (Together AI)
689
- 1. You'll need an API key from Together AI
690
- 2. Sign up at [Together AI](https://www.together.ai/) to get your API key
691
- 3. Enter your API key in the designated field in the "Image Problem Solver" tab
692
- 4. Upload an image of your math problem
693
- 5. Optionally add text to provide additional context
694
-
695
- ### Solving Math Problems
696
- - For text problems: Type or paste your math problem in the input field
697
- - For image problems: Upload a clear image of the math problem
698
- - Click "Solve Problem" to see a thinking animation followed by a detailed step-by-step solution in HTML format
699
- - Use the download buttons to save HTML or PDF versions
700
- - Click "Print to PDF" within the solution to print directly from your browser
701
-
702
- ### HTML Output Features
703
- - **Professional styling**: Clean, readable format with proper typography
704
- - **Mathematical expressions**: Highlighted math expressions and code blocks
705
- - **Step-by-step sections**: Clearly organized solution steps
706
- - **Print-friendly**: Optimized for printing and PDF conversion
707
- - **Timestamps**: Each solution includes generation timestamp
708
- - **Thinking Animation**: Dynamic dots animation while processing
709
-
710
- ### Tips for Best Results
711
- - Be specific in your problem description
712
- - Include all necessary information
713
- - For complex equations, use clear notation
714
- - For algebraic expressions, use ^ for exponents (e.g., x^2 for x²)
715
- - Use parentheses to group terms clearly
716
- - For images, ensure the math problem is clearly visible and well-lit
717
-
718
- ### Types of Problems You Can Solve
719
- - Algebra (equations, inequalities, systems of equations)
720
- - Calculus (derivatives, integrals, limits)
721
- - Trigonometry
722
- - Geometry
723
- - Statistics and Probability
724
- - Number Theory
725
- - And many more!
726
-
727
- ### Required Dependencies
728
- To run this application, you'll need to install:
729
- ```bash
730
- pip install gradio openai together pillow markdown weasyprint
731
- ```
732
  """)
733
-
734
- # Footer
735
  gr.Markdown("""
736
  ---
737
  ### About
738
- This enhanced application uses Microsoft's Phi-4-reasoning-plus model via OpenRouter for text-based problems
739
- and Llama-Vision-Free via Together AI for image-based problems.
740
-
741
- **New Features:**
742
- - Thinking animation during solution generation
743
- - HTML-formatted responses with professional styling
744
- - Download solutions as HTML files
745
- - Convert and download solutions as PDF files
746
- - Print-to-PDF functionality
747
- - Enhanced formatting with mathematical expressions highlighting
748
-
749
- Your API keys are required but not stored permanently.
750
  """)
751
-
752
  return demo
753
 
754
- # Launch the app
755
  if __name__ == "__main__":
756
  demo = create_demo()
757
  demo.launch()
 
1
  import gradio as gr
2
  import os
 
 
3
  import base64
4
  from openai import OpenAI
5
  from together import Together
 
9
  from datetime import datetime
10
  import tempfile
11
  import weasyprint
 
12
 
13
+ # Convert markdown to styled HTML (unchanged from your original)
14
  def markdown_to_html(markdown_text, problem_text="", include_problem=True):
15
  """Convert markdown to styled HTML"""
 
 
16
  html_content = markdown.markdown(markdown_text, extensions=['tables', 'fenced_code'])
 
 
17
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
 
18
  styled_html = f"""
19
  <!DOCTYPE html>
20
  <html lang="en">
 
23
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
24
  <title>Math Solution - Advanced Math Tutor</title>
25
  <style>
26
+ body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f9f9f9; }}
27
+ .container {{ background-color: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }}
28
+ .header {{ text-align: center; border-bottom: 3px solid #4CAF50; padding-bottom: 20px; margin-bottom: 30px; }}
29
+ .header h1 {{ color: #2c3e50; margin: 0; font-size: 2.5em; }}
30
+ .header .subtitle {{ color: #7f8c8d; font-style: italic; margin-top: 10px; }}
31
+ .problem-section {{ background-color: #e8f5e8; padding: 20px; border-radius: 8px; margin-bottom: 30px; border-left: 5px solid #4CAF50; }}
32
+ .problem-section h2 {{ color: #2c3e50; margin-top: 0; }}
33
+ .solution-content {{ background-color: #f8f9fa; padding: 25px; border-radius: 8px; border-left: 5px solid #007bff; }}
34
+ h1, h2, h3 {{ color: #2c3e50; margin-top: 25px; margin-bottom: 15px; }}
35
+ h2 {{ border-bottom: 2px solid #eee; padding-bottom: 10px; }}
36
+ code {{ background-color: #f1f1f1; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #d63384; }}
37
+ pre {{ background-color: #f8f8f8; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #ddd; }}
38
+ pre code {{ background-color: transparent; padding: 0; color: inherit; }}
39
+ .math-expression {{ background-color: #fff3cd; padding: 10px; border-radius: 5px; border: 1px solid #ffeaa7; margin: 10px 0; font-family: 'Times New Roman', serif; font-size: 1.1em; }}
40
+ .step {{ margin: 20px 0; padding: 15px; background-color: #ffffff; border-radius: 8px; border: 1px solid #dee2e6; }}
41
+ .final-answer {{ background-color: #d4edda; border: 2px solid #4CAF50; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: center; font-weight: bold; font-size: 1.2em; }}
42
+ .timestamp {{ text-align: right; color: #6c757d; font-size: 0.9em; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }}
43
+ ul, ol {{ padding-left: 25px; }}
44
+ li {{ margin: 8px 0; }}
45
+ table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
46
+ th, td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }}
47
+ th {{ background-color: #f8f9fa; font-weight: bold; }}
48
+ .print-button {{ background-color: #007bff; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-size: 16px; margin: 10px 5px; display: inline-block; text-decoration: none; }}
49
+ .print-button:hover {{ background-color: #0056b3; }}
50
+ @media print {{ body {{ background-color: white; }} .container {{ box-shadow: none; border: none; }} .print-button {{ display: none; }} }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  </style>
52
  <script>
53
+ function printPage() {{ window.print(); }}
 
 
54
  </script>
55
  </head>
56
  <body>
 
59
  <h1>📚 Advanced Math Tutor</h1>
60
  <div class="subtitle">Step-by-Step Mathematical Solution</div>
61
  </div>
 
62
  <button class="print-button" onclick="printPage()">🖨️ Print to PDF</button>
 
63
  {f'''
64
  <div class="problem-section">
65
  <h2>📝 Problem Statement</h2>
66
  <p><strong>{problem_text}</strong></p>
67
  </div>
68
  ''' if include_problem and problem_text.strip() else ''}
69
+ <div class="solution-content">
 
70
  <h2>🔍 Solution</h2>
71
  {html_content}
72
  </div>
 
73
  <div class="timestamp">
74
  Generated on: {timestamp}
75
  </div>
 
77
  </body>
78
  </html>
79
  """
 
80
  return styled_html
81
 
82
+ # Save HTML to a temporary file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  def save_html_to_file(html_content, filename_prefix="math_solution"):
 
84
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
85
  filename = f"{filename_prefix}_{timestamp}.html"
 
 
86
  temp_dir = tempfile.gettempdir()
87
  file_path = os.path.join(temp_dir, filename)
 
88
  with open(file_path, 'w', encoding='utf-8') as f:
89
  f.write(html_content)
 
90
  return file_path
91
 
92
+ # Convert HTML to PDF
93
  def html_to_pdf(html_content, filename_prefix="math_solution"):
 
94
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
95
  filename = f"{filename_prefix}_{timestamp}.pdf"
 
 
96
  temp_dir = tempfile.gettempdir()
97
  pdf_path = os.path.join(temp_dir, filename)
 
98
  try:
 
99
  weasyprint.HTML(string=html_content).write_pdf(pdf_path)
100
  return pdf_path
101
  except Exception as e:
102
  print(f"Error converting to PDF: {str(e)}")
103
  return None
104
 
105
+ # OpenRouter solution generator with streaming "Thinking" process
106
  def generate_math_solution_openrouter(api_key, problem_text, history=None):
107
  if not api_key.strip():
108
+ yield "Please enter your OpenRouter API key.", None, None, history
109
+ return
110
  if not problem_text.strip():
111
+ yield "Please enter a math problem.", None, None, history
112
+ return
113
+
114
  try:
115
+ client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=api_key)
 
 
 
 
116
  messages = [
117
  {"role": "system", "content":
118
  """You are an expert math tutor who explains concepts clearly and thoroughly.
 
121
  1. Show the mathematical operation
122
  2. Explain why this step is necessary
123
  3. Connect it to relevant mathematical concepts
 
124
  Format your response using markdown with clear section headers.
125
  Begin with an "Initial Analysis" section, follow with numbered steps,
126
  and conclude with a "Final Answer" section.
 
127
  Use proper markdown formatting including:
128
  - Headers (##, ###)
129
  - **Bold text** for important points
130
  - `Code blocks` for mathematical expressions
131
  - Lists and numbered steps
132
+ - Tables if needed for comparisons or data"""}
133
  ]
 
 
134
  if history:
135
  for exchange in history:
136
  messages.append({"role": "user", "content": exchange[0]})
137
  if len(exchange) > 1 and exchange[1]:
138
  messages.append({"role": "assistant", "content": exchange[1]})
 
 
139
  messages.append({"role": "user", "content": f"Solve this math problem step-by-step: {problem_text}"})
140
+
141
+ # Start streaming
 
 
 
142
  completion = client.chat.completions.create(
143
  model="deepseek/deepseek-r1-0528:free",
144
  messages=messages,
145
+ stream=True,
146
+ extra_headers={"HTTP-Referer": "https://advancedmathtutor.edu", "X-Title": "Advanced Math Tutor"}
 
 
147
  )
148
+
149
+ # Show "Thinking..." initially
150
+ initial_html = markdown_to_html("**Thinking...**", problem_text, include_problem=True)
151
+ yield initial_html, None, None, history
152
+
153
+ # Accumulate and yield partial solutions
154
+ accumulated_markdown = ""
155
+ for chunk in completion:
156
+ if chunk.choices[0].delta.content:
157
+ accumulated_markdown += chunk.choices[0].delta.content
158
+ html_solution = markdown_to_html(accumulated_markdown, problem_text, include_problem=True)
159
+ yield html_solution, None, None, history
160
+
161
+ # Final output with files
162
+ final_html = markdown_to_html(accumulated_markdown, problem_text, include_problem=True)
163
+ html_file_path = save_html_to_file(final_html, "openrouter_solution")
164
+ pdf_file_path = html_to_pdf(final_html, "openrouter_solution")
165
  if history is None:
166
  history = []
167
+ history.append((problem_text, accumulated_markdown))
168
+ yield final_html, html_file_path, pdf_file_path, history
169
+
 
170
  except Exception as e:
171
+ yield f"Error: {str(e)}", None, None, history
 
172
 
173
+ # Together AI solution generator with streaming "Thinking" process
174
  def generate_math_solution_together(api_key, problem_text, image_path=None, history=None):
175
  if not api_key.strip():
176
+ yield "Please enter your Together AI API key.", None, None, history
177
+ return
178
  if not problem_text.strip() and image_path is None:
179
+ yield "Please enter a math problem or upload an image.", None, None, history
180
+ return
181
+
182
  try:
183
  client = Together(api_key=api_key)
 
 
184
  messages = [
185
+ {"role": "system", "content":
186
+ """You are an expert math tutor who explains concepts clearly and thoroughly.
187
+ Analyze the given math problem and provide a detailed step-by-step solution.
188
+ For each step:
189
+ 1. Show the mathematical operation
190
+ 2. Explain why this step is necessary
191
+ 3. Connect it to relevant mathematical concepts
192
+ Format your response using markdown with clear section headers.
193
+ Begin with an "Initial Analysis" section, follow with numbered steps,
194
+ and conclude with a "Final Answer" section.
195
+ Use proper markdown formatting including:
196
+ - Headers (##, ###)
197
+ - **Bold text** for important points
198
+ - `Code blocks` for mathematical expressions
199
+ - Lists and numbered steps
200
+ - Tables if needed for comparisons or data"""}
 
 
 
 
201
  ]
 
 
202
  if history:
203
  for exchange in history:
204
  messages.append({"role": "user", "content": exchange[0]})
205
  if len(exchange) > 1 and exchange[1]:
206
  messages.append({"role": "assistant", "content": exchange[1]})
207
+
 
208
  user_message_content = []
209
+ problem_display = problem_text if problem_text.strip() else "Image-based problem"
 
210
  if problem_text.strip():
211
+ user_message_content.append({"type": "text", "text": f"Solve this math problem: {problem_text}"})
 
 
 
212
  else:
213
+ user_message_content.append({"type": "text", "text": "Solve this math problem from the image:"})
 
 
 
 
 
214
  if image_path:
 
215
  base64_image = image_to_base64(image_path)
216
  if base64_image:
217
+ user_message_content.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}})
218
+ messages.append({"role": "user", "content": user_message_content})
219
+
220
+ # Start streaming
221
+ response = client.chat.completions.create(model="meta-llama/Llama-Vision-Free", messages=messages, stream=True)
222
+
223
+ # Show "Thinking..." initially
224
+ initial_html = markdown_to_html("**Thinking...**", problem_display, include_problem=True)
225
+ yield initial_html, None, None, history
226
+
227
+ # Accumulate and yield partial solutions
228
+ accumulated_markdown = ""
229
+ for chunk in response:
230
+ if chunk.choices[0].delta.content:
231
+ accumulated_markdown += chunk.choices[0].delta.content
232
+ html_solution = markdown_to_html(accumulated_markdown, problem_display, include_problem=True)
233
+ yield html_solution, None, None, history
234
+
235
+ # Final output with files
236
+ final_html = markdown_to_html(accumulated_markdown, problem_display, include_problem=True)
237
+ html_file_path = save_html_to_file(final_html, "together_solution")
238
+ pdf_file_path = html_to_pdf(final_html, "together_solution")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  if history is None:
240
  history = []
241
+ history.append((problem_display, accumulated_markdown))
242
+ yield final_html, html_file_path, pdf_file_path, history
243
+
 
244
  except Exception as e:
245
+ yield f"Error: {str(e)}", None, None, history
 
246
 
247
+ # Convert image to base64 for Together AI
248
  def image_to_base64(image_path):
249
  if image_path is None:
250
  return None
 
251
  try:
252
  with open(image_path, "rb") as img_file:
253
  return base64.b64encode(img_file.read()).decode("utf-8")
 
255
  print(f"Error converting image to base64: {str(e)}")
256
  return None
257
 
258
+ # Gradio interface
259
  def create_demo():
260
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
261
  gr.Markdown("# 📚 Advanced Math Tutor")
262
  gr.Markdown("""
263
  This application provides step-by-step solutions to math problems using advanced AI models.
264
+ Watch the **Thinking** process unfold in real-time before the final solution is presented!
265
+ Solutions are in **HTML format** with download and print-to-PDF options.
 
266
  """)
267
+
 
268
  with gr.Tabs():
269
+ # OpenRouter Tab
270
  with gr.TabItem("Text Problem Solver (OpenRouter)"):
271
  with gr.Row():
272
  with gr.Column(scale=1):
273
+ openrouter_api_key = gr.Textbox(label="OpenRouter API Key", placeholder="Enter your OpenRouter API key (starts with sk-or-)", type="password")
274
+ text_problem_input = gr.Textbox(label="Math Problem", placeholder="Enter your math problem here...", lines=5)
275
+ gr.Examples(examples=[
276
+ ["Solve the quadratic equation: 3x² + 5x - 2 = 0"],
277
+ ["Calculate the area of a circle with radius 6 meters"]
278
+ ], inputs=[text_problem_input], label="Example Problems")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  with gr.Row():
280
  openrouter_submit_btn = gr.Button("Solve Problem", variant="primary")
281
  openrouter_clear_btn = gr.Button("Clear")
282
+ with gr.Column(scale=2):
283
+ openrouter_solution_output = gr.HTML(label="Solution (HTML Format)")
284
+ with gr.Row():
285
+ openrouter_html_download = gr.File(label="📄 Download HTML Solution", visible=False)
286
+ openrouter_pdf_download = gr.File(label="📄 Download PDF Solution", visible=False)
 
 
 
 
 
 
 
 
 
 
287
  openrouter_conversation_history = gr.State(value=None)
288
+
 
289
  def handle_openrouter_submit(api_key, problem_text, history):
290
+ for html_solution, html_file, pdf_file, updated_history in generate_math_solution_openrouter(api_key, problem_text, history):
291
+ yield html_solution, html_file, pdf_file, updated_history
292
+
 
 
 
 
 
 
293
  openrouter_submit_btn.click(
294
  fn=handle_openrouter_submit,
295
  inputs=[openrouter_api_key, text_problem_input, openrouter_conversation_history],
296
+ outputs=[openrouter_solution_output, openrouter_html_download, openrouter_pdf_download, openrouter_conversation_history]
 
 
 
 
 
297
  )
298
+
299
  def clear_openrouter():
300
+ return "", None, None, None
301
+ openrouter_clear_btn.click(fn=clear_openrouter, outputs=[openrouter_solution_output, openrouter_html_download, openrouter_pdf_download, openrouter_conversation_history])
302
+
303
+ # Together AI Tab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  with gr.TabItem("Image Problem Solver (Together AI)"):
305
  with gr.Row():
306
  with gr.Column(scale=1):
307
+ together_api_key = gr.Textbox(label="Together AI API Key", placeholder="Enter your Together AI API key", type="password")
308
+ together_problem_input = gr.Textbox(label="Problem Description (Optional)", placeholder="Enter additional context...", lines=3)
309
+ together_image_input = gr.Image(label="Upload Math Problem Image", type="filepath")
 
 
 
 
 
 
 
 
 
 
 
310
  with gr.Row():
311
  together_submit_btn = gr.Button("Solve Problem", variant="primary")
312
  together_clear_btn = gr.Button("Clear")
313
+ with gr.Column(scale=2):
314
+ together_solution_output = gr.HTML(label="Solution (HTML Format)")
315
+ with gr.Row():
316
+ together_html_download = gr.File(label="📄 Download HTML Solution", visible=False)
317
+ together_pdf_download = gr.File(label="📄 Download PDF Solution", visible=False)
 
 
 
 
 
 
 
 
 
 
318
  together_conversation_history = gr.State(value=None)
319
+
 
320
  def handle_together_submit(api_key, problem_text, image_path, history):
321
+ for html_solution, html_file, pdf_file, updated_history in generate_math_solution_together(api_key, problem_text, image_path, history):
322
+ yield html_solution, html_file, pdf_file, updated_history
323
+
 
 
 
 
 
 
324
  together_submit_btn.click(
325
  fn=handle_together_submit,
326
  inputs=[together_api_key, together_problem_input, together_image_input, together_conversation_history],
327
+ outputs=[together_solution_output, together_html_download, together_pdf_download, together_conversation_history]
 
 
 
 
 
328
  )
329
+
330
  def clear_together():
331
+ return "", None, None, None
332
+ together_clear_btn.click(fn=clear_together, outputs=[together_solution_output, together_html_download, together_pdf_download, together_conversation_history])
333
+
334
+ # Help Tab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
335
  with gr.TabItem("Help"):
336
  gr.Markdown("""
337
  ## How to Use the Advanced Math Tutor
338
+
339
+ ### New Feature: Dynamic Thinking Process 🎉
340
+ - Watch the AI’s **Thinking** process unfold step-by-step in real-time, just like DeepSeek’s "deepThink r1"!
341
+ - Solutions are still in **HTML format** with download and print-to-PDF options.
342
+
 
 
 
343
  ### Getting Started
344
+ #### Text Problems (OpenRouter)
345
+ 1. Get an API key from [OpenRouter](https://openrouter.ai/).
346
+ 2. Enter it in the "Text Problem Solver" tab.
347
+ 3. Type your math problem and hit "Solve Problem".
348
+
349
+ #### Image Problems (Together AI)
350
+ 1. Get an API key from [Together AI](https://www.together.ai/).
351
+ 2. Enter it in the "Image Problem Solver" tab.
352
+ 3. Upload an image or add text context, then click "Solve Problem".
353
+
354
+ ### What You’ll See
355
+ - **Thinking Process**: Starts with "Thinking..." and builds the solution step-by-step (e.g., formula, calculations).
356
+ - **Final Output**: Full HTML solution with downloadable HTML and PDF files.
357
+
358
+ ### Tips
359
+ - Use clear problem statements (e.g., "Calculate the area of a circle with radius 6 meters").
360
+ - For images, ensure good quality.
361
+ - Install dependencies: `pip install gradio openai together pillow markdown weasyprint`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  """)
363
+
 
364
  gr.Markdown("""
365
  ---
366
  ### About
367
+ Powered by OpenRouter’s Phi-4-reasoning-plus and Together AI’s Llama-Vision-Free.
368
+ Now with a live "Thinking" display!
 
 
 
 
 
 
 
 
 
 
369
  """)
370
+
371
  return demo
372
 
373
+ # Launch it
374
  if __name__ == "__main__":
375
  demo = create_demo()
376
  demo.launch()