shukdevdattaEX commited on
Commit
456a417
·
verified ·
1 Parent(s): 48c36f4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -632
app.py CHANGED
@@ -1,632 +0,0 @@
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
8
- from PIL import Image
9
- import io
10
- import markdown
11
- from datetime import datetime
12
- import tempfile
13
- from playwright.sync_api import sync_playwright
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
- # Convert markdown to HTML
20
- html_content = markdown.markdown(markdown_text, extensions=['tables', 'fenced_code'])
21
- # Get current timestamp
22
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
23
- # Prepare problem section
24
- if include_problem and problem_text.strip():
25
- problem_section = f'''<div class="problem-section">
26
- <h2>📝 Problem Statement</h2>
27
- <p><strong>{problem_text}</strong></p>
28
- </div>'''
29
- else:
30
- problem_section = ''
31
- # Create styled HTML document
32
- styled_html = f"""<!DOCTYPE html>
33
- <html lang="en">
34
- <head>
35
- <meta charset="UTF-8">
36
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
37
- <title>Math Solution - Advanced Math Tutor</title>
38
- <style>
39
- body {{
40
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
41
- line-height: 1.6;
42
- color: #333;
43
- max-width: 800px;
44
- margin: 0 auto;
45
- padding: 20px;
46
- background-color: #f9f9f9;
47
- }}
48
- .container {{
49
- background-color: white;
50
- padding: 40px;
51
- border-radius: 10px;
52
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
53
- }}
54
- .header {{
55
- text-align: center;
56
- border-bottom: 3px solid #4CAF50;
57
- padding-bottom: 20px;
58
- margin-bottom: 30px;
59
- }}
60
- .header h1 {{
61
- color: #2c3e50;
62
- margin: 0;
63
- font-size: 2.5em;
64
- }}
65
- .header .subtitle {{
66
- color: #7f8c8d;
67
- font-style: italic;
68
- margin-top: 10px;
69
- }}
70
- .problem-section {{
71
- background-color: #e8f5e8;
72
- padding: 20px;
73
- border-radius: 8px;
74
- margin-bottom: 30px;
75
- border-left: 5px solid #4CAF50;
76
- }}
77
- .problem-section h2 {{
78
- color: #2c3e50;
79
- margin-top: 0;
80
- }}
81
- .solution-content {{
82
- background-color: #f8f9fa;
83
- padding: 25px;
84
- border-radius: 8px;
85
- border-left: 5px solid #007bff;
86
- }}
87
- h1, h2, h3, h4, h5, h6 {{
88
- color: #2c3e50;
89
- margin-top: 25px;
90
- margin-bottom: 15px;
91
- }}
92
- h2 {{
93
- border-bottom: 2px solid #eee;
94
- padding-bottom: 10px;
95
- }}
96
- code {{
97
- background-color: #f1f1f1;
98
- padding: 2px 6px;
99
- border-radius: 3px;
100
- font-family: 'Courier New', monospace;
101
- color: #d63384;
102
- }}
103
- pre {{
104
- background-color: #f8f8f8;
105
- padding: 15px;
106
- border-radius: 5px;
107
- overflow-x: auto;
108
- border: 1px solid #ddd;
109
- }}
110
- pre code {{
111
- background-color: transparent;
112
- padding: 0;
113
- color: inherit;
114
- }}
115
- .math-expression {{
116
- background-color: #fff3cd;
117
- padding: 10px;
118
- border-radius: 5px;
119
- border: 1px solid #ffeaa7;
120
- margin: 10px 0;
121
- font-family: 'Times New Roman', serif;
122
- font-size: 1.1em;
123
- }}
124
- .step {{
125
- margin: 20px 0;
126
- padding: 15px;
127
- background-color: #ffffff;
128
- border-radius: 8px;
129
- border: 1px solid #dee2e6;
130
- }}
131
- .final-answer {{
132
- background-color: #d4edda;
133
- border: 2px solid #4CAF50;
134
- padding: 20px;
135
- border-radius: 8px;
136
- margin-top: 30px;
137
- text-align: center;
138
- font-weight: bold;
139
- font-size: 1.2em;
140
- }}
141
- .timestamp {{
142
- text-align: right;
143
- color: #6c757d;
144
- font-size: 0.9em;
145
- margin-top: 30px;
146
- padding-top: 20px;
147
- border-top: 1px solid #eee;
148
- }}
149
- ul, ol {{
150
- padding-left: 25px;
151
- }}
152
- li {{
153
- margin: 8px 0;
154
- }}
155
- table {{
156
- border-collapse: collapse;
157
- width: 100%;
158
- margin: 20px 0;
159
- }}
160
- th, td {{
161
- border: 1px solid #ddd;
162
- padding: 12px;
163
- text-align: left;
164
- }}
165
- th {{
166
- background-color: #f8f9fa;
167
- font-weight: bold;
168
- }}
169
- .print-button {{
170
- background-color: #007bff;
171
- color: white;
172
- border: none;
173
- padding: 10px 20px;
174
- border-radius: 5px;
175
- cursor: pointer;
176
- font-size: 16px;
177
- margin: 10px 5px;
178
- display: inline-block;
179
- text-decoration: none;
180
- }}
181
- .print-button:hover {{
182
- background-color: #0056b3;
183
- }}
184
- @media print {{
185
- body {{
186
- background-color: white;
187
- }}
188
- .container {{
189
- box-shadow: none;
190
- border: none;
191
- }}
192
- .print-button {{
193
- display: none;
194
- }}
195
- }}
196
- </style>
197
- <script>
198
- function printPage() {{
199
- window.print();
200
- }}
201
- </script>
202
- <!-- MathJax Configuration -->
203
- <script>
204
- window.MathJax = {{
205
- tex: {{
206
- inlineMath: [['$', '$'], ['\\\\(', '\\\\)']],
207
- displayMath: [['$$', '$$'], ['\\\\[', '\\\\]']],
208
- processEscapes: true,
209
- tags: 'ams',
210
- macros: {{
211
- boxed: ['\\\\fbox{\\\\,#1}', 1]
212
- }}
213
- }},
214
- options: {{
215
- skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
216
- ignoreHtmlClass: 'tex2jax_ignore',
217
- processHtmlClass: 'tex2jax_process'
218
- }},
219
- loader: {{load: ['[tex]/ams', '[tex]/bbox']}},
220
- startup: {{
221
- ready: () => {{
222
- MathJax.startup.defaultReady();
223
- MathJax.typesetPromise();
224
- }}
225
- }}
226
- }};
227
- </script>
228
- <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
229
- </head>
230
- <body>
231
- <div class="container">
232
- <div class="header">
233
- <h1>📚 Advanced Math Tutor</h1>
234
- <div class="subtitle">Step-by-Step Mathematical Solution</div>
235
- </div>
236
- <button class="print-button" onclick="printPage()">🖨️ Print to PDF</button>
237
- {problem_section}
238
- <div class="solution-content">
239
- <h2>🔍 Solution</h2>
240
- {html_content}
241
- </div>
242
- <div class="timestamp">Generated on: {timestamp}</div>
243
- </div>
244
- </body>
245
- </html>"""
246
- return styled_html
247
-
248
- # Function to save HTML to file
249
- def save_html_to_file(html_content, filename_prefix="math_solution"):
250
- """Save HTML content to a temporary file and return the file path"""
251
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
252
- filename = f"{filename_prefix}_{timestamp}.html"
253
- # Create a temporary file
254
- temp_dir = tempfile.gettempdir()
255
- file_path = os.path.join(temp_dir, filename)
256
- with open(file_path, 'w', encoding='utf-8') as f:
257
- f.write(html_content)
258
- return file_path
259
-
260
- # Function to convert HTML to PDF using Playwright
261
- def html_to_pdf(html_content, filename_prefix="math_solution"):
262
- """Convert HTML content to PDF using Playwright for MathJax rendering and return the file path"""
263
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
264
- filename = f"{filename_prefix}_{timestamp}.pdf"
265
- # Create a temporary file for HTML
266
- temp_dir = tempfile.gettempdir()
267
- html_path = os.path.join(temp_dir, f"{filename_prefix}_{timestamp}.html")
268
- with open(html_path, 'w', encoding='utf-8') as f:
269
- f.write(html_content)
270
- pdf_path = os.path.join(temp_dir, filename)
271
- try:
272
- with sync_playwright() as p:
273
- browser = p.chromium.launch(headless=True)
274
- page = browser.new_page()
275
- page.goto(f"file://{os.path.abspath(html_path)}")
276
- # Wait for MathJax to fully render
277
- page.wait_for_function(
278
- "MathJax.tex2chtmlPromise ? MathJax.typesetPromise().then(() => true) : true",
279
- timeout=30000
280
- )
281
- page.pdf(path=pdf_path, format='A4', print_background=True)
282
- browser.close()
283
- # Clean up HTML file
284
- os.remove(html_path)
285
- return pdf_path
286
- except Exception as e:
287
- print(f"Error converting to PDF: {str(e)}")
288
- if os.path.exists(html_path):
289
- os.remove(html_path)
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
- if not problem_text.strip():
297
- return "Please enter a math problem.", None, None, history
298
- try:
299
- client = OpenAI(
300
- base_url="https://openrouter.ai/api/v1",
301
- api_key=api_key,
302
- )
303
- messages = [
304
- {
305
- "role": "system",
306
- "content": """You are an expert math tutor who explains concepts clearly and thoroughly. Analyze the given math problem and provide a detailed step-by-step solution. For each step:
307
- 1. Show the mathematical operation
308
- 2. Explain why this step is necessary
309
- 3. Connect it to relevant mathematical concepts
310
- Format your response using markdown with clear section headers. Begin with an "Initial Analysis" section, follow with numbered steps, and conclude with a "Final Answer" section. Use proper markdown formatting including:
311
- - Headers (##, ###)
312
- - **Bold text** for important points
313
- - Use LaTeX math delimiters: $...$ for inline math, $$...$$ for display math. Use LaTeX syntax inside (e.g., $$A = \\pi r^2$$, $\\boxed{25\\pi}$).
314
- - Lists and numbered steps
315
- - Tables if needed for comparisons or data
316
- Do not use [ ] or code blocks for math expressions; use math delimiters instead."""
317
- },
318
- ]
319
- # Add conversation history if it exists
320
- if history:
321
- for exchange in history:
322
- messages.append({"role": "user", "content": exchange[0]})
323
- if len(exchange) > 1 and exchange[1]:
324
- # Check if there's a response
325
- messages.append({"role": "assistant", "content": exchange[1]})
326
- # Add the current problem
327
- messages.append({"role": "user", "content": f"Solve this math problem step-by-step: {problem_text}"})
328
- # Create the completion
329
- completion = client.chat.completions.create(
330
- model="deepseek/deepseek-r1-0528:free",
331
- messages=messages,
332
- extra_headers={
333
- "HTTP-Referer": "https://advancedmathtutor.edu",
334
- "X-Title": "Advanced Math Tutor",
335
- }
336
- )
337
- markdown_solution = completion.choices[0].message.content
338
- # Convert to HTML
339
- html_solution = markdown_to_html(markdown_solution, problem_text)
340
- # Save HTML file
341
- html_file_path = save_html_to_file(html_solution, "openrouter_solution")
342
- # Convert to PDF
343
- pdf_file_path = html_to_pdf(html_solution, "openrouter_solution")
344
- # Update history
345
- if history is None:
346
- history = []
347
- history.append((problem_text, markdown_solution))
348
- return html_solution, html_file_path, pdf_file_path, history
349
- except Exception as e:
350
- error_message = f"Error: {str(e)}"
351
- return error_message, None, None, history
352
-
353
- # Enhanced function to generate math solution using Together AI with HTML output
354
- def generate_math_solution_together(api_key, problem_text, image_path=None, history=None):
355
- if not api_key.strip():
356
- return "Please enter your Together AI API key.", None, None, history
357
- if not problem_text.strip() and image_path is None:
358
- return "Please enter a math problem or upload an image of a math problem.", None, None, history
359
- try:
360
- client = Together(api_key=api_key)
361
- # Create the base message structure
362
- messages = [
363
- {
364
- "role": "system",
365
- "content": """You are an expert math tutor who explains concepts clearly and thoroughly. Analyze the given math problem and provide a detailed step-by-step solution. For each step:
366
- 1. Show the mathematical operation
367
- 2. Explain why this step is necessary
368
- 3. Connect it to relevant mathematical concepts
369
- Format your response using markdown with clear section headers. Begin with an "Initial Analysis" section, follow with numbered steps, and conclude with a "Final Answer" section. Use proper markdown formatting including:
370
- - Headers (##, ###)
371
- - **Bold text** for important points
372
- - Use LaTeX math delimiters: $...$ for inline math, $$...$$ for display math. Use LaTeX syntax inside (e.g., $$A = \\pi r^2$$, $\\boxed{25\\pi}$).
373
- - Lists and numbered steps
374
- - Tables if needed for comparisons or data
375
- Do not use [ ] or code blocks for math expressions; use math delimiters instead."""
376
- }
377
- ]
378
- # Add conversation history if it exists
379
- if history:
380
- for exchange in history:
381
- messages.append({"role": "user", "content": exchange[0]})
382
- if len(exchange) > 1 and exchange[1]:
383
- # Check if there's a response
384
- messages.append({"role": "assistant", "content": exchange[1]})
385
- # Prepare the user message content
386
- user_message_content = []
387
- # Add text content if provided
388
- if problem_text.strip():
389
- user_message_content.append(
390
- {
391
- "type": "text",
392
- "text": f"Solve this math problem: {problem_text}"
393
- }
394
- )
395
- else:
396
- user_message_content.append(
397
- {
398
- "type": "text",
399
- "text": "Solve this math problem from the image:"
400
- }
401
- )
402
- # Add image if provided
403
- if image_path:
404
- # Convert image to base64
405
- base64_image = image_to_base64(image_path)
406
- if base64_image:
407
- user_message_content.append(
408
- {
409
- "type": "image_url",
410
- "image_url": {
411
- "url": f"data:image/jpeg;base64,{base64_image}"
412
- }
413
- }
414
- )
415
- # Add the user message with content
416
- messages.append(
417
- {
418
- "role": "user",
419
- "content": user_message_content
420
- }
421
- )
422
- # Create the completion
423
- response = client.chat.completions.create(
424
- model="ServiceNow-AI/Apriel-1.5-15b-Thinker",
425
- messages=messages,
426
- stream=False
427
- )
428
- markdown_solution = response.choices[0].message.content
429
- # Convert to HTML
430
- problem_display = problem_text if problem_text.strip() else "Image-based problem"
431
- html_solution = markdown_to_html(markdown_solution, problem_display)
432
- # Save HTML file
433
- html_file_path = save_html_to_file(html_solution, "together_solution")
434
- # Convert to PDF
435
- pdf_file_path = html_to_pdf(html_solution, "together_solution")
436
- # Update history - for simplicity, just store the text problem
437
- if history is None:
438
- history = []
439
- history.append((problem_display, markdown_solution))
440
- return html_solution, html_file_path, pdf_file_path, history
441
- except Exception as e:
442
- error_message = f"Error: {str(e)}"
443
- return error_message, None, None, history
444
-
445
- # Function to convert image to base64
446
- def image_to_base64(image_path):
447
- if image_path is None:
448
- return None
449
- try:
450
- with open(image_path, "rb") as img_file:
451
- return base64.b64encode(img_file.read()).decode("utf-8")
452
- except Exception as e:
453
- print(f"Error converting image to base64: {str(e)}")
454
- return None
455
-
456
- # Define the Gradio interface
457
- def create_demo():
458
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
459
- gr.Markdown("# 📚 Advanced Math Tutor")
460
- gr.Markdown(
461
- """This application provides step-by-step solutions to math problems using advanced AI models. Solutions are generated in **HTML format** with download and print-to-PDF capabilities. Choose between OpenRouter's Phi-4-reasoning-plus for text-based problems or Together AI's Llama-Vision for problems with images."""
462
- )
463
- # Main tabs
464
- with gr.Tabs():
465
- # Text-based problem solver (OpenRouter)
466
- with gr.TabItem("Text Problem Solver (OpenRouter)"):
467
- with gr.Row():
468
- with gr.Column(scale=1):
469
- openrouter_api_key = gr.Textbox(
470
- label="OpenRouter API Key",
471
- placeholder="Enter your OpenRouter API key (starts with sk-or-)",
472
- type="password"
473
- )
474
- text_problem_input = gr.Textbox(
475
- label="Math Problem",
476
- placeholder="Enter your math problem here...",
477
- lines=5
478
- )
479
- example_problems = gr.Examples(
480
- examples=[
481
- ["Solve the quadratic equation: 3x² + 5x - 2 = 0"],
482
- ["Find the derivative of f(x) = x³ln(x)"],
483
- ["Calculate the area of a circle with radius 5 cm"],
484
- ["Find all values of x that satisfy the equation: log₂(x-1) + log₂(x+3) = 5"]
485
- ],
486
- inputs=[text_problem_input],
487
- label="Example Problems"
488
- )
489
- with gr.Row():
490
- openrouter_submit_btn = gr.Button("Solve Problem", variant="primary")
491
- openrouter_clear_btn = gr.Button("Clear")
492
- with gr.Column(scale=2):
493
- openrouter_solution_output = gr.HTML(label="Solution (HTML Format)")
494
- with gr.Row():
495
- openrouter_html_download = gr.File(
496
- label="📄 Download HTML Solution",
497
- visible=False
498
- )
499
- openrouter_pdf_download = gr.File(
500
- label="📄 Download PDF Solution",
501
- visible=False
502
- )
503
- # Store conversation history (invisible to user)
504
- openrouter_conversation_history = gr.State(value=None)
505
- # Button actions
506
- def handle_openrouter_submit(api_key, problem_text, history):
507
- html_solution, html_file, pdf_file, updated_history = generate_math_solution_openrouter(
508
- api_key, problem_text, history
509
- )
510
- # Return outputs including file updates
511
- return (
512
- html_solution,
513
- updated_history,
514
- gr.update(value=html_file, visible=html_file is not None),
515
- gr.update(value=pdf_file, visible=pdf_file is not None)
516
- )
517
- openrouter_submit_btn.click(
518
- fn=handle_openrouter_submit,
519
- inputs=[openrouter_api_key, text_problem_input, openrouter_conversation_history],
520
- outputs=[
521
- openrouter_solution_output,
522
- openrouter_conversation_history,
523
- openrouter_html_download,
524
- openrouter_pdf_download
525
- ]
526
- )
527
- def clear_openrouter():
528
- return (
529
- "",
530
- None,
531
- gr.update(value=None, visible=False),
532
- gr.update(value=None, visible=False)
533
- )
534
- openrouter_clear_btn.click(
535
- fn=clear_openrouter,
536
- inputs=[],
537
- outputs=[
538
- openrouter_solution_output,
539
- openrouter_conversation_history,
540
- openrouter_html_download,
541
- openrouter_pdf_download
542
- ]
543
- )
544
- # Image-based problem solver (Together AI)
545
- with gr.TabItem("Image Problem Solver (Together AI)"):
546
- with gr.Row():
547
- with gr.Column(scale=1):
548
- together_api_key = gr.Textbox(
549
- label="Together AI API Key",
550
- placeholder="Enter your Together AI API key",
551
- type="password"
552
- )
553
- together_problem_input = gr.Textbox(
554
- label="Problem Description (Optional)",
555
- placeholder="Enter additional context for the image problem...",
556
- lines=3
557
- )
558
- together_image_input = gr.Image(
559
- label="Upload Math Problem Image",
560
- type="filepath"
561
- )
562
- with gr.Row():
563
- together_submit_btn = gr.Button("Solve Problem", variant="primary")
564
- together_clear_btn = gr.Button("Clear")
565
- with gr.Column(scale=2):
566
- together_solution_output = gr.HTML(label="Solution (HTML Format)")
567
- with gr.Row():
568
- together_html_download = gr.File(
569
- label="📄 Download HTML Solution",
570
- visible=False
571
- )
572
- together_pdf_download = gr.File(
573
- label="📄 Download PDF Solution",
574
- visible=False
575
- )
576
- # Store conversation history (invisible to user)
577
- together_conversation_history = gr.State(value=None)
578
- # Button actions
579
- def handle_together_submit(api_key, problem_text, image_path, history):
580
- html_solution, html_file, pdf_file, updated_history = generate_math_solution_together(
581
- api_key, problem_text, image_path, history
582
- )
583
- # Return outputs including file updates
584
- return (
585
- html_solution,
586
- updated_history,
587
- gr.update(value=html_file, visible=html_file is not None),
588
- gr.update(value=pdf_file, visible=pdf_file is not None)
589
- )
590
- together_submit_btn.click(
591
- fn=handle_together_submit,
592
- inputs=[together_api_key, together_problem_input, together_image_input, together_conversation_history],
593
- outputs=[
594
- together_solution_output,
595
- together_conversation_history,
596
- together_html_download,
597
- together_pdf_download
598
- ]
599
- )
600
- def clear_together():
601
- return (
602
- "",
603
- None,
604
- gr.update(value=None, visible=False),
605
- gr.update(value=None, visible=False)
606
- )
607
- together_clear_btn.click(
608
- fn=clear_together,
609
- inputs=[],
610
- outputs=[
611
- together_solution_output,
612
- together_conversation_history,
613
- together_html_download,
614
- together_pdf_download
615
- ]
616
- )
617
- # Help tab
618
- with gr.TabItem("Help"):
619
- gr.Markdown(
620
-
621
- ### New Features 🎉
622
- - **HTML-formatted solutions**: All responses are now generated in beautiful HTML format with MathJax for proper math rendering
623
- - **Download HTML**: Download the complete solution as an HTML file (math renders perfectly)
624
- - **Download PDF**: Convert and download solutions as PDF files (math rendered via browser for perfect equations)
625
- - **Print functionality**: Use the "Print to PDF" button in the HTML output to print directly (renders math via MathJax)
626
- )
627
- return demo
628
-
629
- # Launch the app
630
- if __name__ == "__main__":
631
- demo = create_demo()
632
- demo.launch()