shukdevdattaEX commited on
Commit
58623f6
Β·
verified Β·
1 Parent(s): 6561dd2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +538 -188
app.py CHANGED
@@ -1,5 +1,7 @@
1
  import gradio as gr
2
  import os
 
 
3
  import base64
4
  from openai import OpenAI
5
  from together import Together
@@ -9,12 +11,19 @@ import markdown
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,34 +32,168 @@ def markdown_to_html(markdown_text, problem_text="", include_problem=True):
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,17 +202,21 @@ def markdown_to_html(markdown_text, problem_text="", include_problem=True):
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,42 +224,56 @@ def markdown_to_html(markdown_text, problem_text="", include_problem=True):
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,133 +282,172 @@ def generate_math_solution_openrouter(api_key, problem_text, history=None):
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,122 +455,272 @@ def image_to_base64(image_path):
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()
 
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
  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
  <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
+ @media print {{
181
+ body {{
182
+ background-color: white;
183
+ }}
184
+ .container {{
185
+ box-shadow: none;
186
+ border: none;
187
+ }}
188
+ .print-button {{
189
+ display: none;
190
+ }}
191
+ }}
192
  </style>
193
  <script>
194
+ function printPage() {{
195
+ window.print();
196
+ }}
197
  </script>
198
  </head>
199
  <body>
 
202
  <h1>πŸ“š Advanced Math Tutor</h1>
203
  <div class="subtitle">Step-by-Step Mathematical Solution</div>
204
  </div>
205
+
206
  <button class="print-button" onclick="printPage()">πŸ–¨οΈ Print to PDF</button>
207
+
208
  {f'''
209
  <div class="problem-section">
210
  <h2>πŸ“ Problem Statement</h2>
211
  <p><strong>{problem_text}</strong></p>
212
  </div>
213
  ''' if include_problem and problem_text.strip() else ''}
214
+
215
  <div class="solution-content">
216
  <h2>πŸ” Solution</h2>
217
  {html_content}
218
  </div>
219
+
220
  <div class="timestamp">
221
  Generated on: {timestamp}
222
  </div>
 
224
  </body>
225
  </html>
226
  """
227
+
228
  return styled_html
229
 
230
+ # Function to save HTML to file
231
  def save_html_to_file(html_content, filename_prefix="math_solution"):
232
+ """Save HTML content to a temporary file and return the file path"""
233
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
234
  filename = f"{filename_prefix}_{timestamp}.html"
235
+
236
+ # Create a temporary file
237
  temp_dir = tempfile.gettempdir()
238
  file_path = os.path.join(temp_dir, filename)
239
+
240
  with open(file_path, 'w', encoding='utf-8') as f:
241
  f.write(html_content)
242
+
243
  return file_path
244
 
245
+ # Function to convert HTML to PDF
246
  def html_to_pdf(html_content, filename_prefix="math_solution"):
247
+ """Convert HTML content to PDF and return the file path"""
248
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
249
  filename = f"{filename_prefix}_{timestamp}.pdf"
250
+
251
+ # Create a temporary file for PDF
252
  temp_dir = tempfile.gettempdir()
253
  pdf_path = os.path.join(temp_dir, filename)
254
+
255
  try:
256
+ # Convert HTML to PDF using WeasyPrint
257
  weasyprint.HTML(string=html_content).write_pdf(pdf_path)
258
  return pdf_path
259
  except Exception as e:
260
  print(f"Error converting to PDF: {str(e)}")
261
  return None
262
 
263
+ # Enhanced function to generate math solution using OpenRouter with HTML output
264
  def generate_math_solution_openrouter(api_key, problem_text, history=None):
265
  if not api_key.strip():
266
+ return "Please enter your OpenRouter API key.", None, None, history
267
+
268
  if not problem_text.strip():
269
+ return "Please enter a math problem.", None, None, history
270
+
 
271
  try:
272
+ client = OpenAI(
273
+ base_url="https://openrouter.ai/api/v1",
274
+ api_key=api_key,
275
+ )
276
+
277
  messages = [
278
  {"role": "system", "content":
279
  """You are an expert math tutor who explains concepts clearly and thoroughly.
 
282
  1. Show the mathematical operation
