|
import gradio as gr |
|
import os |
|
import base64 |
|
from openai import OpenAI |
|
from together import Together |
|
from PIL import Image |
|
import io |
|
import markdown |
|
from datetime import datetime |
|
import tempfile |
|
import weasyprint |
|
|
|
|
|
def markdown_to_html(markdown_text, problem_text="", include_problem=True): |
|
"""Convert markdown to styled HTML""" |
|
html_content = markdown.markdown(markdown_text, extensions=['tables', 'fenced_code']) |
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
styled_html = f""" |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Math Solution - Advanced Math Tutor</title> |
|
<style> |
|
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; }} |
|
.container {{ background-color: white; padding: 40px; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }} |
|
.header {{ text-align: center; border-bottom: 3px solid #4CAF50; padding-bottom: 20px; margin-bottom: 30px; }} |
|
.header h1 {{ color: #2c3e50; margin: 0; font-size: 2.5em; }} |
|
.header .subtitle {{ color: #7f8c8d; font-style: italic; margin-top: 10px; }} |
|
.problem-section {{ background-color: #e8f5e8; padding: 20px; border-radius: 8px; margin-bottom: 30px; border-left: 5px solid #4CAF50; }} |
|
.problem-section h2 {{ color: #2c3e50; margin-top: 0; }} |
|
.solution-content {{ background-color: #f8f9fa; padding: 25px; border-radius: 8px; border-left: 5px solid #007bff; }} |
|
h1, h2, h3 {{ color: #2c3e50; margin-top: 25px; margin-bottom: 15px; }} |
|
h2 {{ border-bottom: 2px solid #eee; padding-bottom: 10px; }} |
|
code {{ background-color: #f1f1f1; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #d63384; }} |
|
pre {{ background-color: #f8f8f8; padding: 15px; border-radius: 5px; overflow-x: auto; border: 1px solid #ddd; }} |
|
pre code {{ background-color: transparent; padding: 0; color: inherit; }} |
|
.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; }} |
|
.step {{ margin: 20px 0; padding: 15px; background-color: #ffffff; border-radius: 8px; border: 1px solid #dee2e6; }} |
|
.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; }} |
|
.timestamp {{ text-align: right; color: #6c757d; font-size: 0.9em; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; }} |
|
ul, ol {{ padding-left: 25px; }} |
|
li {{ margin: 8px 0; }} |
|
table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }} |
|
th, td {{ border: 1px solid #ddd; padding: 12px; text-align: left; }} |
|
th {{ background-color: #f8f9fa; font-weight: bold; }} |
|
.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; }} |
|
.print-button:hover {{ background-color: #0056b3; }} |
|
@media print {{ body {{ background-color: white; }} .container {{ box-shadow: none; border: none; }} .print-button {{ display: none; }} }} |
|
</style> |
|
<script> |
|
function printPage() {{ window.print(); }} |
|
</script> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="header"> |
|
<h1>📚 Advanced Math Tutor</h1> |
|
<div class="subtitle">Step-by-Step Mathematical Solution</div> |
|
</div> |
|
<button class="print-button" onclick="printPage()">🖨️ Print to PDF</button> |
|
{f''' |
|
<div class="problem-section"> |
|
<h2>📝 Problem Statement</h2> |
|
<p><strong>{problem_text}</strong></p> |
|
</div> |
|
''' if include_problem and problem_text.strip() else ''} |
|
<div class="solution-content"> |
|
<h2>🔍 Solution</h2> |
|
{html_content} |
|
</div> |
|
<div class="timestamp"> |
|
Generated on: {timestamp} |
|
</div> |
|
</div> |
|
</body> |
|
</html> |
|
""" |
|
return styled_html |
|
|
|
|
|
def save_html_to_file(html_content, filename_prefix="math_solution"): |
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
filename = f"{filename_prefix}_{timestamp}.html" |
|
temp_dir = tempfile.gettempdir() |
|
file_path = os.path.join(temp_dir, filename) |
|
with open(file_path, 'w', encoding='utf-8') as f: |
|
f.write(html_content) |
|
return file_path |
|
|
|
|
|
def html_to_pdf(html_content, filename_prefix="math_solution"): |
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
filename = f"{filename_prefix}_{timestamp}.pdf" |
|
temp_dir = tempfile.gettempdir() |
|
pdf_path = os.path.join(temp_dir, filename) |
|
try: |
|
weasyprint.HTML(string=html_content).write_pdf(pdf_path) |
|
return pdf_path |
|
except Exception as e: |
|
print(f"Error converting to PDF: {str(e)}") |
|
return None |
|
|
|
|
|
def generate_math_solution_openrouter(api_key, problem_text, history=None): |
|
if not api_key.strip(): |
|
yield "Please enter your OpenRouter API key.", None, None, history |
|
return |
|
if not problem_text.strip(): |
|
yield "Please enter a math problem.", None, None, history |
|
return |
|
|
|
try: |
|
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=api_key) |
|
messages = [ |
|
{"role": "system", "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: |
|
1. Show the mathematical operation |
|
2. Explain why this step is necessary |
|
3. Connect it to relevant mathematical concepts |
|
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: |
|
- Headers (##, ###) |
|
- **Bold text** for important points |
|
- `Code blocks` for mathematical expressions |
|
- Lists and numbered steps |
|
- Tables if needed for comparisons or data"""} |
|
] |
|
if history: |
|
for exchange in history: |
|
messages.append({"role": "user", "content": exchange[0]}) |
|
if len(exchange) > 1 and exchange[1]: |
|
messages.append({"role": "assistant", "content": exchange[1]}) |
|
messages.append({"role": "user", "content": f"Solve this math problem step-by-step: {problem_text}"}) |
|
|
|
|
|
completion = client.chat.completions.create( |
|
model="deepseek/deepseek-r1-0528:free", |
|
messages=messages, |
|
stream=True, |
|
extra_headers={"HTTP-Referer": "https://advancedmathtutor.edu", "X-Title": "Advanced Math Tutor"} |
|
) |
|
|
|
|
|
initial_html = markdown_to_html("**Thinking...**", problem_text, include_problem=True) |
|
yield initial_html, None, None, history |
|
|
|
|
|
accumulated_markdown = "" |
|
for chunk in completion: |
|
if chunk.choices[0].delta.content: |
|
accumulated_markdown += chunk.choices[0].delta.content |
|
html_solution = markdown_to_html(accumulated_markdown, problem_text, include_problem=True) |
|
yield html_solution, None, None, history |
|
|
|
|
|
final_html = markdown_to_html(accumulated_markdown, problem_text, include_problem=True) |
|
html_file_path = save_html_to_file(final_html, "openrouter_solution") |
|
pdf_file_path = html_to_pdf(final_html, "openrouter_solution") |
|
if history is None: |
|
history = [] |
|
history.append((problem_text, accumulated_markdown)) |
|
yield final_html, html_file_path, pdf_file_path, history |
|
|
|
except Exception as e: |
|
yield f"Error: {str(e)}", None, None, history |
|
|
|
|
|
def generate_math_solution_together(api_key, problem_text, image_path=None, history=None): |
|
if not api_key.strip(): |
|
yield "Please enter your Together AI API key.", None, None, history |
|
return |
|
if not problem_text.strip() and image_path is None: |
|
yield "Please enter a math problem or upload an image.", None, None, history |
|
return |
|
|
|
try: |
|
client = Together(api_key=api_key) |
|
messages = [ |
|
{"role": "system", "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: |
|
1. Show the mathematical operation |
|
2. Explain why this step is necessary |
|
3. Connect it to relevant mathematical concepts |
|
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: |
|
- Headers (##, ###) |
|
- **Bold text** for important points |
|
- `Code blocks` for mathematical expressions |
|
- Lists and numbered steps |
|
- Tables if needed for comparisons or data"""} |
|
] |
|
if history: |
|
for exchange in history: |
|
messages.append({"role": "user", "content": exchange[0]}) |
|
if len(exchange) > 1 and exchange[1]: |
|
messages.append({"role": "assistant", "content": exchange[1]}) |
|
|
|
user_message_content = [] |
|
problem_display = problem_text if problem_text.strip() else "Image-based problem" |
|
if problem_text.strip(): |
|
user_message_content.append({"type": "text", "text": f"Solve this math problem: {problem_text}"}) |
|
else: |
|
user_message_content.append({"type": "text", "text": "Solve this math problem from the image:"}) |
|
if image_path: |
|
base64_image = image_to_base64(image_path) |
|
if base64_image: |
|
user_message_content.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}) |
|
messages.append({"role": "user", "content": user_message_content}) |
|
|
|
|
|
response = client.chat.completions.create(model="meta-llama/Llama-Vision-Free", messages=messages, stream=True) |
|
|
|
|
|
initial_html = markdown_to_html("**Thinking...**", problem_display, include_problem=True) |
|
yield initial_html, None, None, history |
|
|
|
|
|
accumulated_markdown = "" |
|
for chunk in response: |
|
if chunk.choices[0].delta.content: |
|
accumulated_markdown += chunk.choices[0].delta.content |
|
html_solution = markdown_to_html(accumulated_markdown, problem_display, include_problem=True) |
|
yield html_solution, None, None, history |
|
|
|
|
|
final_html = markdown_to_html(accumulated_markdown, problem_display, include_problem=True) |
|
html_file_path = save_html_to_file(final_html, "together_solution") |
|
pdf_file_path = html_to_pdf(final_html, "together_solution") |
|
if history is None: |
|
history = [] |
|
history.append((problem_display, accumulated_markdown)) |
|
yield final_html, html_file_path, pdf_file_path, history |
|
|
|
except Exception as e: |
|
yield f"Error: {str(e)}", None, None, history |
|
|
|
|
|
def image_to_base64(image_path): |
|
if image_path is None: |
|
return None |
|
try: |
|
with open(image_path, "rb") as img_file: |
|
return base64.b64encode(img_file.read()).decode("utf-8") |
|
except Exception as e: |
|
print(f"Error converting image to base64: {str(e)}") |
|
return None |
|
|
|
|
|
def create_demo(): |
|
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo: |
|
gr.Markdown("# 📚 Advanced Math Tutor") |
|
gr.Markdown(""" |
|
This application provides step-by-step solutions to math problems using advanced AI models. |
|
Watch the **Thinking** process unfold in real-time before the final solution is presented! |
|
Solutions are in **HTML format** with download and print-to-PDF options. |
|
""") |
|
|
|
with gr.Tabs(): |
|
|
|
with gr.TabItem("Text Problem Solver (OpenRouter)"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
openrouter_api_key = gr.Textbox(label="OpenRouter API Key", placeholder="Enter your OpenRouter API key (starts with sk-or-)", type="password") |
|
text_problem_input = gr.Textbox(label="Math Problem", placeholder="Enter your math problem here...", lines=5) |
|
gr.Examples(examples=[ |
|
["Solve the quadratic equation: 3x² + 5x - 2 = 0"], |
|
["Calculate the area of a circle with radius 6 meters"] |
|
], inputs=[text_problem_input], label="Example Problems") |
|
with gr.Row(): |
|
openrouter_submit_btn = gr.Button("Solve Problem", variant="primary") |
|
openrouter_clear_btn = gr.Button("Clear") |
|
with gr.Column(scale=2): |
|
openrouter_solution_output = gr.HTML(label="Solution (HTML Format)") |
|
with gr.Row(): |
|
openrouter_html_download = gr.File(label="📄 Download HTML Solution", visible=False) |
|
openrouter_pdf_download = gr.File(label="📄 Download PDF Solution", visible=False) |
|
openrouter_conversation_history = gr.State(value=None) |
|
|
|
def handle_openrouter_submit(api_key, problem_text, history): |
|
for html_solution, html_file, pdf_file, updated_history in generate_math_solution_openrouter(api_key, problem_text, history): |
|
yield html_solution, html_file, pdf_file, updated_history |
|
|
|
openrouter_submit_btn.click( |
|
fn=handle_openrouter_submit, |
|
inputs=[openrouter_api_key, text_problem_input, openrouter_conversation_history], |
|
outputs=[openrouter_solution_output, openrouter_html_download, openrouter_pdf_download, openrouter_conversation_history] |
|
) |
|
|
|
def clear_openrouter(): |
|
return "", None, None, None |
|
openrouter_clear_btn.click(fn=clear_openrouter, outputs=[openrouter_solution_output, openrouter_html_download, openrouter_pdf_download, openrouter_conversation_history]) |
|
|
|
|
|
with gr.TabItem("Image Problem Solver (Together AI)"): |
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
together_api_key = gr.Textbox(label="Together AI API Key", placeholder="Enter your Together AI API key", type="password") |
|
together_problem_input = gr.Textbox(label="Problem Description (Optional)", placeholder="Enter additional context...", lines=3) |
|
together_image_input = gr.Image(label="Upload Math Problem Image", type="filepath") |
|
with gr.Row(): |
|
together_submit_btn = gr.Button("Solve Problem", variant="primary") |
|
together_clear_btn = gr.Button("Clear") |
|
with gr.Column(scale=2): |
|
together_solution_output = gr.HTML(label="Solution (HTML Format)") |
|
with gr.Row(): |
|
together_html_download = gr.File(label="📄 Download HTML Solution", visible=False) |
|
together_pdf_download = gr.File(label="📄 Download PDF Solution", visible=False) |
|
together_conversation_history = gr.State(value=None) |
|
|
|
def handle_together_submit(api_key, problem_text, image_path, history): |
|
for html_solution, html_file, pdf_file, updated_history in generate_math_solution_together(api_key, problem_text, image_path, history): |
|
yield html_solution, html_file, pdf_file, updated_history |
|
|
|
together_submit_btn.click( |
|
fn=handle_together_submit, |
|
inputs=[together_api_key, together_problem_input, together_image_input, together_conversation_history], |
|
outputs=[together_solution_output, together_html_download, together_pdf_download, together_conversation_history] |
|
) |
|
|
|
def clear_together(): |
|
return "", None, None, None |
|
together_clear_btn.click(fn=clear_together, outputs=[together_solution_output, together_html_download, together_pdf_download, together_conversation_history]) |
|
|
|
|
|
with gr.TabItem("Help"): |
|
gr.Markdown(""" |
|
## How to Use the Advanced Math Tutor |
|
|
|
### New Feature: Dynamic Thinking Process 🎉 |
|
- Watch the AI’s **Thinking** process unfold step-by-step in real-time, just like DeepSeek’s "deepThink r1"! |
|
- Solutions are still in **HTML format** with download and print-to-PDF options. |
|
|
|
### Getting Started |
|
#### Text Problems (OpenRouter) |
|
1. Get an API key from [OpenRouter](https://openrouter.ai/). |
|
2. Enter it in the "Text Problem Solver" tab. |
|
3. Type your math problem and hit "Solve Problem". |
|
|
|
#### Image Problems (Together AI) |
|
1. Get an API key from [Together AI](https://www.together.ai/). |
|
2. Enter it in the "Image Problem Solver" tab. |
|
3. Upload an image or add text context, then click "Solve Problem". |
|
|
|
### What You’ll See |
|
- **Thinking Process**: Starts with "Thinking..." and builds the solution step-by-step (e.g., formula, calculations). |
|
- **Final Output**: Full HTML solution with downloadable HTML and PDF files. |
|
|
|
### Tips |
|
- Use clear problem statements (e.g., "Calculate the area of a circle with radius 6 meters"). |
|
- For images, ensure good quality. |
|
- Install dependencies: `pip install gradio openai together pillow markdown weasyprint`. |
|
""") |
|
|
|
gr.Markdown(""" |
|
--- |
|
### About |
|
Powered by OpenRouter’s Phi-4-reasoning-plus and Together AI’s Llama-Vision-Free. |
|
Now with a live "Thinking" display! |
|
""") |
|
|
|
return demo |
|
|
|
|
|
if __name__ == "__main__": |
|
demo = create_demo() |
|
demo.launch() |