| import gradio as gr | |
| import os | |
| import re | |
| import time | |
| 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 | |
| from pathlib import Path | |
| # Function to convert markdown to HTML with styling | |
| def markdown_to_html(markdown_text, problem_text="", include_problem=True): | |
| """Convert markdown to styled HTML""" | |
| # Convert markdown to HTML | |
| html_content = markdown.markdown(markdown_text, extensions=['tables', 'fenced_code']) | |
| # Get current timestamp | |
| timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") | |
| # Create styled HTML document | |
| 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, h4, h5, h6 {{ | |
| 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 | |
| # Function to save HTML to file | |
| def save_html_to_file(html_content, filename_prefix="math_solution"): | |
| """Save HTML content to a temporary file and return the file path""" | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"{filename_prefix}_{timestamp}.html" | |
| # Create a temporary file | |
| 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 | |
| # Function to convert HTML to PDF | |
| def html_to_pdf(html_content, filename_prefix="math_solution"): | |
| """Convert HTML content to PDF and return the file path""" | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| filename = f"{filename_prefix}_{timestamp}.pdf" | |
| # Create a temporary file for PDF | |
| temp_dir = tempfile.gettempdir() | |
| pdf_path = os.path.join(temp_dir, filename) | |
| try: | |
| # Convert HTML to PDF using WeasyPrint | |
| 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 | |
| # Enhanced function to generate math solution using OpenRouter with HTML output | |
| def generate_math_solution_openrouter(api_key, problem_text, history=None): | |
| if not api_key.strip(): | |
| return "Please enter your OpenRouter API key.", None, None, history | |
| if not problem_text.strip(): | |
| return "Please enter a math problem.", None, None, history | |
| 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"""}, | |
| ] | |
| # Add conversation history if it exists | |
| if history: | |
| for exchange in history: | |
| messages.append({"role": "user", "content": exchange[0]}) | |
| if len(exchange) > 1 and exchange[1]: # Check if there's a response | |
| messages.append({"role": "assistant", "content": exchange[1]}) | |
| # Add the current problem | |
| messages.append({"role": "user", "content": f"Solve this math problem step-by-step: {problem_text}"}) | |
| # Create the completion | |
| completion = client.chat.completions.create( | |
| model="meta-llama/llama-3.3-70b-instruct:free", | |
| messages=messages, | |
| extra_headers={ | |
| "HTTP-Referer": "https://advancedmathtutor.edu", | |
| "X-Title": "Advanced Math Tutor", | |
| } | |
| ) | |
| markdown_solution = completion.choices[0].message.content | |
| # Convert to HTML | |
| html_solution = markdown_to_html(markdown_solution, problem_text) | |
| # Save HTML file | |
| html_file_path = save_html_to_file(html_solution, "openrouter_solution") | |
| # Convert to PDF | |
| pdf_file_path = html_to_pdf(html_solution, "openrouter_solution") | |
| # Update history | |
| if history is None: | |
| history = [] | |
| history.append((problem_text, markdown_solution)) | |
| return html_solution, html_file_path, pdf_file_path, history | |
| except Exception as e: | |
| error_message = f"Error: {str(e)}" | |
| return error_message, None, None, history | |
| # Enhanced function to generate math solution using Together AI with HTML output | |
| def generate_math_solution_together(api_key, problem_text, image_path=None, history=None): | |
| if not api_key.strip(): | |
| return "Please enter your Together AI API key.", None, None, history | |
| if not problem_text.strip() and image_path is None: | |
| return "Please enter a math problem or upload an image of a math problem.", None, None, history | |
| try: | |
| client = Together(api_key=api_key) | |
| # Create the base message structure | |
| 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""" | |
| } | |
| ] | |
| # Add conversation history if it exists | |
| if history: | |
| for exchange in history: | |
| messages.append({"role": "user", "content": exchange[0]}) | |
| if len(exchange) > 1 and exchange[1]: # Check if there's a response | |
| messages.append({"role": "assistant", "content": exchange[1]}) | |
| # Prepare the user message content | |
| user_message_content = [] | |
| # Add text content if provided | |
| 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:" | |
| }) | |
| # Add image if provided | |
| if image_path: | |
| # Convert image to base64 | |
| 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}" | |
| } | |
| }) | |
| # Add the user message with content | |
| messages.append({ | |
| "role": "user", | |
| "content": user_message_content | |
| }) | |
| # Create the completion | |
| response = client.chat.completions.create( | |
| model="ServiceNow-AI/Apriel-1.5-15b-Thinker", | |
| messages=messages, | |
| stream=False | |
| ) | |
| markdown_solution = response.choices[0].message.content | |
| # Convert to HTML | |
| problem_display = problem_text if problem_text.strip() else "Image-based problem" | |
| html_solution = markdown_to_html(markdown_solution, problem_display) | |
| # Save HTML file | |
| html_file_path = save_html_to_file(html_solution, "together_solution") | |
| # Convert to PDF | |
| pdf_file_path = html_to_pdf(html_solution, "together_solution") | |
| # Update history - for simplicity, just store the text problem | |
| if history is None: | |
| history = [] | |
| history.append((problem_display, markdown_solution)) | |
| return html_solution, html_file_path, pdf_file_path, history | |
| except Exception as e: | |
| error_message = f"Error: {str(e)}" | |
| return error_message, None, None, history | |
| # Function to convert image to base64 | |
| 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 | |
| # Define the Gradio interface | |
| 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. | |
| Solutions are generated in **HTML format** with download and print-to-PDF capabilities. | |
| Choose between OpenRouter's Meta: Llama 3.3 70B Instruct for text-based problems or Together AI's | |
| Apriel-1.5-15b-Thinker for problems with images. | |
| """) | |
| # Main tabs | |
| with gr.Tabs(): | |
| # Text-based problem solver (OpenRouter) | |
| 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 | |
| ) | |
| example_problems = gr.Examples( | |
| examples=[ | |
| ["Solve the quadratic equation: 3x² + 5x - 2 = 0"], | |
| ["Find the derivative of f(x) = x³ln(x)"], | |
| ["Calculate the area of a circle with radius 5 cm"], | |
| ["Find all values of x that satisfy the equation: log₂(x-1) + log₂(x+3) = 5"] | |
| ], | |
| 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 | |
| ) | |
| # Store conversation history (invisible to user) | |
| openrouter_conversation_history = gr.State(value=None) | |
| # Button actions | |
| def handle_openrouter_submit(api_key, problem_text, history): | |
| html_solution, html_file, pdf_file, updated_history = generate_math_solution_openrouter( | |
| api_key, problem_text, history | |
| ) | |
| # Return outputs including file updates | |
| return ( | |
| html_solution, | |
| updated_history, | |
| gr.update(value=html_file, visible=html_file is not None), | |
| gr.update(value=pdf_file, visible=pdf_file is not None) | |
| ) | |
| openrouter_submit_btn.click( | |
| fn=handle_openrouter_submit, | |
| inputs=[openrouter_api_key, text_problem_input, openrouter_conversation_history], | |
| outputs=[ | |
| openrouter_solution_output, | |
| openrouter_conversation_history, | |
| openrouter_html_download, | |
| openrouter_pdf_download | |
| ] | |
| ) | |
| def clear_openrouter(): | |
| return ( | |
| "", | |
| None, | |
| gr.update(value=None, visible=False), | |
| gr.update(value=None, visible=False) | |
| ) | |
| openrouter_clear_btn.click( | |
| fn=clear_openrouter, | |
| inputs=[], | |
| outputs=[ | |
| openrouter_solution_output, | |
| openrouter_conversation_history, | |
| openrouter_html_download, | |
| openrouter_pdf_download | |
| ] | |
| ) | |
| # Image-based problem solver (Together AI) | |
| 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 for the image problem...", | |
| 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 | |
| ) | |
| # Store conversation history (invisible to user) | |
| together_conversation_history = gr.State(value=None) | |
| # Button actions | |
| def handle_together_submit(api_key, problem_text, image_path, history): | |
| html_solution, html_file, pdf_file, updated_history = generate_math_solution_together( | |
| api_key, problem_text, image_path, history | |
| ) | |
| # Return outputs including file updates | |
| return ( | |
| html_solution, | |
| updated_history, | |
| gr.update(value=html_file, visible=html_file is not None), | |
| gr.update(value=pdf_file, visible=pdf_file is not None) | |
| ) | |
| 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_conversation_history, | |
| together_html_download, | |
| together_pdf_download | |
| ] | |
| ) | |
| def clear_together(): | |
| return ( | |
| "", | |
| None, | |
| gr.update(value=None, visible=False), | |
| gr.update(value=None, visible=False) | |
| ) | |
| together_clear_btn.click( | |
| fn=clear_together, | |
| inputs=[], | |
| outputs=[ | |
| together_solution_output, | |
| together_conversation_history, | |
| together_html_download, | |
| together_pdf_download | |
| ] | |
| ) | |
| # Help tab | |
| with gr.TabItem("Help"): | |
| gr.Markdown(""" | |
| ## How to Use the Advanced Math Tutor | |
| ### New Features 🎉 | |
| - **HTML-formatted solutions**: All responses are now generated in beautiful HTML format | |
| - **Download HTML**: Download the complete solution as an HTML file | |
| - **Download PDF**: Convert and download solutions as PDF files | |
| - **Print functionality**: Use the "Print to PDF" button in the HTML output to print directly | |
| ### Getting Started | |
| #### For Text-Based Problems (OpenRouter) | |
| 1. You'll need an API key from OpenRouter | |
| 2. Sign up at [OpenRouter](https://openrouter.ai/) to get your API key | |
| 3. Enter your API key in the designated field in the "Text Problem Solver" tab | |
| #### For Image-Based Problems (Together AI) | |
| 1. You'll need an API key from Together AI | |
| 2. Sign up at [Together AI](https://www.together.ai/) to get your API key | |
| 3. Enter your API key in the designated field in the "Image Problem Solver" tab | |
| 4. Upload an image of your math problem | |
| 5. Optionally add text to provide additional context | |
| ### Solving Math Problems | |
| - For text problems: Type or paste your math problem in the input field | |
| - For image problems: Upload a clear image of the math problem | |
| - Click "Solve Problem" to get a detailed step-by-step solution in HTML format | |
| - Use the download buttons to save HTML or PDF versions | |
| - Click "Print to PDF" within the solution to print directly from your browser | |
| ### HTML Output Features | |
| - **Professional styling**: Clean, readable format with proper typography | |
| - **Mathematical expressions**: Highlighted math expressions and code blocks | |
| - **Step-by-step sections**: Clearly organized solution steps | |
| - **Print-friendly**: Optimized for printing and PDF conversion | |
| - **Timestamps**: Each solution includes generation timestamp | |
| ### Tips for Best Results | |
| - Be specific in your problem description | |
| - Include all necessary information | |
| - For complex equations, use clear notation | |
| - For algebraic expressions, use ^ for exponents (e.g., x^2 for x²) | |
| - Use parentheses to group terms clearly | |
| - For images, ensure the math problem is clearly visible and well-lit | |
| ### Types of Problems You Can Solve | |
| - Algebra (equations, inequalities, systems of equations) | |
| - Calculus (derivatives, integrals, limits) | |
| - Trigonometry | |
| - Geometry | |
| - Statistics and Probability | |
| - Number Theory | |
| - And many more! | |
| ### Required Dependencies | |
| To run this application, you'll need to install: | |
| ```bash | |
| pip install gradio openai together pillow markdown weasyprint | |
| ``` | |
| """) | |
| # Footer | |
| gr.Markdown(""" | |
| --- | |
| ### About | |
| This enhanced application uses Microsoft's Phi-4-reasoning-plus model via OpenRouter for text-based problems | |
| and Llama-Vision-Free via Together AI for image-based problems. | |
| **New Features:** | |
| - HTML-formatted responses with professional styling | |
| - Download solutions as HTML files | |
| - Convert and download solutions as PDF files | |
| - Print-to-PDF functionality | |
| - Enhanced formatting with mathematical expressions highlighting | |
| Your API keys are required but not stored permanently. | |
| """) | |
| return demo | |
| # Launch the app | |
| if __name__ == "__main__": | |
| demo = create_demo() | |
| demo.launch() |