283
  2. Explain why this step is necessary
284
  3. Connect it to relevant mathematical concepts
285
+
286
  Format your response using markdown with clear section headers.
287
  Begin with an "Initial Analysis" section, follow with numbered steps,
288
  and conclude with a "Final Answer" section.
289
+
290
  Use proper markdown formatting including:
291
  - Headers (##, ###)
292
  - **Bold text** for important points
293
  - `Code blocks` for mathematical expressions
294
  - Lists and numbered steps
295
+ - Tables if needed for comparisons or data"""},
296
  ]
297
+
298
+ # Add conversation history if it exists
299
  if history:
300
  for exchange in history:
301
  messages.append({"role": "user", "content": exchange[0]})
302
+ if len(exchange) > 1 and exchange[1]: # Check if there's a response
303
  messages.append({"role": "assistant", "content": exchange[1]})
304
+
305
+ # Add the current problem
306
  messages.append({"role": "user", "content": f"Solve this math problem step-by-step: {problem_text}"})
307
+
308
+ # Create the completion
309
  completion = client.chat.completions.create(
310
  model="deepseek/deepseek-r1-0528:free",
311
  messages=messages,
312
  stream=True,
313
+ extra_headers={
314
+ "HTTP-Referer": "https://advancedmathtutor.edu",
315
+ "X-Title": "Advanced Math Tutor",
316
+ }
317
  )
318
+
319
+ markdown_solution = completion.choices[0].message.content
320
+
321
+ # Convert to HTML
322
+ html_solution = markdown_to_html(markdown_solution, problem_text)
323
+
324
+ # Save HTML file
325
+ html_file_path = save_html_to_file(html_solution, "openrouter_solution")
326
+
327
+ # Convert to PDF
328
+ pdf_file_path = html_to_pdf(html_solution, "openrouter_solution")
329
+
330
+ # Update history
 
 
 
 
331
  if history is None:
332
  history = []
333
+ history.append((problem_text, markdown_solution))
334
+
335
+ return html_solution, html_file_path, pdf_file_path, history
336
+
337
  except Exception as e:
338
+ error_message = f"Error: {str(e)}"
339
+ return error_message, None, None, history
340
 
341
+ # Enhanced function to generate math solution using Together AI with HTML output
342
  def generate_math_solution_together(api_key, problem_text, image_path=None, history=None):
343
  if not api_key.strip():
344
+ return "Please enter your Together AI API key.", None, None, history
345
+
346
  if not problem_text.strip() and image_path is None:
347
+ return "Please enter a math problem or upload an image of a math problem.", None, None, history
348
+
 
349
  try:
350
  client = Together(api_key=api_key)
351
+
352
+ # Create the base message structure
353
  messages = [
354
+ {
355
+ "role": "system",
356
+ "content": """You are an expert math tutor who explains concepts clearly and thoroughly.
357
+ Analyze the given math problem and provide a detailed step-by-step solution.
358
+ For each step:
359
+ 1. Show the mathematical operation
360
+ 2. Explain why this step is necessary
361
+ 3. Connect it to relevant mathematical concepts
362
+
363
+ Format your response using markdown with clear section headers.
364
+ Begin with an "Initial Analysis" section, follow with numbered steps,
365
+ and conclude with a "Final Answer" section.
366
+
367
+ Use proper markdown formatting including:
368
+ - Headers (##, ###)
369
+ - **Bold text** for important points
370
+ - `Code blocks` for mathematical expressions
371
+ - Lists and numbered steps
372
+ - Tables if needed for comparisons or data"""
373
+ }
374
  ]
375
+
376
+ # Add conversation history if it exists
377
  if history:
378
  for exchange in history:
379
  messages.append({"role": "user", "content": exchange[0]})
380
+ if len(exchange) > 1 and exchange[1]: # Check if there's a response
381
  messages.append({"role": "assistant", "content": exchange[1]})
382
+
383
+ # Prepare the user message content
384
  user_message_content = []
385
+
386
+ # Add text content if provided
387
  if problem_text.strip():
388
+ user_message_content.append({
389
+ "type": "text",
390
+ "text": f"Solve this math problem: {problem_text}"
391
+ })
392
  else:
393
+ user_message_content.append({
394
+ "type": "text",
395
+ "text": "Solve this math problem from the image:"
396
+ })
397
+
398
+ # Add image if provided
399
  if image_path:
400
+ # Convert image to base64
401
  base64_image = image_to_base64(image_path)
402
  if base64_image:
403
+ user_message_content.append({
404
+ "type": "image_url",
405
+ "image_url": {
406
+ "url": f"data:image/jpeg;base64,{base64_image}"
407
+ }
408
+ })
409
+
410
+ # Add the user message with content
411
+ messages.append({
412
+ "role": "user",
413
+ "content": user_message_content
414
+ })
415
+
416
+ # Create the completion
417
+ response = client.chat.completions.create(
418
+ model="meta-llama/Llama-Vision-Free",
419
+ messages=messages,
420
+ stream=True
421
+ )
422
+
423
+ markdown_solution = response.choices[0].message.content
424
+
425
+ # Convert to HTML
426
+ problem_display = problem_text if problem_text.strip() else "Image-based problem"
427
+ html_solution = markdown_to_html(markdown_solution, problem_display)
428
+
429
+ # Save HTML file
430
+ html_file_path = save_html_to_file(html_solution, "together_solution")
431
+
432
+ # Convert to PDF
433
+ pdf_file_path = html_to_pdf(html_solution, "together_solution")
434
+
435
+ # Update history - for simplicity, just store the text problem
436
  if history is None:
437
  history = []
438
+ history.append((problem_display, markdown_solution))
439
+
440
+ return html_solution, html_file_path, pdf_file_path, history
441
+
442
  except Exception as e:
443
+ error_message = f"Error: {str(e)}"
444
+ return error_message, None, None, history
445
 
446
+ # Function to convert image to base64
447
  def image_to_base64(image_path):
448
  if image_path is None:
449
  return None
450
+
451
  try:
452
  with open(image_path, "rb") as img_file:
453
  return base64.b64encode(img_file.read()).decode("utf-8")
 
455
  print(f"Error converting image to base64: {str(e)}")
456
  return None
457
 
458
+ # Define the Gradio interface
459
  def create_demo():
460
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
461
  gr.Markdown("# πŸ“š Advanced Math Tutor")
462
  gr.Markdown("""
463
  This application provides step-by-step solutions to math problems using advanced AI models.
464
+ Solutions are generated in **HTML format** with download and print-to-PDF capabilities.
465
+ Choose between OpenRouter's Phi-4-reasoning-plus for text-based problems or Together AI's
466
+ Llama-Vision for problems with images.
467
  """)
468
+
469
+ # Main tabs
470
  with gr.Tabs():
471
+ # Text-based problem solver (OpenRouter)
472
  with gr.TabItem("Text Problem Solver (OpenRouter)"):
473
  with gr.Row():
474
  with gr.Column(scale=1):
475
+ openrouter_api_key = gr.Textbox(
476
+ label="OpenRouter API Key",
477
+ placeholder="Enter your OpenRouter API key (starts with sk-or-)",
478
+ type="password"
479
+ )
480
+ text_problem_input = gr.Textbox(
481
+ label="Math Problem",
482
+ placeholder="Enter your math problem here...",
483
+ lines=5
484
+ )
485
+ example_problems = gr.Examples(
486
+ examples=[
487
+ ["Solve the quadratic equation: 3xΒ² + 5x - 2 = 0"],
488
+ ["Find the derivative of f(x) = xΒ³ln(x)"],
489
+ ["Calculate the area of a circle with radius 5 cm"],
490
+ ["Find all values of x that satisfy the equation: logβ‚‚(x-1) + logβ‚‚(x+3) = 5"]
491
+ ],
492
+ inputs=[text_problem_input],
493
+ label="Example Problems"
494
+ )
495
  with gr.Row():
496
  openrouter_submit_btn = gr.Button("Solve Problem", variant="primary")
497
  openrouter_clear_btn = gr.Button("Clear")
498
+
499
+ with gr.Column(scale=2):
500
+ openrouter_solution_output = gr.HTML(label="Solution (HTML Format)")
501
+
502
+ with gr.Row():
503
+ openrouter_html_download = gr.File(
504
+ label="πŸ“„ Download HTML Solution",
505
+ visible=False
506
+ )
507
+ openrouter_pdf_download = gr.File(
508
+ label="πŸ“„ Download PDF Solution",
509
+ visible=False
510
+ )
511
+
512
+ # Store conversation history (invisible to user)
513
  openrouter_conversation_history = gr.State(value=None)
514
+
515
+ # Button actions
516
  def handle_openrouter_submit(api_key, problem_text, history):
517
+ html_solution, html_file, pdf_file, updated_history = generate_math_solution_openrouter(
518
+ api_key, problem_text, history
519
+ )
520
+
521
+ # Return outputs including file updates
522
+ return (
523
+ html_solution,
524
+ updated_history,
525
+ gr.update(value=html_file, visible=html_file is not None),
526
+ gr.update(value=pdf_file, visible=pdf_file is not None)
527
+ )
528
+
529
  openrouter_submit_btn.click(
530
  fn=handle_openrouter_submit,
531
  inputs=[openrouter_api_key, text_problem_input, openrouter_conversation_history],
532
+ outputs=[
533
+ openrouter_solution_output,
534
+ openrouter_conversation_history,
535
+ openrouter_html_download,
536
+ openrouter_pdf_download
537
+ ]
538
  )
539
+
540
  def clear_openrouter():
541
+ return (
542
+ "",
543
+ None,
544
+ gr.update(value=None, visible=False),
545
+ gr.update(value=None, visible=False)
546
+ )
547
+
548
+ openrouter_clear_btn.click(
549
+ fn=clear_openrouter,
550
+ inputs=[],
551
+ outputs=[
552
+ openrouter_solution_output,
553
+ openrouter_conversation_history,
554
+ openrouter_html_download,
555
+ openrouter_pdf_download
556
+ ]
557
+ )
558
+
559
+ # Image-based problem solver (Together AI)
560
  with gr.TabItem("Image Problem Solver (Together AI)"):
561
  with gr.Row():
562
  with gr.Column(scale=1):
563
+ together_api_key = gr.Textbox(
564
+ label="Together AI API Key",
565
+ placeholder="Enter your Together AI API key",
566
+ type="password"
567
+ )
568
+ together_problem_input = gr.Textbox(
569
+ label="Problem Description (Optional)",
570
+ placeholder="Enter additional context for the image problem...",
571
+ lines=3
572
+ )
573
+ together_image_input = gr.Image(
574
+ label="Upload Math Problem Image",
575
+ type="filepath"
576
+ )
577
  with gr.Row():
578
  together_submit_btn = gr.Button("Solve Problem", variant="primary")
579
  together_clear_btn = gr.Button("Clear")
580
+
581
+ with gr.Column(scale=2):
582
+ together_solution_output = gr.HTML(label="Solution (HTML Format)")
583
+
584
+ with gr.Row():
585
+ together_html_download = gr.File(
586
+ label="πŸ“„ Download HTML Solution",
587
+ visible=False
588
+ )
589
+ together_pdf_download = gr.File(
590
+ label="πŸ“„ Download PDF Solution",
591
+ visible=False
592
+ )
593
+
594
+ # Store conversation history (invisible to user)
595
  together_conversation_history = gr.State(value=None)
596
+
597
+ # Button actions
598
  def handle_together_submit(api_key, problem_text, image_path, history):
599
+ html_solution, html_file, pdf_file, updated_history = generate_math_solution_together(
600
+ api_key, problem_text, image_path, history
601
+ )
602
+
603
+ # Return outputs including file updates
604
+ return (
605
+ html_solution,
606
+ updated_history,
607
+ gr.update(value=html_file, visible=html_file is not None),
608
+ gr.update(value=pdf_file, visible=pdf_file is not None)
609
+ )
610
+
611
  together_submit_btn.click(
612
  fn=handle_together_submit,
613
  inputs=[together_api_key, together_problem_input, together_image_input, together_conversation_history],
614
+ outputs=[
615
+ together_solution_output,
616
+ together_conversation_history,
617
+ together_html_download,
618
+ together_pdf_download
619
+ ]
620
  )
621
+
622
  def clear_together():
623
+ return (
624
+ "",
625
+ None,
626
+ gr.update(value=None, visible=False),
627
+ gr.update(value=None, visible=False)
628
+ )
629
+
630
+ together_clear_btn.click(
631
+ fn=clear_together,
632
+ inputs=[],
633
+ outputs=[
634
+ together_solution_output,
635
+ together_conversation_history,
636
+ together_html_download,
637
+ together_pdf_download
638
+ ]
639
+ )
640
+
641
+ # Help tab
642
  with gr.TabItem("Help"):
643
  gr.Markdown("""
644
  ## How to Use the Advanced Math Tutor
645
+
646
+ ### New Features πŸŽ‰
647
+ - **HTML-formatted solutions**: All responses are now generated in beautiful HTML format
648
+ - **Download HTML**: Download the complete solution as an HTML file
649
+ - **Download PDF**: Convert and download solutions as PDF files
650
+ - **Print functionality**: Use the "Print to PDF" button in the HTML output to print directly
651
+
652
  ### Getting Started
653
+
654
+ #### For Text-Based Problems (OpenRouter)
655
+ 1. You'll need an API key from OpenRouter
656
+ 2. Sign up at [OpenRouter](https://openrouter.ai/) to get your API key
657
+ 3. Enter your API key in the designated field in the "Text Problem Solver" tab
658
+
659
+ #### For Image-Based Problems (Together AI)
660
+ 1. You'll need an API key from Together AI
661
+ 2. Sign up at [Together AI](https://www.together.ai/) to get your API key
662
+ 3. Enter your API key in the designated field in the "Image Problem Solver" tab
663
+ 4. Upload an image of your math problem
664
+ 5. Optionally add text to provide additional context
665
+
666
+ ### Solving Math Problems
667
+ - For text problems: Type or paste your math problem in the input field
668
+ - For image problems: Upload a clear image of the math problem
669
+ - Click "Solve Problem" to get a detailed step-by-step solution in HTML format
670
+ - Use the download buttons to save HTML or PDF versions
671
+ - Click "Print to PDF" within the solution to print directly from your browser
672
+
673
+ ### HTML Output Features
674
+ - **Professional styling**: Clean, readable format with proper typography
675
+ - **Mathematical expressions**: Highlighted math expressions and code blocks
676
+ - **Step-by-step sections**: Clearly organized solution steps
677
+ - **Print-friendly**: Optimized for printing and PDF conversion
678
+ - **Timestamps**: Each solution includes generation timestamp
679
+
680
+ ### Tips for Best Results
681
+ - Be specific in your problem description
682
+ - Include all necessary information
683
+ - For complex equations, use clear notation
684
+ - For algebraic expressions, use ^ for exponents (e.g., x^2 for xΒ²)
685
+ - Use parentheses to group terms clearly
686
+ - For images, ensure the math problem is clearly visible and well-lit
687
+
688
+ ### Types of Problems You Can Solve
689
+ - Algebra (equations, inequalities, systems of equations)
690
+ - Calculus (derivatives, integrals, limits)
691
+ - Trigonometry
692
+ - Geometry
693
+ - Statistics and Probability
694
+ - Number Theory
695
+ - And many more!
696
+
697
+ ### Required Dependencies
698
+ To run this application, you'll need to install:
699
+ ```bash
700
+ pip install gradio openai together pillow markdown weasyprint
701
+ ```
702
  """)
703
+
704
+ # Footer
705
  gr.Markdown("""
706
  ---
707
  ### About
708
+ This enhanced application uses Microsoft's Phi-4-reasoning-plus model via OpenRouter for text-based problems
709
+ and Llama-Vision-Free via Together AI for image-based problems.
710
+
711
+ **New Features:**
712
+ - HTML-formatted responses with professional styling
713
+ - Download solutions as HTML files
714
+ - Convert and download solutions as PDF files
715
+ - Print-to-PDF functionality
716
+ - Enhanced formatting with mathematical expressions highlighting
717
+
718
+ Your API keys are required but not stored permanently.
719
  """)
720
+
721
  return demo
722
 
723
+ # Launch the app
724
  if __name__ == "__main__":
725
  demo = create_demo()
726
  demo.launch()