Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import tempfile | |
| import os | |
| import logging | |
| from pathlib import Path | |
| from PIL import Image | |
| import io | |
| import numpy as np | |
| import sys | |
| import subprocess | |
| import json | |
| from pygments import highlight | |
| from pygments.lexers import PythonLexer, CppLexer | |
| from pygments.formatters import HtmlFormatter | |
| import base64 | |
| from transformers import pipeline | |
| import re | |
| import shutil | |
| import time | |
| from datetime import datetime, timedelta | |
| import streamlit.components.v1 as components | |
| import uuid | |
| import platform | |
| import pandas as pd | |
| import plotly.express as px | |
| import markdown | |
| import zipfile | |
| import contextlib | |
| import threading | |
| import traceback | |
| from io import StringIO, BytesIO | |
| # Set up enhanced logging | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', | |
| handlers=[ | |
| logging.StreamHandler() | |
| ] | |
| ) | |
| logger = logging.getLogger(__name__) | |
| # Check if sudo is available on the system | |
| def is_sudo_available(): | |
| """Check if sudo command is available on the system""" | |
| if platform.system() == "Windows": | |
| return False # Windows doesn't use sudo | |
| try: | |
| result = subprocess.run( | |
| ["which", "sudo"], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| return result.returncode == 0 | |
| except Exception: | |
| return False | |
| # Try to use sudo if available, with password prompt if needed | |
| def run_with_sudo(command, password=None): | |
| """Run a command with sudo if available, with optional password""" | |
| if not is_sudo_available(): | |
| # Fall back to running without sudo | |
| return subprocess.run(command, capture_output=True, text=True) | |
| # Prepare sudo command | |
| sudo_cmd = ["sudo", "-S"] + command | |
| try: | |
| if password: | |
| # Run with provided password | |
| process = subprocess.Popen( | |
| sudo_cmd, | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| text=True | |
| ) | |
| stdout, stderr = process.communicate(input=password + "\n") | |
| return subprocess.CompletedProcess( | |
| sudo_cmd, process.returncode, stdout, stderr | |
| ) | |
| else: | |
| # Run without password (relies on cached sudo credentials) | |
| return subprocess.run(sudo_cmd, capture_output=True, text=True) | |
| except Exception as e: | |
| logger.error(f"Error running sudo command: {str(e)}") | |
| # Fall back to running without sudo | |
| return subprocess.run(command, capture_output=True, text=True) | |
| # Model configuration mapping for different API requirements and limits | |
| MODEL_CONFIGS = { | |
| "DeepSeek-V3-0324": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "DeepSeek", "warning": None}, | |
| "DeepSeek-R1": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "DeepSeek", "warning": None}, | |
| "gpt-4o": {"max_tokens": 16000, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, | |
| "gpt-4.1": {"max_tokens": 32768, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, | |
| "gpt-4.1-mini": {"max_tokens": 32768, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, | |
| "gpt-4.1-nano": {"max_tokens": 32768, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, | |
| "o3": {"max_tokens": 100000, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, | |
| "o4-mini": {"max_tokens": 100000, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, | |
| # Default configuration for other models | |
| "default": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Other", "warning": None} | |
| } | |
| # Try to import Streamlit Ace | |
| try: | |
| from streamlit_ace import st_ace | |
| ACE_EDITOR_AVAILABLE = True | |
| except ImportError: | |
| ACE_EDITOR_AVAILABLE = False | |
| logger.warning("streamlit-ace not available, falling back to standard text editor") | |
| def prepare_api_params(messages, model_name): | |
| """Create appropriate API parameters based on model configuration""" | |
| # Get model configuration | |
| config = MODEL_CONFIGS.get(model_name, MODEL_CONFIGS["default"]) | |
| # Base parameters common to all models | |
| api_params = { | |
| "messages": messages, | |
| "model": model_name | |
| } | |
| # Add the appropriate token parameter based on model's parameter name | |
| token_param = config["param_name"] | |
| token_value = config[token_param] # Get the actual value from the config | |
| # Add the parameter to the API params | |
| api_params[token_param] = token_value | |
| return api_params, config | |
| # New functions for accessing secrets and password verification | |
| def get_secret(github_token_api): | |
| """Retrieve a secret from HuggingFace Spaces environment variables""" | |
| secret_value = os.environ.get(github_token_api) | |
| if not secret_value: | |
| logger.warning(f"Secret '{github_token_api}' not found") | |
| return None | |
| return secret_value | |
| def check_password(): | |
| """Returns True if the user entered the correct password""" | |
| # Get the password from secrets | |
| correct_password = get_secret("password") | |
| if not correct_password: | |
| st.error("Admin password not configured in HuggingFace Spaces secrets") | |
| return False | |
| # Password input | |
| if "password_entered" not in st.session_state: | |
| st.session_state.password_entered = False | |
| if not st.session_state.password_entered: | |
| password = st.text_input("Enter password to access AI features", type="password") | |
| if password: | |
| if password == correct_password: | |
| st.session_state.password_entered = True | |
| return True | |
| else: | |
| st.error("Incorrect password") | |
| return False | |
| return False | |
| return True | |
| # Enhanced package management | |
| def ensure_packages(): | |
| """Install required packages with sudo if available""" | |
| required_packages = { | |
| 'manim': '0.17.3', | |
| 'Pillow': '9.0.0', | |
| 'numpy': '1.22.0', | |
| 'transformers': '4.30.0', | |
| 'torch': '2.0.0', | |
| 'pygments': '2.15.1', | |
| 'streamlit-ace': '0.1.1', | |
| 'pydub': '0.25.1', | |
| 'plotly': '5.14.0', | |
| 'pandas': '2.0.0', | |
| 'python-pptx': '0.6.21', | |
| 'markdown': '3.4.3', | |
| 'fpdf': '1.7.2', | |
| 'matplotlib': '3.5.0', | |
| 'seaborn': '0.11.2', | |
| 'scipy': '1.7.3', | |
| 'huggingface_hub': '0.16.0', | |
| 'azure-ai-inference': '1.0.0b9', | |
| 'azure-core': '1.33.0', | |
| 'openai': '1.0.0' | |
| } | |
| # System dependencies for manim (Ubuntu/Debian-based systems) | |
| system_dependencies = [ | |
| "libcairo2-dev", | |
| "pkg-config", | |
| "python3-dev", | |
| "libpango1.0-dev", | |
| "ffmpeg", | |
| "texlive-latex-recommended", | |
| "texlive-fonts-recommended", | |
| "texlive-latex-extra", | |
| "fonts-dejavu-core", | |
| "libsndfile1" | |
| ] | |
| with st.spinner("Checking and installing system dependencies..."): | |
| # Check if we're on a system that uses apt | |
| apt_available = False | |
| try: | |
| result = subprocess.run( | |
| ["which", "apt-get"], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| apt_available = result.returncode == 0 | |
| except Exception: | |
| apt_available = False | |
| if apt_available: | |
| # Install system dependencies | |
| progress_bar = st.progress(0) | |
| status_text = st.empty() | |
| # Update apt | |
| status_text.text("Updating package lists...") | |
| try: | |
| # First try with sudo | |
| sudo_password = None | |
| if is_sudo_available(): | |
| sudo_password = st.text_input("Enter sudo password for system package installation:", type="password") | |
| if sudo_password: | |
| run_with_sudo(["apt-get", "update"], sudo_password) | |
| else: | |
| # Try without password (cached sudo credentials) | |
| run_with_sudo(["apt-get", "update"]) | |
| else: | |
| # Try without sudo | |
| subprocess.run(["apt-get", "update"], capture_output=True) | |
| except Exception as e: | |
| logger.warning(f"Error updating apt: {str(e)}") | |
| # Install each dependency | |
| for i, package in enumerate(system_dependencies): | |
| progress = (i / len(system_dependencies)) | |
| progress_bar.progress(progress) | |
| status_text.text(f"Installing system dependency: {package}...") | |
| try: | |
| # Try with sudo | |
| if is_sudo_available() and sudo_password: | |
| result = run_with_sudo( | |
| ["apt-get", "install", "-y", package], | |
| sudo_password | |
| ) | |
| else: | |
| # Try without sudo | |
| result = subprocess.run( | |
| ["apt-get", "install", "-y", package], | |
| capture_output=True, | |
| text=True | |
| ) | |
| if result.returncode != 0: | |
| logger.warning(f"Could not install system package {package}: {result.stderr}") | |
| except Exception as e: | |
| logger.warning(f"Error installing system package {package}: {str(e)}") | |
| progress_bar.progress(1.0) | |
| status_text.text("System dependencies installation complete!") | |
| time.sleep(0.5) | |
| progress_bar.empty() | |
| status_text.empty() | |
| else: | |
| # If not on an apt-based system, show message | |
| st.warning("System dependencies may need to be installed manually. See the documentation for details.") | |
| # Check and install Python packages | |
| with st.spinner("Checking required Python packages..."): | |
| # First, quickly check if packages are already installed | |
| missing_packages = {} | |
| for package, version in required_packages.items(): | |
| try: | |
| # Try to import the package to check if it's available | |
| if package == 'manim': | |
| import manim | |
| elif package == 'Pillow': | |
| import PIL | |
| elif package == 'numpy': | |
| import numpy | |
| elif package == 'transformers': | |
| import transformers | |
| elif package == 'torch': | |
| import torch | |
| elif package == 'pygments': | |
| import pygments | |
| elif package == 'streamlit-ace': | |
| # This one is trickier, we already handle it with ACE_EDITOR_AVAILABLE flag | |
| pass | |
| elif package == 'pydub': | |
| import pydub | |
| elif package == 'plotly': | |
| import plotly | |
| elif package == 'pandas': | |
| import pandas | |
| elif package == 'python-pptx': | |
| import pptx | |
| elif package == 'markdown': | |
| import markdown | |
| elif package == 'fpdf': | |
| import fpdf | |
| elif package == 'matplotlib': | |
| import matplotlib | |
| elif package == 'seaborn': | |
| import seaborn | |
| elif package == 'scipy': | |
| import scipy | |
| elif package == 'huggingface_hub': | |
| import huggingface_hub | |
| elif package == 'azure-ai-inference': | |
| import azure.ai.inference | |
| elif package == 'azure-core': | |
| import azure.core | |
| elif package == 'openai': | |
| import openai | |
| except ImportError: | |
| missing_packages[package] = version | |
| # If no packages are missing, return success immediately | |
| if not missing_packages: | |
| logger.info("All required Python packages already installed.") | |
| return True | |
| # If there are missing packages, install them with progress reporting | |
| progress_bar = st.progress(0) | |
| status_text = st.empty() | |
| # Check if pip install requires sudo | |
| pip_requires_sudo = False | |
| try: | |
| # Try to write to site-packages | |
| import site | |
| site_packages = site.getsitepackages()[0] | |
| # Check if we have write access | |
| test_file = os.path.join(site_packages, "test_write_access.txt") | |
| try: | |
| with open(test_file, "w") as f: | |
| f.write("test") | |
| os.remove(test_file) | |
| except (PermissionError, OSError): | |
| pip_requires_sudo = True | |
| except Exception: | |
| # If anything goes wrong, assume we might need sudo | |
| pip_requires_sudo = True | |
| # Ask for sudo password if needed | |
| sudo_password = None | |
| if pip_requires_sudo and is_sudo_available(): | |
| sudo_password = st.text_input("Enter sudo password for Python package installation:", type="password") | |
| for i, (package, version) in enumerate(missing_packages.items()): | |
| try: | |
| progress = (i / len(missing_packages)) | |
| progress_bar.progress(progress) | |
| status_text.text(f"Installing {package}...") | |
| pip_install_cmd = [sys.executable, "-m", "pip", "install", f"{package}>={version}"] | |
| if pip_requires_sudo and is_sudo_available(): | |
| # Use sudo for pip install | |
| if sudo_password: | |
| result = run_with_sudo(pip_install_cmd, sudo_password) | |
| else: | |
| # Try without password (cached sudo credentials) | |
| result = run_with_sudo(pip_install_cmd) | |
| else: | |
| # Use normal pip install | |
| result = subprocess.run( | |
| pip_install_cmd, | |
| capture_output=True, | |
| text=True | |
| ) | |
| if result.returncode != 0: | |
| st.error(f"Failed to install {package}: {result.stderr}") | |
| logger.error(f"Package installation failed: {package}") | |
| return False | |
| except Exception as e: | |
| st.error(f"Error installing {package}: {str(e)}") | |
| logger.error(f"Package installation error: {str(e)}") | |
| return False | |
| progress_bar.progress(1.0) | |
| status_text.text("All Python packages installed successfully!") | |
| time.sleep(0.5) | |
| progress_bar.empty() | |
| status_text.empty() | |
| return True | |
| def install_custom_packages(package_list): | |
| """Install custom packages specified by the user with sudo if needed""" | |
| if not package_list.strip(): | |
| return True, "No packages specified" | |
| # Split and clean package list | |
| packages = [pkg.strip() for pkg in package_list.split(',') if pkg.strip()] | |
| if not packages: | |
| return True, "No valid packages specified" | |
| status_placeholder = st.sidebar.empty() | |
| progress_bar = st.sidebar.progress(0) | |
| # Check if pip install requires sudo | |
| pip_requires_sudo = False | |
| try: | |
| # Try to write to site-packages | |
| import site | |
| site_packages = site.getsitepackages()[0] | |
| # Check if we have write access | |
| test_file = os.path.join(site_packages, "test_write_access.txt") | |
| try: | |
| with open(test_file, "w") as f: | |
| f.write("test") | |
| os.remove(test_file) | |
| except (PermissionError, OSError): | |
| pip_requires_sudo = True | |
| except Exception: | |
| # If anything goes wrong, assume we might need sudo | |
| pip_requires_sudo = True | |
| # Ask for sudo password if needed | |
| sudo_password = None | |
| if pip_requires_sudo and is_sudo_available(): | |
| sudo_password = st.text_input("Enter sudo password for custom package installation:", type="password") | |
| results = [] | |
| success = True | |
| for i, package in enumerate(packages): | |
| try: | |
| progress = (i / len(packages)) | |
| progress_bar.progress(progress) | |
| status_placeholder.text(f"Installing {package}...") | |
| pip_install_cmd = [sys.executable, "-m", "pip", "install", package] | |
| if pip_requires_sudo and is_sudo_available(): | |
| # Use sudo for pip install | |
| if sudo_password: | |
| result = run_with_sudo(pip_install_cmd, sudo_password) | |
| else: | |
| # Try without password (cached sudo credentials) | |
| result = run_with_sudo(pip_install_cmd) | |
| else: | |
| # Use normal pip install | |
| result = subprocess.run( | |
| pip_install_cmd, | |
| capture_output=True, | |
| text=True | |
| ) | |
| if result.returncode != 0: | |
| error_msg = f"Failed to install {package}: {result.stderr}" | |
| results.append(error_msg) | |
| logger.error(error_msg) | |
| success = False | |
| else: | |
| results.append(f"Successfully installed {package}") | |
| logger.info(f"Successfully installed custom package: {package}") | |
| except Exception as e: | |
| error_msg = f"Error installing {package}: {str(e)}" | |
| results.append(error_msg) | |
| logger.error(error_msg) | |
| success = False | |
| progress_bar.progress(1.0) | |
| status_placeholder.text("Installation complete!") | |
| time.sleep(0.5) | |
| progress_bar.empty() | |
| status_placeholder.empty() | |
| return success, "\n".join(results) | |
| # Install C/C++ libraries with sudo if needed | |
| def install_cpp_libraries(libraries): | |
| """Install C/C++ libraries using system package manager with sudo if needed""" | |
| if not libraries: | |
| return True, "No libraries specified" | |
| # Library to package mappings for different systems | |
| library_packages = { | |
| "Ubuntu": { | |
| "Eigen": ["libeigen3-dev"], | |
| "Boost": ["libboost-all-dev"], | |
| "OpenCV": ["libopencv-dev", "python3-opencv"], | |
| "FFTW": ["libfftw3-dev"], | |
| "SDL2": ["libsdl2-dev"], | |
| "SFML": ["libsfml-dev"], | |
| "OpenGL": ["libgl1-mesa-dev", "libglu1-mesa-dev", "freeglut3-dev"] | |
| }, | |
| "Debian": { | |
| "Eigen": ["libeigen3-dev"], | |
| "Boost": ["libboost-all-dev"], | |
| "OpenCV": ["libopencv-dev", "python3-opencv"], | |
| "FFTW": ["libfftw3-dev"], | |
| "SDL2": ["libsdl2-dev"], | |
| "SFML": ["libsfml-dev"], | |
| "OpenGL": ["libgl1-mesa-dev", "libglu1-mesa-dev", "freeglut3-dev"] | |
| }, | |
| "Fedora": { | |
| "Eigen": ["eigen3-devel"], | |
| "Boost": ["boost-devel"], | |
| "OpenCV": ["opencv-devel", "python3-opencv"], | |
| "FFTW": ["fftw-devel"], | |
| "SDL2": ["SDL2-devel"], | |
| "SFML": ["SFML-devel"], | |
| "OpenGL": ["mesa-libGL-devel", "mesa-libGLU-devel", "freeglut-devel"] | |
| }, | |
| "CentOS": { | |
| "Eigen": ["eigen3-devel"], | |
| "Boost": ["boost-devel"], | |
| "OpenCV": ["opencv-devel"], | |
| "FFTW": ["fftw-devel"], | |
| "SDL2": ["SDL2-devel"], | |
| "SFML": ["SFML-devel"], | |
| "OpenGL": ["mesa-libGL-devel", "mesa-libGLU-devel", "freeglut-devel"] | |
| }, | |
| "Arch": { | |
| "Eigen": ["eigen"], | |
| "Boost": ["boost"], | |
| "OpenCV": ["opencv"], | |
| "FFTW": ["fftw"], | |
| "SDL2": ["sdl2"], | |
| "SFML": ["sfml"], | |
| "OpenGL": ["mesa", "glu", "freeglut"] | |
| }, | |
| "MacOS": { | |
| "Eigen": ["eigen"], | |
| "Boost": ["boost"], | |
| "OpenCV": ["opencv"], | |
| "FFTW": ["fftw"], | |
| "SDL2": ["sdl2"], | |
| "SFML": ["sfml"], | |
| "OpenGL": ["mesa", "freeglut"] | |
| } | |
| } | |
| # Detect OS | |
| os_name = "Unknown" | |
| package_manager = None | |
| install_cmd = [] | |
| if platform.system() == "Linux": | |
| # Try to detect Linux distribution | |
| try: | |
| if os.path.exists("/etc/os-release"): | |
| with open("/etc/os-release", "r") as f: | |
| os_release = f.read() | |
| if "Ubuntu" in os_release: | |
| os_name = "Ubuntu" | |
| package_manager = "apt-get" | |
| install_cmd = ["apt-get", "install", "-y"] | |
| elif "Debian" in os_release: | |
| os_name = "Debian" | |
| package_manager = "apt-get" | |
| install_cmd = ["apt-get", "install", "-y"] | |
| elif "Fedora" in os_release: | |
| os_name = "Fedora" | |
| package_manager = "dnf" | |
| install_cmd = ["dnf", "install", "-y"] | |
| elif "CentOS" in os_release: | |
| os_name = "CentOS" | |
| package_manager = "yum" | |
| install_cmd = ["yum", "install", "-y"] | |
| elif "Arch" in os_release: | |
| os_name = "Arch" | |
| package_manager = "pacman" | |
| install_cmd = ["pacman", "-S", "--noconfirm"] | |
| # Fallback detection | |
| if os_name == "Unknown": | |
| which_apt = subprocess.run(["which", "apt-get"], capture_output=True, text=True) | |
| which_dnf = subprocess.run(["which", "dnf"], capture_output=True, text=True) | |
| which_yum = subprocess.run(["which", "yum"], capture_output=True, text=True) | |
| which_pacman = subprocess.run(["which", "pacman"], capture_output=True, text=True) | |
| if which_apt.returncode == 0: | |
| os_name = "Debian" | |
| package_manager = "apt-get" | |
| install_cmd = ["apt-get", "install", "-y"] | |
| elif which_dnf.returncode == 0: | |
| os_name = "Fedora" | |
| package_manager = "dnf" | |
| install_cmd = ["dnf", "install", "-y"] | |
| elif which_yum.returncode == 0: | |
| os_name = "CentOS" | |
| package_manager = "yum" | |
| install_cmd = ["yum", "install", "-y"] | |
| elif which_pacman.returncode == 0: | |
| os_name = "Arch" | |
| package_manager = "pacman" | |
| install_cmd = ["pacman", "-S", "--noconfirm"] | |
| except Exception as e: | |
| logger.error(f"Error detecting Linux distribution: {str(e)}") | |
| elif platform.system() == "Darwin": | |
| os_name = "MacOS" | |
| which_brew = subprocess.run(["which", "brew"], capture_output=True, text=True) | |
| if which_brew.returncode == 0: | |
| package_manager = "brew" | |
| install_cmd = ["brew", "install"] | |
| # If package manager not detected, return error | |
| if not package_manager: | |
| return False, f"Could not detect package manager for {platform.system()}. Please install libraries manually." | |
| # Get packages to install | |
| all_packages = [] | |
| for library in libraries: | |
| if os_name in library_packages and library in library_packages[os_name]: | |
| all_packages.extend(library_packages[os_name][library]) | |
| if not all_packages: | |
| return False, f"No packages found for the selected libraries on {os_name}. Please install libraries manually." | |
| # Display progress | |
| status_placeholder = st.sidebar.empty() | |
| progress_bar = st.sidebar.progress(0) | |
| # Ask for sudo password if needed (most package managers need sudo) | |
| sudo_password = None | |
| if is_sudo_available() and platform.system() != "Darwin": # macOS Homebrew doesn't need sudo | |
| sudo_password = st.text_input("Enter sudo password for C/C++ library installation:", type="password") | |
| results = [] | |
| success = True | |
| # Update package lists if needed | |
| if package_manager in ["apt-get", "apt"]: | |
| status_placeholder.text("Updating package lists...") | |
| try: | |
| if is_sudo_available() and sudo_password: | |
| result = run_with_sudo(["apt-get", "update"], sudo_password) | |
| elif is_sudo_available(): | |
| result = run_with_sudo(["apt-get", "update"]) | |
| else: | |
| result = subprocess.run(["apt-get", "update"], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| logger.warning(f"Failed to update package lists: {result.stderr}") | |
| results.append(f"Warning: Failed to update package lists: {result.stderr}") | |
| except Exception as e: | |
| logger.warning(f"Error updating package lists: {str(e)}") | |
| results.append(f"Warning: Error updating package lists: {str(e)}") | |
| # Install each package | |
| for i, package in enumerate(all_packages): | |
| try: | |
| progress = (i / len(all_packages)) | |
| progress_bar.progress(progress) | |
| status_placeholder.text(f"Installing {package}...") | |
| cmd = install_cmd + [package] | |
| if is_sudo_available() and platform.system() != "Darwin": # macOS Homebrew doesn't need sudo | |
| if sudo_password: | |
| result = run_with_sudo(cmd, sudo_password) | |
| else: | |
| result = run_with_sudo(cmd) | |
| else: | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| if result.returncode != 0: | |
| error_msg = f"Failed to install {package}: {result.stderr}" | |
| results.append(error_msg) | |
| logger.error(error_msg) | |
| success = False | |
| else: | |
| results.append(f"Successfully installed {package}") | |
| logger.info(f"Successfully installed C/C++ library package: {package}") | |
| except Exception as e: | |
| error_msg = f"Error installing {package}: {str(e)}" | |
| results.append(error_msg) | |
| logger.error(error_msg) | |
| success = False | |
| progress_bar.progress(1.0) | |
| status_placeholder.text("Installation complete!") | |
| time.sleep(0.5) | |
| progress_bar.empty() | |
| status_placeholder.empty() | |
| return success, "\n".join(results) | |
| # Auto-detect C/C++ libraries | |
| def detect_cpp_libraries(): | |
| """Auto-detect installed C/C++ libraries on the system""" | |
| libraries = {} | |
| # Function to check if a library is installed | |
| def check_library(name, headers, pkg_config=None): | |
| # Check if headers exist | |
| header_found = False | |
| for header in headers: | |
| # Common include directories | |
| include_dirs = [ | |
| "/usr/include", | |
| "/usr/local/include", | |
| "/opt/local/include", | |
| "/opt/homebrew/include" | |
| ] | |
| for include_dir in include_dirs: | |
| if os.path.exists(os.path.join(include_dir, header)): | |
| header_found = True | |
| break | |
| # Check using pkg-config if available | |
| pkg_config_found = False | |
| if pkg_config: | |
| try: | |
| result = subprocess.run( | |
| ["pkg-config", "--exists", pkg_config], | |
| capture_output=True, | |
| check=False | |
| ) | |
| pkg_config_found = result.returncode == 0 | |
| except Exception: | |
| pass | |
| return header_found or pkg_config_found | |
| # Check for common libraries | |
| libraries["Eigen"] = check_library("Eigen", ["Eigen/Core", "eigen3/Eigen/Core"]) | |
| libraries["Boost"] = check_library("Boost", ["boost/config.hpp", "boost/version.hpp"]) | |
| libraries["OpenCV"] = check_library("OpenCV", ["opencv2/opencv.hpp"], "opencv4") | |
| libraries["FFTW"] = check_library("FFTW", ["fftw3.h"], "fftw3") | |
| libraries["SDL2"] = check_library("SDL2", ["SDL2/SDL.h"], "sdl2") | |
| libraries["SFML"] = check_library("SFML", ["SFML/Graphics.hpp"], "sfml-all") | |
| libraries["OpenGL"] = check_library("OpenGL", ["GL/gl.h", "OpenGL/gl.h"]) | |
| return libraries | |
| def init_ai_models_direct(): | |
| """Direct implementation using the exact pattern from the example code""" | |
| try: | |
| # Get token from secrets | |
| token = get_secret("github_token_api") | |
| if not token: | |
| st.error("GitHub token not found in secrets. Please add 'github_token_api' to your HuggingFace Spaces secrets.") | |
| return None | |
| # Log what we're doing - for debugging | |
| logger.info(f"Initializing AI model with token: {token[:5]}...") | |
| # Use exact imports as in your example | |
| import os | |
| from azure.ai.inference import ChatCompletionsClient | |
| from azure.ai.inference.models import SystemMessage, UserMessage | |
| from azure.core.credentials import AzureKeyCredential | |
| # Use exact endpoint as in your example | |
| endpoint = "https://models.inference.ai.azure.com" | |
| # Use default model | |
| model_name = "gpt-4o" | |
| # Create client exactly as in your example | |
| client = ChatCompletionsClient( | |
| endpoint=endpoint, | |
| credential=AzureKeyCredential(token), | |
| ) | |
| # Return the necessary information | |
| return { | |
| "client": client, | |
| "model_name": model_name, | |
| "endpoint": endpoint | |
| } | |
| except ImportError as ie: | |
| st.error(f"Import error: {str(ie)}. Please make sure azure-ai-inference is installed.") | |
| logger.error(f"Import error: {str(ie)}") | |
| return None | |
| except Exception as e: | |
| st.error(f"Error initializing AI model: {str(e)}") | |
| logger.error(f"Initialization error: {str(e)}") | |
| return None | |
| def suggest_code_completion(code_snippet, models): | |
| """Generate code completion using the AI model""" | |
| if not models: | |
| st.error("AI models not properly initialized.") | |
| return None | |
| try: | |
| # Create the prompt | |
| prompt = f"""Write a complete Manim animation scene based on this code or idea: | |
| {code_snippet} | |
| The code should be a complete, working Manim animation that includes: | |
| - Proper Scene class definition | |
| - Constructor with animations | |
| - Proper use of self.play() for animations | |
| - Proper wait times between animations | |
| Here's the complete Manim code: | |
| """ | |
| with st.spinner("AI is generating your animation code..."): | |
| # Get the current model name and base URL | |
| model_name = models["model_name"] | |
| # Convert message to the appropriate format based on model category | |
| config = MODEL_CONFIGS.get(model_name, MODEL_CONFIGS["default"]) | |
| category = config.get("category", "Other") | |
| if category == "OpenAI": | |
| # Import OpenAI client | |
| from openai import OpenAI | |
| # Get token | |
| token = get_secret("github_token_api") | |
| # Create or get client | |
| if "openai_client" not in models: | |
| client = OpenAI( | |
| base_url="https://models.github.ai/inference", | |
| api_key=token | |
| ) | |
| models["openai_client"] = client | |
| else: | |
| client = models["openai_client"] | |
| # For OpenAI models, we need role-based messages | |
| messages = [ | |
| {"role": "system", "content": "You are an expert in Manim animations."}, | |
| {"role": "user", "content": prompt} | |
| ] | |
| # Create params | |
| params = { | |
| "messages": messages, | |
| "model": model_name | |
| } | |
| # Add token parameter | |
| token_param = config["param_name"] | |
| params[token_param] = config[token_param] | |
| # Make API call | |
| response = client.chat.completions.create(**params) | |
| completed_code = response.choices[0].message.content | |
| else: | |
| # Use Azure client | |
| from azure.ai.inference.models import UserMessage | |
| # Convert message format for Azure | |
| messages = [UserMessage(prompt)] | |
| api_params, _ = prepare_api_params(messages, model_name) | |
| # Make API call with Azure client | |
| response = models["client"].complete(**api_params) | |
| completed_code = response.choices[0].message.content | |
| # Process the code | |
| if "```python" in completed_code: | |
| completed_code = completed_code.split("```python")[1].split("```")[0] | |
| elif "```" in completed_code: | |
| completed_code = completed_code.split("```")[1].split("```")[0] | |
| # Add Scene class if missing | |
| if "Scene" not in completed_code: | |
| completed_code = f"""from manim import * | |
| class MyScene(Scene): | |
| def construct(self): | |
| {completed_code}""" | |
| return completed_code | |
| except Exception as e: | |
| st.error(f"Error generating code: {str(e)}") | |
| st.code(traceback.format_exc()) | |
| return None | |
| def check_model_freshness(): | |
| """Check if models need to be reloaded based on TTL""" | |
| if 'ai_models' not in st.session_state or st.session_state.ai_models is None: | |
| return False | |
| if 'last_loaded' not in st.session_state.ai_models: | |
| return False | |
| last_loaded = datetime.fromisoformat(st.session_state.ai_models['last_loaded']) | |
| ttl_hours = 1 # 1 hour TTL | |
| return datetime.now() - last_loaded < timedelta(hours=ttl_hours) | |
| def extract_scene_class_name(python_code): | |
| """Extract the scene class name from Python code.""" | |
| import re | |
| scene_classes = re.findall(r'class\s+(\w+)\s*\([^)]*Scene[^)]*\)', python_code) | |
| if scene_classes: | |
| # Return the first scene class found | |
| return scene_classes[0] | |
| else: | |
| # If no scene class is found, use a default name | |
| return "MyScene" | |
| def suggest_code_completion(code_snippet, models): | |
| if not models or "code_model" not in models: | |
| st.error("AI models not properly initialized") | |
| return None | |
| try: | |
| prompt = f"""Write a complete Manim animation scene based on this code or idea: | |
| {code_snippet} | |
| The code should be a complete, working Manim animation that includes: | |
| - Proper Scene class definition | |
| - Constructor with animations | |
| - Proper use of self.play() for animations | |
| - Proper wait times between animations | |
| Here's the complete Manim code: | |
| ```python | |
| """ | |
| with st.spinner("AI is generating your animation code..."): | |
| response = models["code_model"]( | |
| prompt, | |
| max_length=1024, | |
| do_sample=True, | |
| temperature=0.2, | |
| top_p=0.95, | |
| top_k=50, | |
| num_return_sequences=1, | |
| truncation=True, | |
| pad_token_id=50256 | |
| ) | |
| if not response or not response[0].get('generated_text'): | |
| st.error("No valid completion generated") | |
| return None | |
| completed_code = response[0]['generated_text'] | |
| if "```python" in completed_code: | |
| completed_code = completed_code.split("```python")[1].split("```")[0] | |
| if "Scene" not in completed_code: | |
| completed_code = f"""from manim import * | |
| class MyScene(Scene): | |
| def construct(self): | |
| {completed_code}""" | |
| return completed_code | |
| except Exception as e: | |
| st.error(f"Error suggesting code: {str(e)}") | |
| logger.error(f"Code suggestion error: {str(e)}") | |
| return None | |
| # Quality presets | |
| QUALITY_PRESETS = { | |
| "480p": {"resolution": "480p", "fps": "30"}, | |
| "720p": {"resolution": "720p", "fps": "30"}, | |
| "1080p": {"resolution": "1080p", "fps": "60"}, | |
| "4K": {"resolution": "2160p", "fps": "60"}, | |
| "8K": {"resolution": "4320p", "fps": "60"} # Added 8K option | |
| } | |
| # Animation speeds | |
| ANIMATION_SPEEDS = { | |
| "Slow": 0.5, | |
| "Normal": 1.0, | |
| "Fast": 2.0, | |
| "Very Fast": 3.0 | |
| } | |
| # Export formats | |
| EXPORT_FORMATS = { | |
| "MP4 Video": "mp4", | |
| "GIF Animation": "gif", | |
| "WebM Video": "webm", | |
| "PNG Image Sequence": "png_sequence", | |
| "SVG Image": "svg" | |
| } | |
| # FPS options | |
| FPS_OPTIONS = [15, 24, 30, 60, 120] | |
| def highlight_code(code): | |
| formatter = HtmlFormatter(style='monokai') | |
| highlighted = highlight(code, PythonLexer(), formatter) | |
| return highlighted, formatter.get_style_defs() | |
| def generate_manim_preview(python_code): | |
| """Generate a lightweight preview of the Manim animation""" | |
| try: | |
| # Extract scene components for preview | |
| scene_objects = [] | |
| if "Circle" in python_code: | |
| scene_objects.append("circle") | |
| if "Square" in python_code: | |
| scene_objects.append("square") | |
| if "MathTex" in python_code or "Tex" in python_code: | |
| scene_objects.append("equation") | |
| if "Text" in python_code: | |
| scene_objects.append("text") | |
| if "Axes" in python_code: | |
| scene_objects.append("graph") | |
| if "ThreeDScene" in python_code or "ThreeDAxes" in python_code: | |
| scene_objects.append("3D scene") | |
| if "Sphere" in python_code: | |
| scene_objects.append("sphere") | |
| if "Cube" in python_code: | |
| scene_objects.append("cube") | |
| # Generate a more detailed visual preview based on extracted objects | |
| object_icons = { | |
| "circle": "⭕", | |
| "square": "🔲", | |
| "equation": "📊", | |
| "text": "📝", | |
| "graph": "📈", | |
| "3D scene": "🧊", | |
| "sphere": "🌐", | |
| "cube": "🧊" | |
| } | |
| icon_html = "" | |
| for obj in scene_objects: | |
| if obj in object_icons: | |
| icon_html += f'<span style="font-size:2rem; margin:0.3rem;">{object_icons[obj]}</span>' | |
| preview_html = f""" | |
| <div style="background-color:#000000; width:100%; height:220px; border-radius:10px; display:flex; flex-direction:column; align-items:center; justify-content:center; color:white; text-align:center;"> | |
| <h3 style="margin-bottom:10px;">Animation Preview</h3> | |
| <div style="margin-bottom:15px;"> | |
| {icon_html if icon_html else '<span style="font-size:2rem;">🎬</span>'} | |
| </div> | |
| <p>Scene contains: {', '.join(scene_objects) if scene_objects else 'No detected objects'}</p> | |
| <div style="margin-top:10px; font-size:0.8rem; opacity:0.8;">Full rendering required for accurate preview</div> | |
| </div> | |
| """ | |
| return preview_html | |
| except Exception as e: | |
| logger.error(f"Preview generation error: {str(e)}") | |
| return f""" | |
| <div style="background-color:#FF0000; width:100%; height:200px; border-radius:10px; display:flex; align-items:center; justify-content:center; color:white; text-align:center;"> | |
| <div> | |
| <h3>Preview Error</h3> | |
| <p>{str(e)}</p> | |
| </div> | |
| </div> | |
| """ | |
| def prepare_audio_for_manim(audio_file, target_dir): | |
| """Process audio file and return path for use in Manim""" | |
| try: | |
| # Create audio directory if it doesn't exist | |
| audio_dir = os.path.join(target_dir, "audio") | |
| os.makedirs(audio_dir, exist_ok=True) | |
| # Generate a unique filename | |
| filename = f"audio_{int(time.time())}.mp3" | |
| output_path = os.path.join(audio_dir, filename) | |
| # Save audio file | |
| with open(output_path, "wb") as f: | |
| f.write(audio_file.getvalue()) | |
| return output_path | |
| except Exception as e: | |
| logger.error(f"Audio processing error: {str(e)}") | |
| return None | |
| def mp4_to_gif(mp4_path, output_path, fps=15): | |
| """Convert MP4 to GIF using ffmpeg as a backup when Manim fails""" | |
| try: | |
| # Use ffmpeg for conversion with optimized settings | |
| command = [ | |
| "ffmpeg", | |
| "-i", mp4_path, | |
| "-vf", f"fps={fps},scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse", | |
| "-loop", "0", | |
| output_path | |
| ] | |
| # Run the conversion | |
| result = subprocess.run(command, capture_output=True, text=True) | |
| if result.returncode != 0: | |
| logger.error(f"FFmpeg conversion error: {result.stderr}") | |
| return None | |
| return output_path | |
| except Exception as e: | |
| logger.error(f"GIF conversion error: {str(e)}") | |
| return None | |
| def generate_manim_video(python_code, format_type, quality_preset, animation_speed=1.0, audio_path=None, fps=None): | |
| temp_dir = None | |
| progress_placeholder = st.empty() | |
| status_placeholder = st.empty() | |
| log_placeholder = st.empty() | |
| video_data = None # Initialize video data variable | |
| try: | |
| if not python_code or not format_type: | |
| raise ValueError("Missing required parameters") | |
| # Create temporary directory | |
| temp_dir = tempfile.mkdtemp(prefix="manim_render_") | |
| # Extract the scene class name from the code | |
| scene_class = extract_scene_class_name(python_code) | |
| logger.info(f"Detected scene class: {scene_class}") | |
| # If audio is provided, we need to modify the code to include it | |
| if audio_path: | |
| # Check if the code already has a with_sound decorator | |
| if "with_sound" not in python_code: | |
| # Add the necessary import | |
| if "from manim.scene.scene_file_writer import SceneFileWriter" not in python_code: | |
| python_code = "from manim.scene.scene_file_writer import SceneFileWriter\n" + python_code | |
| # Add sound to the scene | |
| scene_def_pattern = f"class {scene_class}\\(.*?\\):" | |
| scene_def_match = re.search(scene_def_pattern, python_code) | |
| if scene_def_match: | |
| scene_def = scene_def_match.group(0) | |
| scene_def_with_sound = f"@with_sound(\"{audio_path}\")\n{scene_def}" | |
| python_code = python_code.replace(scene_def, scene_def_with_sound) | |
| else: | |
| logger.warning("Could not find scene definition to add audio") | |
| # Write the code to a file | |
| scene_file = os.path.join(temp_dir, "scene.py") | |
| with open(scene_file, "w", encoding="utf-8") as f: | |
| f.write(python_code) | |
| # Map quality preset to Manim quality flag | |
| quality_map = { | |
| "480p": "-ql", # Low quality | |
| "720p": "-qm", # Medium quality | |
| "1080p": "-qh", # High quality | |
| "4K": "-qk", # 4K quality | |
| "8K": "-qp" # 8K quality (production quality) | |
| } | |
| quality_flag = quality_map.get(quality_preset, "-qm") | |
| # Handle special formats | |
| if format_type == "png_sequence": | |
| # For PNG sequence, we need additional flags | |
| format_arg = "--format=png" | |
| extra_args = ["--save_pngs"] | |
| elif format_type == "svg": | |
| # For SVG, we need a different format | |
| format_arg = "--format=svg" | |
| extra_args = [] | |
| else: | |
| # Standard video formats | |
| format_arg = f"--format={format_type}" | |
| extra_args = [] | |
| # Add custom FPS if specified | |
| if fps is not None: | |
| extra_args.append(f"--fps={fps}") | |
| # Show status and create progress bar | |
| status_placeholder.info(f"Rendering {scene_class} with {quality_preset} quality...") | |
| progress_bar = progress_placeholder.progress(0) | |
| # Build command | |
| command = [ | |
| "manim", | |
| scene_file, | |
| scene_class, | |
| quality_flag, | |
| format_arg | |
| ] | |
| command.extend(extra_args) | |
| logger.info(f"Running command: {' '.join(command)}") | |
| # Execute the command | |
| process = subprocess.Popen( | |
| command, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| text=True | |
| ) | |
| # Track output | |
| full_output = [] | |
| output_file_path = None | |
| mp4_output_path = None # Track MP4 output for GIF fallback | |
| # Animation tracking variables | |
| total_animations = None | |
| current_animation = 0 | |
| total_frames = None | |
| current_frame = 0 | |
| while True: | |
| line = process.stdout.readline() | |
| if not line and process.poll() is not None: | |
| break | |
| full_output.append(line) | |
| log_placeholder.code("".join(full_output[-10:])) | |
| # Try to detect total animations | |
| if "Rendering animation number" in line or "Processing animation" in line: | |
| try: | |
| # Extract current animation number | |
| anim_match = re.search(r"(?:Rendering animation number|Processing animation) (\d+) (?:out of|/) (\d+)", line) | |
| if anim_match: | |
| current_animation = int(anim_match.group(1)) | |
| total_animations = int(anim_match.group(2)) | |
| logger.info(f"Animation progress: {current_animation}/{total_animations}") | |
| # Calculate progress based on animations | |
| animation_progress = current_animation / total_animations | |
| progress_bar.progress(animation_progress) | |
| status_placeholder.info(f"Rendering {scene_class}: Animation {current_animation}/{total_animations} ({int(animation_progress*100)}%)") | |
| except Exception as e: | |
| logger.error(f"Error parsing animation progress: {str(e)}") | |
| # Try to extract total frames information as fallback | |
| elif "Render animations with total frames:" in line and not total_animations: | |
| try: | |
| total_frames = int(line.split("Render animations with total frames:")[1].strip().split()[0]) | |
| logger.info(f"Total frames to render: {total_frames}") | |
| except Exception as e: | |
| logger.error(f"Error parsing total frames: {str(e)}") | |
| # Update progress bar based on frame information if animation count not available | |
| elif "Rendering frame" in line and total_frames and not total_animations: | |
| try: | |
| # Extract current frame number | |
| frame_match = re.search(r"Rendering frame (\d+)", line) | |
| if frame_match: | |
| current_frame = int(frame_match.group(1)) | |
| # Calculate progress as current frame / total frames | |
| frame_progress = min(0.99, current_frame / total_frames) | |
| progress_bar.progress(frame_progress) | |
| # Update status with frame information | |
| status_placeholder.info(f"Rendering {scene_class}: Frame {current_frame}/{total_frames} ({int(frame_progress*100)}%)") | |
| except Exception as e: | |
| logger.error(f"Error parsing frame progress: {str(e)}") | |
| elif "%" in line and not total_animations and not total_frames: | |
| try: | |
| # Fallback to percentage if available | |
| percent = float(line.split("%")[0].strip().split()[-1]) | |
| progress_bar.progress(min(0.99, percent / 100)) | |
| except: | |
| pass | |
| # Try to capture the output file path from Manim's output | |
| if "File ready at" in line: | |
| try: | |
| # Combine next few lines to get the full path | |
| path_parts = [] | |
| path_parts.append(line.split("File ready at")[-1].strip()) | |
| # Read up to 5 more lines to get the complete path | |
| for _ in range(5): | |
| additional_line = process.stdout.readline() | |
| if additional_line: | |
| full_output.append(additional_line) | |
| path_parts.append(additional_line.strip()) | |
| if additional_line.strip().endswith(('.mp4', '.gif', '.webm', '.svg')): | |
| break | |
| # Join all parts and clean up | |
| potential_path = ''.join(path_parts).replace("'", "").strip() | |
| # Look for path pattern surrounded by quotes | |
| path_match = re.search(r'([\'"]?)((?:/|[a-zA-Z]:\\).*?\.(?:mp4|gif|webm|svg))(\1)', potential_path) | |
| if path_match: | |
| output_file_path = path_match.group(2) | |
| logger.info(f"Found output path in logs: {output_file_path}") | |
| # Track MP4 file for potential GIF fallback | |
| if output_file_path.endswith('.mp4'): | |
| mp4_output_path = output_file_path | |
| except Exception as e: | |
| logger.error(f"Error parsing output path: {str(e)}") | |
| # Wait for the process to complete | |
| process.wait() | |
| progress_bar.progress(1.0) | |
| # IMPORTANT: Wait a moment for file system to catch up | |
| time.sleep(3) | |
| # Special handling for GIF format - if Manim failed to generate a GIF but we have an MP4 | |
| if format_type == "gif" and (not output_file_path or not os.path.exists(output_file_path)) and mp4_output_path and os.path.exists(mp4_output_path): | |
| status_placeholder.info("GIF generation via Manim failed. Trying FFmpeg conversion...") | |
| # Generate a GIF using FFmpeg | |
| gif_output_path = os.path.join(temp_dir, f"{scene_class}_converted.gif") | |
| gif_path = mp4_to_gif(mp4_output_path, gif_output_path, fps=fps if fps else 15) | |
| if gif_path and os.path.exists(gif_path): | |
| output_file_path = gif_path | |
| logger.info(f"Successfully converted MP4 to GIF using FFmpeg: {gif_path}") | |
| # For PNG sequence, we need to collect the PNGs | |
| if format_type == "png_sequence": | |
| # Find the PNG directory | |
| png_dirs = [] | |
| search_dirs = [ | |
| os.path.join(os.getcwd(), "media", "images", scene_class, "Animations"), | |
| os.path.join(temp_dir, "media", "images", scene_class, "Animations"), | |
| "/tmp/media/images", | |
| ] | |
| for search_dir in search_dirs: | |
| if os.path.exists(search_dir): | |
| for root, dirs, _ in os.walk(search_dir): | |
| for d in dirs: | |
| if os.path.exists(os.path.join(root, d)): | |
| png_dirs.append(os.path.join(root, d)) | |
| if png_dirs: | |
| # Get the newest directory | |
| newest_dir = max(png_dirs, key=os.path.getctime) | |
| # Create a zip file with all PNGs | |
| png_files = [f for f in os.listdir(newest_dir) if f.endswith('.png')] | |
| if png_files: | |
| zip_path = os.path.join(temp_dir, f"{scene_class}_pngs.zip") | |
| with zipfile.ZipFile(zip_path, 'w') as zipf: | |
| for png in png_files: | |
| png_path = os.path.join(newest_dir, png) | |
| zipf.write(png_path, os.path.basename(png_path)) | |
| with open(zip_path, 'rb') as f: | |
| video_data = f.read() | |
| logger.info(f"Created PNG sequence zip: {zip_path}") | |
| else: | |
| logger.error("No PNG files found in directory") | |
| else: | |
| logger.error("No PNG directories found") | |
| elif output_file_path and os.path.exists(output_file_path): | |
| # For other formats, read the output file directly | |
| with open(output_file_path, 'rb') as f: | |
| video_data = f.read() | |
| logger.info(f"Read output file from path: {output_file_path}") | |
| else: | |
| # If we didn't find the output path, search for files | |
| search_paths = [ | |
| os.path.join(os.getcwd(), "media", "videos"), | |
| os.path.join(os.getcwd(), "media", "videos", "scene"), | |
| os.path.join(os.getcwd(), "media", "videos", scene_class), | |
| "/tmp/media/videos", | |
| temp_dir, | |
| os.path.join(temp_dir, "media", "videos"), | |
| ] | |
| # Add quality-specific paths | |
| for quality in ["480p30", "720p30", "1080p60", "2160p60", "4320p60"]: | |
| search_paths.append(os.path.join(os.getcwd(), "media", "videos", "scene", quality)) | |
| search_paths.append(os.path.join(os.getcwd(), "media", "videos", scene_class, quality)) | |
| # For SVG format | |
| if format_type == "svg": | |
| search_paths.extend([ | |
| os.path.join(os.getcwd(), "media", "designs"), | |
| os.path.join(os.getcwd(), "media", "designs", scene_class), | |
| ]) | |
| # Find all output files in the search paths | |
| output_files = [] | |
| for search_path in search_paths: | |
| if os.path.exists(search_path): | |
| for root, _, files in os.walk(search_path): | |
| for file in files: | |
| if file.endswith(f".{format_type}") and "partial" not in file: | |
| file_path = os.path.join(root, file) | |
| if os.path.exists(file_path): | |
| output_files.append(file_path) | |
| logger.info(f"Found output file: {file_path}") | |
| if output_files: | |
| # Get the newest file | |
| latest_file = max(output_files, key=os.path.getctime) | |
| with open(latest_file, 'rb') as f: | |
| video_data = f.read() | |
| logger.info(f"Read output from file search: {latest_file}") | |
| # If the format is GIF but we got an MP4, try to convert it | |
| if format_type == "gif" and latest_file.endswith('.mp4'): | |
| gif_output_path = os.path.join(temp_dir, f"{scene_class}_converted.gif") | |
| gif_path = mp4_to_gif(latest_file, gif_output_path, fps=fps if fps else 15) | |
| if gif_path and os.path.exists(gif_path): | |
| with open(gif_path, 'rb') as f: | |
| video_data = f.read() | |
| logger.info(f"Successfully converted MP4 to GIF using FFmpeg: {gif_path}") | |
| # If we got output data, return it | |
| if video_data: | |
| file_size_mb = len(video_data) / (1024 * 1024) | |
| # Clear placeholders | |
| progress_placeholder.empty() | |
| status_placeholder.empty() | |
| log_placeholder.empty() | |
| return video_data, f"✅ Animation generated successfully! ({file_size_mb:.1f} MB)" | |
| else: | |
| output_str = ''.join(full_output) | |
| logger.error(f"No output files found. Full output: {output_str}") | |
| # Check if we have an MP4 but need a GIF (special handling for GIF issues) | |
| if format_type == "gif": | |
| # Try one more aggressive search for any MP4 file | |
| mp4_files = [] | |
| for search_path in [os.getcwd(), temp_dir, "/tmp"]: | |
| for root, _, files in os.walk(search_path): | |
| for file in files: | |
| if file.endswith('.mp4') and scene_class.lower() in file.lower(): | |
| mp4_path = os.path.join(root, file) | |
| if os.path.exists(mp4_path) and os.path.getsize(mp4_path) > 0: | |
| mp4_files.append(mp4_path) | |
| if mp4_files: | |
| newest_mp4 = max(mp4_files, key=os.path.getctime) | |
| logger.info(f"Found MP4 for GIF conversion: {newest_mp4}") | |
| # Convert to GIF | |
| gif_output_path = os.path.join(temp_dir, f"{scene_class}_converted.gif") | |
| gif_path = mp4_to_gif(newest_mp4, gif_output_path, fps=fps if fps else 15) | |
| if gif_path and os.path.exists(gif_path): | |
| with open(gif_path, 'rb') as f: | |
| video_data = f.read() | |
| # Clear placeholders | |
| progress_placeholder.empty() | |
| status_placeholder.empty() | |
| log_placeholder.empty() | |
| file_size_mb = len(video_data) / (1024 * 1024) | |
| return video_data, f"✅ Animation converted to GIF successfully! ({file_size_mb:.1f} MB)" | |
| return None, f"❌ Error: No output files were generated.\n\nMakim output:\n{output_str[:500]}..." | |
| except Exception as e: | |
| logger.error(f"Error: {str(e)}") | |
| import traceback | |
| logger.error(traceback.format_exc()) | |
| if progress_placeholder: | |
| progress_placeholder.empty() | |
| if status_placeholder: | |
| status_placeholder.error(f"Rendering Error: {str(e)}") | |
| if log_placeholder: | |
| log_placeholder.empty() | |
| return None, f"❌ Error: {str(e)}" | |
| finally: | |
| # CRITICAL: Only cleanup after we've captured the output data | |
| if temp_dir and os.path.exists(temp_dir) and video_data is not None: | |
| try: | |
| shutil.rmtree(temp_dir) | |
| logger.info(f"Cleaned up temp dir: {temp_dir}") | |
| except Exception as e: | |
| logger.error(f"Failed to clean temp dir: {str(e)}") | |
| # ENHANCED PYTHON RUNNER FUNCTIONS | |
| def detect_input_calls(code): | |
| """Detect input() calls in Python code to prepare for handling""" | |
| input_calls = [] | |
| lines = code.split('\n') | |
| for i, line in enumerate(lines): | |
| if 'input(' in line and not line.strip().startswith('#'): | |
| # Try to extract the prompt if available | |
| prompt_match = re.search(r'input\([\'"](.+?)[\'"]\)', line) | |
| prompt = prompt_match.group(1) if prompt_match else f"Input for line {i+1}" | |
| input_calls.append({"line": i+1, "prompt": prompt}) | |
| return input_calls | |
| def run_python_script_enhanced(code, inputs=None, timeout=60, enable_debug=False, enable_profile=False, | |
| additional_libs=None, project_files=None, realtime_viz=False): | |
| """Enhanced version of run_python_script with debugging, profiling, etc.""" | |
| result = { | |
| "stdout": "", | |
| "stderr": "", | |
| "exception": None, | |
| "plots": [], | |
| "dataframes": [], | |
| "execution_time": 0, | |
| "profile_data": None, | |
| "debug_steps": [], | |
| "realtime_data": [] | |
| } | |
| # Create a tempdir for script execution | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Path for saving plots | |
| plot_dir = os.path.join(temp_dir, 'plots') | |
| os.makedirs(plot_dir, exist_ok=True) | |
| # Handle multi-file project if provided | |
| if project_files: | |
| for filename, file_content in project_files.items(): | |
| with open(os.path.join(temp_dir, filename), 'w', encoding='utf-8') as f: | |
| f.write(file_content) | |
| # Set the main script path | |
| main_script = os.path.join(temp_dir, "main.py") | |
| else: | |
| # Write the single code file | |
| main_script = os.path.join(temp_dir, "script.py") | |
| with open(main_script, 'w', encoding='utf-8') as f: | |
| f.write(code) | |
| # Add library imports if specified | |
| if additional_libs: | |
| lib_imports = "\n".join([f"import {lib}" for lib in additional_libs if lib != "numpy" and lib != "matplotlib"]) | |
| if lib_imports: | |
| with open(main_script, 'r+', encoding='utf-8') as f: | |
| content = f.read() | |
| f.seek(0, 0) | |
| f.write(lib_imports + "\n\n" + content) | |
| # Add debugging setup if enabled | |
| if enable_debug: | |
| debug_setup = """ | |
| import pdb | |
| import sys | |
| import traceback | |
| class StringIODebugger: | |
| def __init__(self): | |
| self.steps = [] | |
| def add_step(self, frame, event, arg): | |
| if event == 'line': | |
| self.steps.append({ | |
| 'file': frame.f_code.co_filename, | |
| 'line': frame.f_lineno, | |
| 'function': frame.f_code.co_name, | |
| 'locals': {k: str(v) for k, v in frame.f_locals.items() if not k.startswith('__')} | |
| }) | |
| return self | |
| debug_steps = [] | |
| def trace_calls(frame, event, arg): | |
| if event != 'call': | |
| return | |
| co = frame.f_code | |
| func_name = co.co_name | |
| if func_name == 'write': | |
| return | |
| line_no = frame.f_lineno | |
| filename = co.co_filename | |
| if 'debugger' in filename or func_name.startswith('__'): | |
| return | |
| debug_steps.append(f"Calling {func_name} in {filename} at line {line_no}") | |
| return trace_calls | |
| sys.settrace(trace_calls) | |
| """ | |
| with open(main_script, 'r+', encoding='utf-8') as f: | |
| content = f.read() | |
| f.seek(0, 0) | |
| f.write(debug_setup + "\n" + content) | |
| # Add profiling if enabled | |
| if enable_profile: | |
| profile_setup = """ | |
| import cProfile | |
| import pstats | |
| import io | |
| # Set up profiler | |
| profiler = cProfile.Profile() | |
| profiler.enable() | |
| """ | |
| profile_teardown = """ | |
| # Finish profiling | |
| profiler.disable() | |
| s = io.StringIO() | |
| ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative') | |
| ps.print_stats() | |
| with open('profile_results.txt', 'w') as f: | |
| f.write(s.getvalue()) | |
| """ | |
| with open(main_script, 'r+', encoding='utf-8') as f: | |
| content = f.read() | |
| f.seek(0, 0) | |
| f.write(profile_setup + "\n" + content + "\n" + profile_teardown) | |
| # Add real-time visualization if enabled | |
| if realtime_viz: | |
| realtime_viz_setup = """ | |
| # Setup for real-time visualization | |
| import threading | |
| import json | |
| import time | |
| class RealTimeData: | |
| def __init__(self): | |
| self.data = [] | |
| def add_data(self, label, value): | |
| self.data.append({'label': label, 'value': value, 'time': time.time()}) | |
| # Write to file for real-time monitoring | |
| with open('realtime_data.json', 'w') as f: | |
| json.dump(self.data, f) | |
| rt_data = RealTimeData() | |
| # Example usage: rt_data.add_data("iteration", i) | |
| """ | |
| with open(main_script, 'r+', encoding='utf-8') as f: | |
| content = f.read() | |
| f.seek(0, 0) | |
| f.write(realtime_viz_setup + "\n" + content) | |
| # Add input handling code | |
| if inputs and len(inputs) > 0: | |
| # Modify the code to use predefined inputs instead of waiting for user input | |
| input_handling = """ | |
| # Input values provided by the user | |
| __INPUT_VALUES = {} | |
| __INPUT_INDEX = 0 | |
| # Override the built-in input function | |
| def input(prompt=''): | |
| global __INPUT_INDEX | |
| print(prompt, end='') | |
| if __INPUT_INDEX < len(__INPUT_VALUES): | |
| value = __INPUT_VALUES[__INPUT_INDEX] | |
| __INPUT_INDEX += 1 | |
| print(value) # Echo the input | |
| return value | |
| else: | |
| print("\\n[WARNING] No more predefined inputs available, using empty string") | |
| return "" | |
| """.format(inputs) | |
| with open(main_script, 'r+', encoding='utf-8') as f: | |
| content = f.read() | |
| f.seek(0, 0) | |
| f.write(input_handling + "\n" + content) | |
| # Add matplotlib and pandas handling | |
| data_handling = """ | |
| # Add plot saving code if matplotlib is used | |
| import os | |
| # For matplotlib plots | |
| if 'matplotlib' in globals() or 'matplotlib.pyplot' in globals() or 'plt' in globals(): | |
| import matplotlib | |
| matplotlib.use('Agg') # Use non-interactive backend | |
| import matplotlib.pyplot as plt | |
| # Hook to save all figures | |
| original_show = plt.show | |
| def custom_show(*args, **kwargs): | |
| for i, fig in enumerate(map(plt.figure, plt.get_fignums())): | |
| fig.savefig(os.path.join('{}', f'plot_{{i}}.png')) | |
| return original_show(*args, **kwargs) | |
| plt.show = custom_show | |
| # For pandas DataFrames | |
| if 'pandas' in globals() or 'pd' in globals(): | |
| import pandas as pd | |
| import json | |
| # Save DataFrames | |
| original_df_repr_html = pd.DataFrame._repr_html_ | |
| def custom_df_repr_html(self): | |
| try: | |
| df_info = {{ | |
| "name": str(id(self)), | |
| "shape": self.shape, | |
| "columns": list(map(str, self.columns)), | |
| "preview_html": self.head().to_html() | |
| }} | |
| with open(f'df_{{id(self)}}.json', 'w') as f: | |
| json.dump(df_info, f) | |
| except: | |
| pass | |
| return original_df_repr_html(self) | |
| pd.DataFrame._repr_html_ = custom_df_repr_html | |
| """.format(plot_dir.replace('\\', '\\\\')) | |
| with open(main_script, 'r+', encoding='utf-8') as f: | |
| content = f.read() | |
| f.seek(0, 0) | |
| f.write(data_handling + "\n" + content) | |
| # Files for capturing stdout and stderr | |
| stdout_file = os.path.join(temp_dir, 'stdout.txt') | |
| stderr_file = os.path.join(temp_dir, 'stderr.txt') | |
| # Execute with timeout | |
| start_time = time.time() | |
| try: | |
| # Run the script with stdout and stderr redirection | |
| with open(stdout_file, 'w') as stdout_f, open(stderr_file, 'w') as stderr_f: | |
| process = subprocess.Popen( | |
| [sys.executable, main_script], | |
| stdout=stdout_f, | |
| stderr=stderr_f, | |
| cwd=temp_dir | |
| ) | |
| # Real-time monitoring for real-time visualization | |
| if realtime_viz: | |
| realtime_data_file = os.path.join(temp_dir, 'realtime_data.json') | |
| while process.poll() is None: | |
| if os.path.exists(realtime_data_file): | |
| try: | |
| with open(realtime_data_file, 'r') as f: | |
| result["realtime_data"] = json.load(f) | |
| except: | |
| pass | |
| time.sleep(0.1) | |
| # Check for timeout | |
| if time.time() - start_time > timeout: | |
| process.kill() | |
| result["stderr"] += f"\nScript execution timed out after {timeout} seconds." | |
| result["exception"] = "TimeoutError" | |
| break | |
| else: | |
| try: | |
| process.wait(timeout=timeout) | |
| except subprocess.TimeoutExpired: | |
| process.kill() | |
| result["stderr"] += f"\nScript execution timed out after {timeout} seconds." | |
| result["exception"] = "TimeoutError" | |
| # Read the output | |
| with open(stdout_file, 'r') as f: | |
| result["stdout"] = f.read() | |
| with open(stderr_file, 'r') as f: | |
| result["stderr"] = f.read() | |
| # Collect plots | |
| if os.path.exists(plot_dir): | |
| plot_files = sorted([f for f in os.listdir(plot_dir) if f.endswith('.png')]) | |
| for plot_file in plot_files: | |
| with open(os.path.join(plot_dir, plot_file), 'rb') as f: | |
| result["plots"].append(f.read()) | |
| # Collect dataframes | |
| df_files = [f for f in os.listdir(temp_dir) if f.startswith('df_') and f.endswith('.json')] | |
| for df_file in df_files: | |
| with open(os.path.join(temp_dir, df_file), 'r') as f: | |
| result["dataframes"].append(json.load(f)) | |
| # Collect profiling data if enabled | |
| if enable_profile and os.path.exists(os.path.join(temp_dir, 'profile_results.txt')): | |
| with open(os.path.join(temp_dir, 'profile_results.txt'), 'r') as f: | |
| result["profile_data"] = f.read() | |
| # Collect debug data if enabled | |
| if enable_debug and 'debug_steps' in globals(): | |
| result["debug_steps"] = debug_steps | |
| # Calculate execution time | |
| result["execution_time"] = time.time() - start_time | |
| except Exception as e: | |
| result["exception"] = str(e) | |
| result["stderr"] += f"\nError executing script: {str(e)}" | |
| return result | |
| def display_python_script_results_enhanced(result): | |
| """Display the enhanced results from the Python script execution""" | |
| if not result: | |
| st.error("No results to display.") | |
| return | |
| # Display execution time | |
| st.info(f"Execution completed in {result['execution_time']:.2f} seconds") | |
| # Display any errors | |
| if result["exception"]: | |
| st.error(f"Exception occurred: {result['exception']}") | |
| if result["stderr"]: | |
| st.error("Errors:") | |
| st.code(result["stderr"], language="bash") | |
| # Display profiling data if available | |
| if result.get("profile_data"): | |
| with st.expander("Profiling Results"): | |
| st.code(result["profile_data"], language="bash") | |
| # Display debugging steps if available | |
| if result.get("debug_steps"): | |
| with st.expander("Debugging Steps"): | |
| for i, step in enumerate(result["debug_steps"]): | |
| st.markdown(f"**Step {i+1}**: {step}") | |
| # Display plots if any | |
| if result["plots"]: | |
| st.markdown("### Plots") | |
| cols = st.columns(min(3, len(result["plots"]))) | |
| for i, plot_data in enumerate(result["plots"]): | |
| cols[i % len(cols)].image(plot_data, use_column_width=True) | |
| # Add button to use this plot in Manim | |
| if cols[i % len(cols)].button(f"Use in Manim", key=f"use_plot_{i}"): | |
| # Create a temporary file | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".png") as tmp: | |
| tmp.write(plot_data) | |
| plot_path = tmp.name | |
| # Generate Manim code | |
| plot_code = f""" | |
| # Import the plot image | |
| plot_image = ImageMobject(r"{plot_path}") | |
| plot_image.scale(2) # Adjust size as needed | |
| self.play(FadeIn(plot_image)) | |
| self.wait(1) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + plot_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class PlotScene(Scene): | |
| def construct(self): | |
| {plot_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success(f"Added plot to your Manim code!") | |
| # Set pending tab switch to editor tab | |
| st.session_state.pending_tab_switch = 0 | |
| st.rerun() | |
| # Display dataframes if any | |
| if result["dataframes"]: | |
| st.markdown("### DataFrames") | |
| for df_info in result["dataframes"]: | |
| with st.expander(f"{df_info.get('name', 'DataFrame')} - {df_info['shape'][0]} rows × {df_info['shape'][1]} columns"): | |
| st.markdown(df_info["preview_html"], unsafe_allow_html=True) | |
| # Add button to visualize this dataframe in Manim | |
| if st.button(f"Visualize in Manim", key=f"viz_df_{df_info.get('name', 'df')}"): | |
| # Generate Manim code for dataframe visualization | |
| df_viz_code = f""" | |
| # Create a simple table visualization | |
| columns = {df_info['columns']} | |
| table = Table( | |
| col_labels=[Text(col, font_size=24) for col in columns] | |
| ) | |
| # Add data rows (showing first 5 rows) | |
| for i in range(min(5, {df_info['shape'][0]})): | |
| # This is a placeholder - in a real implementation, you'd extract actual data | |
| table.add_row(*[Text(f"Row {{i}}, Col {{j}}", font_size=20) for j in range(len(columns))]) | |
| self.play(Create(table)) | |
| self.wait(1) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + df_viz_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class DataFrameScene(Scene): | |
| def construct(self): | |
| {df_viz_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success(f"Added DataFrame visualization to your Manim code!") | |
| # Set pending tab switch to editor tab | |
| st.session_state.pending_tab_switch = 0 | |
| st.rerun() | |
| # Display standard output | |
| if result["stdout"]: | |
| st.markdown("### Standard Output") | |
| st.code(result["stdout"], language="bash") | |
| # Display real-time data if available | |
| if result.get("realtime_data"): | |
| st.markdown("### Real-time Data") | |
| # Convert to DataFrame for easier visualization | |
| import pandas as pd | |
| rt_df = pd.DataFrame(result["realtime_data"]) | |
| # Create a plotly chart | |
| import plotly.express as px | |
| if not rt_df.empty and "time" in rt_df.columns and "value" in rt_df.columns: | |
| fig = px.line(rt_df, x="time", y="value", color="label" if "label" in rt_df.columns else None, | |
| title="Real-time Data Visualization") | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Add button to create Manim animation from this data | |
| if st.button("Create Manim Animation from Data", key="create_manim_from_rt"): | |
| # Extract data points | |
| data_points = [] | |
| for _, row in rt_df.iterrows(): | |
| if "value" in row: | |
| data_points.append(float(row["value"])) | |
| # Generate Manim code | |
| rt_viz_code = f""" | |
| # Visualize real-time data | |
| data = {data_points} | |
| axes = Axes( | |
| x_range=[0, {len(data_points)}, 1], | |
| y_range=[{min(data_points) if data_points else 0}, {max(data_points) if data_points else 10}, {(max(data_points)-min(data_points))/10 if data_points and max(data_points) > min(data_points) else 1}], | |
| axis_config={{"color": BLUE}} | |
| ) | |
| points = [axes.coords_to_point(i, v) for i, v in enumerate(data)] | |
| graph = VMobject(color=RED) | |
| graph.set_points_as_corners(points) | |
| self.play(Create(axes)) | |
| self.play(Create(graph), run_time=2) | |
| self.wait(1) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + rt_viz_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class DataVisualizationScene(Scene): | |
| def construct(self): | |
| {rt_viz_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success(f"Added real-time data visualization to your Manim code!") | |
| # Set pending tab switch to editor tab | |
| st.session_state.pending_tab_switch = 0 | |
| st.rerun() | |
| # C/C++ RUNNER FUNCTIONS | |
| def compile_cpp_code_enhanced(code, settings, project_files=None, enable_debug=False, breakpoints=None, watch_vars=None): | |
| """Enhanced function to compile C++ code with advanced options.""" | |
| try: | |
| # Create a temporary directory for compilation | |
| temp_dir = tempfile.mkdtemp(prefix="cpp_runner_") | |
| # Write the project files | |
| if project_files: | |
| for filename, content in project_files.items(): | |
| file_path = os.path.join(temp_dir, filename) | |
| with open(file_path, "w") as f: | |
| f.write(content) | |
| # Set main file for single file mode | |
| cpp_file = os.path.join(temp_dir, "main.cpp") | |
| else: | |
| # Write the single code file | |
| cpp_file = os.path.join(temp_dir, "main.cpp") | |
| with open(cpp_file, "w") as f: | |
| f.write(code) | |
| # Output executable path | |
| exe_file = os.path.join(temp_dir, "program.exe" if platform.system() == "Windows" else "program") | |
| # Build the compilation command | |
| compiler = settings.get("compiler", "g++") | |
| std_version = settings.get("std", "c++17") | |
| optimization = settings.get("optimization", "-O2") | |
| compile_cmd = [ | |
| compiler, | |
| "-std=" + std_version, | |
| optimization | |
| ] | |
| # Add debug flag if debugging is enabled | |
| if enable_debug: | |
| compile_cmd.append("-g") | |
| # Auto-detect include paths if not specified | |
| include_paths = settings.get("include_paths", []) | |
| if not include_paths: | |
| # Common include directories | |
| common_include_dirs = [ | |
| "/usr/include", | |
| "/usr/local/include", | |
| "/opt/local/include", | |
| "/opt/homebrew/include" | |
| ] | |
| # Add detected paths for specified libraries | |
| for lib in settings.get("libraries", []): | |
| if lib == "Eigen": | |
| for dir in common_include_dirs: | |
| if os.path.exists(os.path.join(dir, "Eigen")): | |
| include_paths.append(dir) | |
| elif os.path.exists(os.path.join(dir, "eigen3")): | |
| include_paths.append(dir) | |
| elif lib == "OpenCV": | |
| try: | |
| # Get OpenCV include paths using pkg-config | |
| result = subprocess.run( | |
| ["pkg-config", "--cflags", "opencv4"], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| if result.returncode == 0: | |
| # Extract include paths from pkg-config output | |
| for flag in result.stdout.strip().split(): | |
| if flag.startswith("-I"): | |
| include_paths.append(flag[2:]) | |
| except Exception: | |
| pass | |
| # Add preprocessor definitions | |
| for definition in settings.get("definitions", []): | |
| if "=" in definition: | |
| name, value = definition.split("=", 1) | |
| compile_cmd.append(f"-D{name}={value}") | |
| else: | |
| compile_cmd.append(f"-D{definition}") | |
| # Add include paths | |
| for path in include_paths: | |
| compile_cmd.append(f"-I{path}") | |
| # Add library paths | |
| for path in settings.get("library_paths", []): | |
| compile_cmd.append(f"-L{path}") | |
| # Add files to compile | |
| if project_files: | |
| source_files = [os.path.join(temp_dir, f) for f in project_files.keys() if f.endswith((".cpp", ".c", ".cc"))] | |
| compile_cmd.extend(source_files) | |
| else: | |
| compile_cmd.append(cpp_file) | |
| # Output file | |
| compile_cmd.extend(["-o", exe_file]) | |
| # Add libraries | |
| for lib in settings.get("libraries", []): | |
| if lib == "Eigen": | |
| # Eigen is header-only, nothing to link | |
| pass | |
| elif lib == "OpenCV": | |
| # Add OpenCV libraries | |
| try: | |
| # Get OpenCV libraries using pkg-config | |
| pkg_config = subprocess.run( | |
| ["pkg-config", "--libs", "opencv4"], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| if pkg_config.returncode == 0: | |
| compile_cmd.extend(pkg_config.stdout.strip().split()) | |
| else: | |
| # Try opencv instead of opencv4 | |
| pkg_config = subprocess.run( | |
| ["pkg-config", "--libs", "opencv"], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| if pkg_config.returncode == 0: | |
| compile_cmd.extend(pkg_config.stdout.strip().split()) | |
| else: | |
| # Fallback to common OpenCV libraries | |
| compile_cmd.extend(["-lopencv_core", "-lopencv_imgproc", "-lopencv_highgui"]) | |
| except: | |
| # Fallback to common OpenCV libraries | |
| compile_cmd.extend(["-lopencv_core", "-lopencv_imgproc", "-lopencv_highgui"]) | |
| elif lib == "Boost": | |
| # Add common Boost libraries | |
| compile_cmd.extend(["-lboost_system", "-lboost_filesystem"]) | |
| elif lib == "FFTW": | |
| compile_cmd.append("-lfftw3") | |
| elif lib == "SDL2": | |
| compile_cmd.append("-lSDL2") | |
| elif lib == "SFML": | |
| compile_cmd.extend(["-lsfml-graphics", "-lsfml-window", "-lsfml-system"]) | |
| elif lib == "OpenGL": | |
| compile_cmd.extend(["-lGL", "-lGLU", "-lglut"]) | |
| # Add additional libraries | |
| for lib in settings.get("additional_libs", []): | |
| compile_cmd.append(f"-l{lib}") | |
| # Add advanced flags | |
| if settings.get("advanced_flags"): | |
| compile_cmd.extend(settings["advanced_flags"].split()) | |
| # Run the compilation process | |
| logger.info(f"Compiling with command: {' '.join(compile_cmd)}") | |
| result = subprocess.run( | |
| compile_cmd, | |
| capture_output=True, | |
| text=True, | |
| check=False, | |
| cwd=temp_dir | |
| ) | |
| if result.returncode != 0: | |
| return None, result.stderr, temp_dir | |
| return exe_file, None, temp_dir | |
| except Exception as e: | |
| return None, str(e), None | |
| def run_cpp_executable_enhanced(exe_path, temp_dir, inputs=None, timeout=30, enable_debug=False, breakpoints=None, watch_vars=None): | |
| """Enhanced function to run C++ executable with debugging support.""" | |
| result = { | |
| "stdout": "", | |
| "stderr": "", | |
| "execution_time": 0, | |
| "images": [], | |
| "exception": None, | |
| "debug_output": None, | |
| "memory_usage": None | |
| } | |
| try: | |
| # Prepare input data if provided | |
| input_data = "\n".join(inputs) if inputs else None | |
| # Start timing | |
| start_time = time.time() | |
| if enable_debug and breakpoints: | |
| # Run with GDB for debugging | |
| gdb_commands = ["set pagination off"] | |
| # Add breakpoints | |
| for bp in breakpoints: | |
| gdb_commands.append(f"break {bp}") | |
| # Add watchpoints for variables | |
| if watch_vars: | |
| for var in watch_vars: | |
| gdb_commands.append(f"watch {var}") | |
| # Run the program | |
| gdb_commands.append("run") | |
| # Continue to end | |
| gdb_commands.append("continue") | |
| # Quit GDB | |
| gdb_commands.append("quit") | |
| # Create GDB command file | |
| gdb_cmd_file = os.path.join(temp_dir, "gdb_commands.txt") | |
| with open(gdb_cmd_file, "w") as f: | |
| f.write("\n".join(gdb_commands)) | |
| # Run with GDB | |
| process = subprocess.run( | |
| ["gdb", "-x", gdb_cmd_file, "-batch", exe_path], | |
| input=input_data, | |
| text=True, | |
| capture_output=True, | |
| timeout=timeout, | |
| cwd=temp_dir | |
| ) | |
| # Capture outputs | |
| result["stdout"] = process.stdout | |
| result["stderr"] = process.stderr | |
| result["debug_output"] = process.stdout | |
| else: | |
| # Run normally | |
| process = subprocess.run( | |
| [exe_path], | |
| input=input_data, | |
| text=True, | |
| capture_output=True, | |
| timeout=timeout, | |
| cwd=temp_dir | |
| ) | |
| # Capture outputs | |
| result["stdout"] = process.stdout | |
| result["stderr"] = process.stderr | |
| # Calculate execution time | |
| result["execution_time"] = time.time() - start_time | |
| # Look for generated images in the executable directory | |
| for ext in [".png", ".jpg", ".jpeg", ".bmp", ".ppm"]: | |
| image_files = [f for f in os.listdir(temp_dir) if f.endswith(ext)] | |
| for img_file in image_files: | |
| try: | |
| img_path = os.path.join(temp_dir, img_file) | |
| # For PPM files, convert to PNG for easier display | |
| if img_file.endswith(".ppm"): | |
| # Create output path | |
| png_path = os.path.join(temp_dir, img_file.replace(".ppm", ".png")) | |
| # Convert using PIL | |
| from PIL import Image | |
| Image.open(img_path).save(png_path) | |
| img_path = png_path | |
| img_file = img_file.replace(".ppm", ".png") | |
| with open(img_path, "rb") as f: | |
| result["images"].append({ | |
| "name": img_file, | |
| "data": f.read() | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error processing image {img_file}: {str(e)}") | |
| # Estimate memory usage | |
| try: | |
| if platform.system() != "Windows": | |
| # Use ps command to get memory usage | |
| ps_output = subprocess.run( | |
| ["ps", "-p", str(process.pid), "-o", "rss="], | |
| capture_output=True, | |
| text=True, | |
| check=False | |
| ) | |
| if ps_output.returncode == 0: | |
| mem_kb = int(ps_output.stdout.strip()) | |
| result["memory_usage"] = mem_kb / 1024 # Convert to MB | |
| except: | |
| pass | |
| return result | |
| except subprocess.TimeoutExpired: | |
| result["stderr"] += f"\nProgram execution timed out after {timeout} seconds." | |
| result["exception"] = "TimeoutError" | |
| return result | |
| except Exception as e: | |
| result["stderr"] += f"\nError executing program: {str(e)}" | |
| result["exception"] = str(e) | |
| return result | |
| def parse_animation_steps(python_code): | |
| """Parse Manim code to extract animation steps for timeline editor""" | |
| animation_steps = [] | |
| # Look for self.play calls in the code | |
| play_calls = re.findall(r'self\.play\((.*?)\)', python_code, re.DOTALL) | |
| wait_calls = re.findall(r'self\.wait\((.*?)\)', python_code, re.DOTALL) | |
| # Extract animation objects from play calls | |
| for i, play_call in enumerate(play_calls): | |
| # Parse the arguments to self.play() | |
| animations = [arg.strip() for arg in play_call.split(',')] | |
| # Get wait time after this animation if available | |
| wait_time = 1.0 # Default wait time | |
| if i < len(wait_calls): | |
| wait_match = re.search(r'(\d+\.?\d*)', wait_calls[i]) | |
| if wait_match: | |
| wait_time = float(wait_match.group(1)) | |
| # Add to animation steps | |
| animation_steps.append({ | |
| "id": i+1, | |
| "type": "play", | |
| "animations": animations, | |
| "duration": wait_time, | |
| "start_time": sum([step.get("duration", 1.0) for step in animation_steps]), | |
| "code": f"self.play({play_call})" | |
| }) | |
| return animation_steps | |
| def generate_code_from_timeline(animation_steps, original_code): | |
| """Generate Manim code from the timeline data""" | |
| # Extract the class definition and setup | |
| class_match = re.search(r'(class\s+\w+\s*\([^)]*\)\s*:.*?def\s+construct\s*\(\s*self\s*\)\s*:)', original_code, re.DOTALL) | |
| if not class_match: | |
| return original_code # Can't find proper structure to modify | |
| setup_code = class_match.group(1) | |
| # Build the new construct method | |
| new_code = [setup_code] | |
| indent = " " # Standard Manim indentation | |
| # Add each animation step in order | |
| for step in sorted(animation_steps, key=lambda x: x["id"]): | |
| new_code.append(f"{indent}{step['code']}") | |
| if "duration" in step and step["duration"] > 0: | |
| new_code.append(f"{indent}self.wait({step['duration']})") | |
| # Add any code that might come after animations | |
| end_match = re.search(r'(#\s*End\s+of\s+animations.*?$)', original_code, re.DOTALL) | |
| if end_match: | |
| new_code.append(end_match.group(1)) | |
| # Combine the code parts with proper indentation | |
| return "\n".join(new_code) | |
| def create_timeline_editor(code): | |
| """Create an interactive timeline editor for animation sequences""" | |
| st.markdown("### 🎞️ Animation Timeline Editor") | |
| if not code: | |
| st.warning("Add animation code first to use the timeline editor.") | |
| return code | |
| # Parse animation steps from the code | |
| animation_steps = parse_animation_steps(code) | |
| if not animation_steps: | |
| st.warning("No animation steps detected in your code.") | |
| return code | |
| # Convert to DataFrame for easier manipulation | |
| df = pd.DataFrame(animation_steps) | |
| # Create an interactive Gantt chart with plotly | |
| st.markdown("#### Animation Timeline") | |
| st.markdown("Drag timeline elements to reorder or resize to change duration") | |
| # Create the Gantt chart | |
| fig = px.timeline( | |
| df, | |
| x_start="start_time", | |
| x_end=df["start_time"] + df["duration"], | |
| y="id", | |
| color="type", | |
| hover_name="animations", | |
| labels={"id": "Step", "start_time": "Time (seconds)"} | |
| ) | |
| # Make it interactive | |
| fig.update_layout( | |
| height=400, | |
| xaxis=dict( | |
| title="Time (seconds)", | |
| rangeslider_visible=True | |
| ) | |
| ) | |
| # Add buttons and interactivity | |
| timeline_chart = st.plotly_chart(fig, use_container_width=True) | |
| # Control panel | |
| st.markdown("#### Timeline Controls") | |
| controls_col1, controls_col2, controls_col3 = st.columns(3) | |
| with controls_col1: | |
| selected_step = st.selectbox( | |
| "Select Step to Edit:", | |
| options=list(range(1, len(animation_steps) + 1)), | |
| format_func=lambda x: f"Step {x}" | |
| ) | |
| with controls_col2: | |
| new_duration = st.number_input( | |
| "Duration (seconds):", | |
| min_value=0.1, | |
| max_value=10.0, | |
| value=float(df[df["id"] == selected_step]["duration"].values[0]), | |
| step=0.1 | |
| ) | |
| with controls_col3: | |
| step_action = st.selectbox( | |
| "Action:", | |
| options=["Update Duration", "Move Up", "Move Down", "Delete Step"] | |
| ) | |
| apply_btn = st.button("Apply Change", key="apply_timeline_change") | |
| # Handle timeline modifications | |
| if apply_btn: | |
| modified = False | |
| if step_action == "Update Duration": | |
| # Update the duration of the selected step | |
| idx = df[df["id"] == selected_step].index[0] | |
| df.at[idx, "duration"] = new_duration | |
| modified = True | |
| elif step_action == "Move Up" and selected_step > 1: | |
| # Swap with the step above | |
| idx1 = df[df["id"] == selected_step].index[0] | |
| idx2 = df[df["id"] == selected_step - 1].index[0] | |
| # Swap IDs to maintain order | |
| df.at[idx1, "id"], df.at[idx2, "id"] = selected_step - 1, selected_step | |
| modified = True | |
| elif step_action == "Move Down" and selected_step < len(animation_steps): | |
| # Swap with the step below | |
| idx1 = df[df["id"] == selected_step].index[0] | |
| idx2 = df[df["id"] == selected_step + 1].index[0] | |
| # Swap IDs to maintain order | |
| df.at[idx1, "id"], df.at[idx2, "id"] = selected_step + 1, selected_step | |
| modified = True | |
| elif step_action == "Delete Step": | |
| # Remove the selected step | |
| df = df[df["id"] != selected_step] | |
| # Reindex remaining steps | |
| new_ids = list(range(1, len(df) + 1)) | |
| df["id"] = new_ids | |
| modified = True | |
| if modified: | |
| # Recalculate start times | |
| df = df.sort_values("id") | |
| cumulative_time = 0 | |
| for idx, row in df.iterrows(): | |
| df.at[idx, "start_time"] = cumulative_time | |
| cumulative_time += row["duration"] | |
| # Regenerate animation code | |
| animation_steps = df.to_dict('records') | |
| new_code = generate_code_from_timeline(animation_steps, code) | |
| st.success("Timeline updated! Code has been regenerated.") | |
| return new_code | |
| # Visual keyframe editor | |
| st.markdown("#### Visual Keyframe Editor") | |
| st.markdown("Add keyframes for smooth property transitions") | |
| keyframe_obj = st.selectbox( | |
| "Select object to animate:", | |
| options=[f"Object {i+1}" for i in range(5)] # Placeholder for actual objects | |
| ) | |
| keyframe_prop = st.selectbox( | |
| "Select property:", | |
| options=["position", "scale", "rotation", "opacity", "color"] | |
| ) | |
| # Keyframe timeline visualization | |
| keyframe_times = [0, 1, 2, 3, 4] # Placeholder | |
| keyframe_values = [0, 0.5, 0.8, 0.2, 1.0] # Placeholder | |
| keyframe_df = pd.DataFrame({ | |
| "time": keyframe_times, | |
| "value": keyframe_values | |
| }) | |
| keyframe_fig = px.line( | |
| keyframe_df, | |
| x="time", | |
| y="value", | |
| markers=True, | |
| title=f"{keyframe_prop.capitalize()} Keyframes" | |
| ) | |
| keyframe_fig.update_layout( | |
| xaxis_title="Time (seconds)", | |
| yaxis_title="Value", | |
| height=250 | |
| ) | |
| st.plotly_chart(keyframe_fig, use_container_width=True) | |
| keyframe_col1, keyframe_col2, keyframe_col3 = st.columns(3) | |
| with keyframe_col1: | |
| keyframe_time = st.number_input("Time (s)", min_value=0.0, max_value=10.0, value=0.0, step=0.1) | |
| with keyframe_col2: | |
| keyframe_value = st.number_input("Value", min_value=0.0, max_value=1.0, value=0.0, step=0.1) | |
| with keyframe_col3: | |
| add_keyframe = st.button("Add Keyframe") | |
| # Return the original code or modified code | |
| return code | |
| def export_to_educational_format(video_data, format_type, animation_title, explanation_text, temp_dir): | |
| """Export animation to various educational formats""" | |
| try: | |
| if format_type == "powerpoint": | |
| # Make sure python-pptx is installed | |
| try: | |
| import pptx | |
| from pptx.util import Inches | |
| except ImportError: | |
| logger.error("python-pptx not installed") | |
| subprocess.run([sys.executable, "-m", "pip", "install", "python-pptx"], check=True) | |
| import pptx | |
| from pptx.util import Inches | |
| # Create PowerPoint presentation | |
| prs = pptx.Presentation() | |
| # Title slide | |
| title_slide = prs.slides.add_slide(prs.slide_layouts[0]) | |
| title_slide.shapes.title.text = animation_title | |
| title_slide.placeholders[1].text = "Created with Manim Animation Studio" | |
| # Video slide | |
| video_slide = prs.slides.add_slide(prs.slide_layouts[5]) | |
| video_slide.shapes.title.text = "Animation" | |
| # Save video to temp file | |
| video_path = os.path.join(temp_dir, "animation.mp4") | |
| with open(video_path, "wb") as f: | |
| f.write(video_data) | |
| # Add video to slide | |
| try: | |
| left = Inches(1) | |
| top = Inches(1.5) | |
| width = Inches(8) | |
| height = Inches(4.5) | |
| video_slide.shapes.add_movie(video_path, left, top, width, height) | |
| except Exception as e: | |
| logger.error(f"Error adding video to PowerPoint: {str(e)}") | |
| # Fallback to adding a picture with link | |
| img_path = os.path.join(temp_dir, "thumbnail.png") | |
| # Generate thumbnail with ffmpeg | |
| subprocess.run([ | |
| "ffmpeg", "-i", video_path, "-ss", "00:00:01.000", | |
| "-vframes", "1", img_path | |
| ], check=True) | |
| if os.path.exists(img_path): | |
| pic = video_slide.shapes.add_picture(img_path, left, top, width, height) | |
| video_slide.shapes.add_textbox(left, top + height + Inches(0.5), width, Inches(0.5)).text_frame.text = "Click to play video (exported separately)" | |
| # Explanation slide | |
| if explanation_text: | |
| text_slide = prs.slides.add_slide(prs.slide_layouts[1]) | |
| text_slide.shapes.title.text = "Explanation" | |
| text_slide.placeholders[1].text = explanation_text | |
| # Save presentation | |
| output_path = os.path.join(temp_dir, f"{animation_title.replace(' ', '_')}.pptx") | |
| prs.save(output_path) | |
| # Read the file to return it | |
| with open(output_path, "rb") as f: | |
| return f.read(), "powerpoint" | |
| elif format_type == "html": | |
| # Create interactive HTML animation | |
| html_template = """ | |
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <title>{title}</title> | |
| <style> | |
| body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }} | |
| .animation-container {{ margin: 20px 0; }} | |
| .controls {{ display: flex; margin: 10px 0; }} | |
| .controls button {{ margin-right: 10px; padding: 5px 10px; }} | |
| .explanation {{ margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; }} | |
| </style> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() {{ | |
| const video = document.getElementById('animation'); | |
| const playBtn = document.getElementById('play'); | |
| const pauseBtn = document.getElementById('pause'); | |
| const restartBtn = document.getElementById('restart'); | |
| const slowBtn = document.getElementById('slow'); | |
| const normalBtn = document.getElementById('normal'); | |
| const fastBtn = document.getElementById('fast'); | |
| playBtn.addEventListener('click', function() {{ video.play(); }}); | |
| pauseBtn.addEventListener('click', function() {{ video.pause(); }}); | |
| restartBtn.addEventListener('click', function() {{ video.currentTime = 0; video.play(); }}); | |
| slowBtn.addEventListener('click', function() {{ video.playbackRate = 0.5; }}); | |
| normalBtn.addEventListener('click', function() {{ video.playbackRate = 1.0; }}); | |
| fastBtn.addEventListener('click', function() {{ video.playbackRate = 2.0; }}); | |
| }}); | |
| </script> | |
| </head> | |
| <body> | |
| <h1>{title}</h1> | |
| <div class="animation-container"> | |
| <video id="animation" width="100%" controls> | |
| <source src="data:video/mp4;base64,{video_base64}" type="video/mp4"> | |
| Your browser does not support the video tag. | |
| </video> | |
| <div class="controls"> | |
| <button id="play">Play</button> | |
| <button id="pause">Pause</button> | |
| <button id="restart">Restart</button> | |
| <button id="slow">0.5x Speed</button> | |
| <button id="normal">1x Speed</button> | |
| <button id="fast">2x Speed</button> | |
| </div> | |
| </div> | |
| <div class="explanation"> | |
| <h2>Explanation</h2> | |
| {explanation_html} | |
| </div> | |
| <footer> | |
| <p>Created with Manim Animation Studio</p> | |
| </footer> | |
| </body> | |
| </html> | |
| """ | |
| # Convert video data to base64 | |
| video_base64 = base64.b64encode(video_data).decode('utf-8') | |
| # Convert markdown explanation to HTML | |
| explanation_html = markdown.markdown(explanation_text) if explanation_text else "<p>No explanation provided.</p>" | |
| # Format the HTML template | |
| html_content = html_template.format( | |
| title=animation_title, | |
| video_base64=video_base64, | |
| explanation_html=explanation_html | |
| ) | |
| # Save to file | |
| output_path = os.path.join(temp_dir, f"{animation_title.replace(' ', '_')}.html") | |
| with open(output_path, "w", encoding="utf-8") as f: | |
| f.write(html_content) | |
| # Read the file to return it | |
| with open(output_path, "rb") as f: | |
| return f.read(), "html" | |
| elif format_type == "sequence": | |
| # Generate animation sequence with explanatory text | |
| # Make sure FPDF is installed | |
| try: | |
| from fpdf import FPDF | |
| except ImportError: | |
| logger.error("fpdf not installed") | |
| subprocess.run([sys.executable, "-m", "pip", "install", "fpdf"], check=True) | |
| from fpdf import FPDF | |
| # Save video temporarily | |
| temp_video_path = os.path.join(temp_dir, "temp_video.mp4") | |
| with open(temp_video_path, "wb") as f: | |
| f.write(video_data) | |
| # Create frames directory | |
| frames_dir = os.path.join(temp_dir, "frames") | |
| os.makedirs(frames_dir, exist_ok=True) | |
| # Extract frames using ffmpeg (assuming it's installed) | |
| frame_count = 5 # Number of key frames to extract | |
| try: | |
| subprocess.run([ | |
| "ffmpeg", | |
| "-i", temp_video_path, | |
| "-vf", f"select=eq(n\\,0)+eq(n\\,{frame_count//4})+eq(n\\,{frame_count//2})+eq(n\\,{frame_count*3//4})+eq(n\\,{frame_count-1})", | |
| "-vsync", "0", | |
| os.path.join(frames_dir, "frame_%03d.png") | |
| ], check=True) | |
| except Exception as e: | |
| logger.error(f"Error extracting frames: {str(e)}") | |
| # Try a simpler approach | |
| subprocess.run([ | |
| "ffmpeg", | |
| "-i", temp_video_path, | |
| "-r", "1", # 1 frame per second | |
| os.path.join(frames_dir, "frame_%03d.png") | |
| ], check=True) | |
| # Parse explanation text into segments (assuming sections divided by ##) | |
| explanation_segments = explanation_text.split("##") if explanation_text else ["No explanation provided."] | |
| # Create a PDF with frames and explanations | |
| pdf = FPDF() | |
| pdf.set_auto_page_break(auto=True, margin=15) | |
| # Title page | |
| pdf.add_page() | |
| pdf.set_font("Arial", "B", 20) | |
| pdf.cell(190, 10, animation_title, ln=True, align="C") | |
| pdf.ln(10) | |
| pdf.set_font("Arial", "", 12) | |
| pdf.cell(190, 10, "Animation Sequence with Explanations", ln=True, align="C") | |
| # Add each frame with explanation | |
| frame_files = sorted([f for f in os.listdir(frames_dir) if f.endswith('.png')]) | |
| for i, frame_file in enumerate(frame_files): | |
| pdf.add_page() | |
| # Add frame image | |
| frame_path = os.path.join(frames_dir, frame_file) | |
| pdf.image(frame_path, x=10, y=10, w=190) | |
| # Add explanation text | |
| pdf.ln(140) # Move below the image | |
| pdf.set_font("Arial", "B", 12) | |
| pdf.cell(190, 10, f"Step {i+1}", ln=True) | |
| pdf.set_font("Arial", "", 10) | |
| # Use the corresponding explanation segment if available | |
| explanation = explanation_segments[min(i, len(explanation_segments)-1)] | |
| pdf.multi_cell(190, 5, explanation.strip()) | |
| # Save PDF | |
| output_path = os.path.join(temp_dir, f"{animation_title.replace(' ', '_')}_sequence.pdf") | |
| pdf.output(output_path) | |
| # Read the file to return it | |
| with open(output_path, "rb") as f: | |
| return f.read(), "pdf" | |
| return None, None | |
| except Exception as e: | |
| logger.error(f"Educational export error: {str(e)}") | |
| import traceback | |
| logger.error(traceback.format_exc()) | |
| return None, None | |
| def main(): | |
| # Initialize session state variables if they don't exist | |
| if 'init' not in st.session_state: | |
| st.session_state.init = True | |
| st.session_state.video_data = None | |
| st.session_state.status = None | |
| st.session_state.ai_models = None | |
| st.session_state.generated_code = "" | |
| st.session_state.code = "" | |
| st.session_state.temp_code = "" | |
| st.session_state.editor_key = str(uuid.uuid4()) | |
| st.session_state.packages_checked = False # Track if packages were already checked | |
| st.session_state.audio_path = None | |
| st.session_state.image_paths = [] | |
| st.session_state.custom_library_result = "" | |
| st.session_state.python_script = "import matplotlib.pyplot as plt\nimport numpy as np\n\n# Example: Create a simple plot\nx = np.linspace(0, 10, 100)\ny = np.sin(x)\n\nplt.figure(figsize=(10, 6))\nplt.plot(x, y, 'b-', label='sin(x)')\nplt.title('Sine Wave')\nplt.xlabel('x')\nplt.ylabel('sin(x)')\nplt.grid(True)\nplt.legend()\n" | |
| st.session_state.python_result = None | |
| st.session_state.active_tab = 0 # Track currently active tab | |
| st.session_state.settings = { | |
| "quality": "720p", | |
| "format_type": "mp4", | |
| "animation_speed": "Normal", | |
| "fps": 30 # Default FPS | |
| } | |
| st.session_state.password_entered = False # Track password authentication | |
| st.session_state.custom_model = "gpt-4o" # Default model | |
| st.session_state.first_load_complete = False # Prevent refreshes on first load | |
| st.session_state.pending_tab_switch = None # Track pending tab switches | |
| # C++ runner state | |
| st.session_state.cpp_code = """#include <iostream> | |
| #include <vector> | |
| #include <algorithm> | |
| int main() { | |
| std::cout << "Hello, Manim Animation Studio!" << std::endl; | |
| // Create a vector of numbers | |
| std::vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6}; | |
| // Sort the vector | |
| std::sort(numbers.begin(), numbers.end()); | |
| // Print the sorted numbers | |
| std::cout << "Sorted numbers: "; | |
| for (int num : numbers) { | |
| std::cout << num << " "; | |
| } | |
| std::cout << std::endl; | |
| return 0; | |
| }""" | |
| st.session_state.cpp_result = None | |
| st.session_state.cpp_project_files = {"main.cpp": st.session_state.cpp_code} | |
| st.session_state.cpp_settings = { | |
| "compiler": "g++", | |
| "std": "c++17", | |
| "optimization": "-O2", | |
| "include_paths": [], | |
| "library_paths": [], | |
| "libraries": [] | |
| } | |
| # Page configuration with improved layout | |
| st.set_page_config( | |
| page_title="Manim Animation Studio", | |
| page_icon="🎬", | |
| layout="wide", | |
| initial_sidebar_state="expanded" | |
| ) | |
| # Custom CSS for improved UI with modern design | |
| st.markdown(""" | |
| <style> | |
| /* CSS Variables for theming */ | |
| :root { | |
| --primary-color: #6366f1; | |
| --primary-hover: #4f46e5; | |
| --secondary-color: #8b5cf6; | |
| --accent-color: #06b6d4; | |
| --success-color: #10b981; | |
| --warning-color: #f59e0b; | |
| --error-color: #ef4444; | |
| --bg-primary: #0f172a; | |
| --bg-secondary: #1e293b; | |
| --bg-card: #1e293b; | |
| --text-primary: #f1f5f9; | |
| --text-secondary: #94a3b8; | |
| --border-color: #334155; | |
| --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); | |
| --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4); | |
| --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5); | |
| --shadow-glow: 0 0 20px rgba(99, 102, 241, 0.3); | |
| --radius-sm: 6px; | |
| --radius-md: 10px; | |
| --radius-lg: 16px; | |
| --radius-xl: 24px; | |
| } | |
| /* Global dark theme */ | |
| .stApp { | |
| background: linear-gradient(135deg, var(--bg-primary) 0%, #1a1f35 100%); | |
| } | |
| /* Main header with gradient animation */ | |
| .main-header { | |
| font-size: 3rem; | |
| font-weight: 800; | |
| background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 25%, #06b6d4 50%, #10b981 75%, #6366f1 100%); | |
| background-size: 300% 300%; | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| margin-bottom: 0.5rem; | |
| text-align: center; | |
| animation: gradient-shift 8s ease infinite; | |
| text-shadow: 0 0 30px rgba(99, 102, 241, 0.3); | |
| } | |
| @keyframes gradient-shift { | |
| 0%, 100% { background-position: 0% 50%; } | |
| 50% { background-position: 100% 50%; } | |
| } | |
| .subtitle { | |
| text-align: center; | |
| color: var(--text-secondary); | |
| font-size: 1.1rem; | |
| margin-bottom: 2rem; | |
| font-weight: 400; | |
| } | |
| /* Improved Cards with glass morphism */ | |
| .card { | |
| background: linear-gradient(135deg, rgba(30, 41, 59, 0.9) 0%, rgba(15, 23, 42, 0.9) 100%); | |
| backdrop-filter: blur(20px); | |
| border-radius: var(--radius-lg); | |
| padding: 1.8rem; | |
| box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.05); | |
| margin-bottom: 1.8rem; | |
| border: 1px solid var(--border-color); | |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .card::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 4px; | |
| height: 100%; | |
| background: linear-gradient(180deg, var(--primary-color), var(--secondary-color)); | |
| border-radius: var(--radius-sm) 0 0 var(--radius-sm); | |
| } | |
| .card:hover { | |
| box-shadow: var(--shadow-glow), var(--shadow-lg); | |
| transform: translateY(-4px); | |
| border-color: var(--primary-color); | |
| } | |
| /* Tab styling - modern pill design */ | |
| .stTabs [data-baseweb="tab-list"] { | |
| gap: 8px; | |
| background: var(--bg-secondary); | |
| padding: 8px; | |
| border-radius: var(--radius-lg); | |
| border: 1px solid var(--border-color); | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| height: 48px; | |
| white-space: pre-wrap; | |
| border-radius: var(--radius-md); | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| color: var(--text-secondary); | |
| background: transparent; | |
| border: none; | |
| transition: all 0.3s ease; | |
| padding: 0 16px; | |
| } | |
| .stTabs [data-baseweb="tab"]:hover { | |
| background: rgba(99, 102, 241, 0.1); | |
| color: var(--text-primary); | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)) !important; | |
| color: white !important; | |
| box-shadow: var(--shadow-md), 0 0 20px rgba(99, 102, 241, 0.4); | |
| } | |
| /* Buttons - gradient and glow effects */ | |
| .stButton button { | |
| border-radius: var(--radius-md); | |
| font-weight: 600; | |
| font-size: 0.95rem; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| border: none; | |
| color: white; | |
| padding: 0.6rem 1.5rem; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .stButton button::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); | |
| transition: left 0.5s ease; | |
| } | |
| .stButton button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(99, 102, 241, 0.4); | |
| } | |
| .stButton button:hover::before { | |
| left: 100%; | |
| } | |
| .stButton button:active { | |
| transform: translateY(0); | |
| } | |
| /* Model selection cards */ | |
| .model-group { | |
| margin-bottom: 1.5rem; | |
| padding: 20px; | |
| border-radius: var(--radius-lg); | |
| background: linear-gradient(135deg, var(--bg-secondary), var(--bg-primary)); | |
| border: 1px solid var(--border-color); | |
| } | |
| .model-card { | |
| background: rgba(30, 41, 59, 0.8); | |
| backdrop-filter: blur(10px); | |
| border-radius: var(--radius-md); | |
| padding: 18px; | |
| margin-bottom: 12px; | |
| border: 1px solid var(--border-color); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .model-card::after { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| width: 4px; | |
| height: 100%; | |
| background: linear-gradient(180deg, var(--primary-color), var(--accent-color)); | |
| border-radius: var(--radius-sm) 0 0 var(--radius-sm); | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .model-card:hover { | |
| box-shadow: var(--shadow-md); | |
| transform: translateX(4px); | |
| border-color: var(--primary-color); | |
| } | |
| .model-card:hover::after { | |
| opacity: 1; | |
| } | |
| .model-category { | |
| font-size: 1.3rem; | |
| font-weight: 700; | |
| padding: 12px 0; | |
| margin-top: 20px; | |
| border-bottom: 2px solid var(--border-color); | |
| color: var(--text-primary); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .model-details { | |
| font-size: 0.85rem; | |
| color: var(--text-secondary); | |
| margin-top: 8px; | |
| line-height: 1.5; | |
| } | |
| .selected-model { | |
| background: linear-gradient(135deg, rgba(99, 102, 241, 0.2), rgba(139, 92, 246, 0.1)); | |
| border: 1px solid var(--primary-color); | |
| } | |
| .selected-model::after { | |
| opacity: 1; | |
| } | |
| /* Preview container with glow */ | |
| .preview-container { | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-lg); | |
| padding: 1.5rem; | |
| margin-bottom: 1.5rem; | |
| min-height: 220px; | |
| background: linear-gradient(135deg, var(--bg-secondary), var(--bg-primary)); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .preview-container::before { | |
| content: ' Preview'; | |
| position: absolute; | |
| top: 12px; | |
| left: 16px; | |
| font-size: 0.75rem; | |
| color: var(--text-secondary); | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| /* Asset cards */ | |
| .asset-card { | |
| background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.9)); | |
| border-radius: var(--radius-md); | |
| padding: 1.2rem; | |
| margin-bottom: 1rem; | |
| border: 1px solid var(--border-color); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| .asset-card::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| width: 4px; | |
| height: 100%; | |
| background: linear-gradient(180deg, var(--accent-color), var(--success-color)); | |
| border-radius: var(--radius-sm) 0 0 var(--radius-sm); | |
| } | |
| .asset-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-md); | |
| } | |
| /* Timeline container */ | |
| .timeline-container { | |
| background: linear-gradient(135deg, var(--bg-secondary), var(--bg-primary)); | |
| border-radius: var(--radius-lg); | |
| padding: 2rem; | |
| margin-bottom: 1.5rem; | |
| border: 1px solid var(--border-color); | |
| position: relative; | |
| } | |
| .keyframe { | |
| width: 14px; | |
| height: 14px; | |
| border-radius: 50%; | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| position: absolute; | |
| transform: translate(-50%, -50%); | |
| cursor: pointer; | |
| box-shadow: 0 0 10px rgba(99, 102, 241, 0.5); | |
| transition: all 0.2s ease; | |
| } | |
| .keyframe:hover { | |
| transform: translate(-50%, -50%) scale(1.3); | |
| box-shadow: 0 0 20px rgba(99, 102, 241, 0.8); | |
| } | |
| /* Educational export container */ | |
| .educational-export-container { | |
| background: linear-gradient(135deg, rgba(6, 182, 212, 0.1), rgba(16, 185, 129, 0.1)); | |
| border-radius: var(--radius-lg); | |
| padding: 2rem; | |
| margin-bottom: 1.5rem; | |
| border: 1px solid rgba(6, 182, 212, 0.3); | |
| } | |
| /* Code output styling */ | |
| .code-output { | |
| background: linear-gradient(135deg, rgba(16, 185, 129, 0.1), rgba(6, 182, 212, 0.05)); | |
| border-radius: var(--radius-md); | |
| padding: 1.2rem; | |
| margin-top: 1rem; | |
| border: 1px solid rgba(16, 185, 129, 0.3); | |
| max-height: 400px; | |
| overflow-y: auto; | |
| font-family: 'JetBrains Mono', 'Fira Code', monospace; | |
| } | |
| .code-output::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .code-output::-webkit-scrollbar-track { | |
| background: var(--bg-primary); | |
| border-radius: 4px; | |
| } | |
| .code-output::-webkit-scrollbar-thumb { | |
| background: var(--border-color); | |
| border-radius: 4px; | |
| } | |
| .code-output::-webkit-scrollbar-thumb:hover { | |
| background: var(--primary-color); | |
| } | |
| .error-output { | |
| background: linear-gradient(135deg, rgba(239, 68, 68, 0.15), rgba(239, 68, 68, 0.05)); | |
| border-radius: var(--radius-md); | |
| padding: 1.2rem; | |
| margin-top: 1rem; | |
| border: 1px solid rgba(239, 68, 68, 0.3); | |
| } | |
| /* Sidebar styling */ | |
| .css-1d391kg, [data-testid="stSidebar"] { | |
| background: linear-gradient(180deg, var(--bg-secondary), var(--bg-primary)); | |
| } | |
| [data-testid="stSidebar"] .block-container { | |
| padding-top: 2rem; | |
| } | |
| /* Input fields */ | |
| .stTextInput input, .stTextArea textarea, .stSelectbox select { | |
| background: var(--bg-secondary) !important; | |
| border: 1px solid var(--border-color) !important; | |
| border-radius: var(--radius-md) !important; | |
| color: var(--text-primary) !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .stTextInput input:focus, .stTextArea textarea:focus { | |
| border-color: var(--primary-color) !important; | |
| box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2) !important; | |
| } | |
| /* Expander styling */ | |
| .streamlit-expanderHeader { | |
| background: var(--bg-secondary) !important; | |
| border-radius: var(--radius-md) !important; | |
| border: 1px solid var(--border-color) !important; | |
| color: var(--text-primary) !important; | |
| font-weight: 600 !important; | |
| } | |
| .streamlit-expanderHeader:hover { | |
| border-color: var(--primary-color) !important; | |
| } | |
| /* Slider styling */ | |
| .stSlider > div > div > div { | |
| background: var(--primary-color) !important; | |
| } | |
| /* Progress bar */ | |
| .stProgress > div > div > div > div { | |
| background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)) !important; | |
| } | |
| /* Info, warning, error boxes */ | |
| .stAlert { | |
| border-radius: var(--radius-md) !important; | |
| border: none !important; | |
| } | |
| /* Metric styling */ | |
| [data-testid="stMetricValue"] { | |
| color: var(--text-primary) !important; | |
| font-weight: 700 !important; | |
| } | |
| /* Spinner */ | |
| .stSpinner > div { | |
| border-top-color: var(--primary-color) !important; | |
| } | |
| /* File uploader */ | |
| [data-testid="stFileUploader"] { | |
| background: var(--bg-secondary) !important; | |
| border-radius: var(--radius-md) !important; | |
| border: 2px dashed var(--border-color) !important; | |
| padding: 1rem !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| [data-testid="stFileUploader"]:hover { | |
| border-color: var(--primary-color) !important; | |
| background: rgba(99, 102, 241, 0.05) !important; | |
| } | |
| /* Small text helper */ | |
| .small-text { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| } | |
| /* Status badges */ | |
| .status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| } | |
| .status-success { | |
| background: rgba(16, 185, 129, 0.2); | |
| color: #10b981; | |
| border: 1px solid rgba(16, 185, 129, 0.3); | |
| } | |
| .status-error { | |
| background: rgba(239, 68, 68, 0.2); | |
| color: #ef4444; | |
| border: 1px solid rgba(239, 68, 68, 0.3); | |
| } | |
| .status-warning { | |
| background: rgba(245, 158, 11, 0.2); | |
| color: #f59e0b; | |
| border: 1px solid rgba(245, 158, 11, 0.3); | |
| } | |
| /* Tooltip styling */ | |
| .tooltip { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .tooltip .tooltiptext { | |
| visibility: hidden; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| text-align: center; | |
| padding: 8px 12px; | |
| border-radius: var(--radius-sm); | |
| font-size: 0.8rem; | |
| position: absolute; | |
| z-index: 1000; | |
| bottom: 125%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| border: 1px solid var(--border-color); | |
| box-shadow: var(--shadow-md); | |
| white-space: nowrap; | |
| } | |
| .tooltip:hover .tooltiptext { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| /* Scrollbar styling for main content */ | |
| ::-webkit-scrollbar { | |
| width: 10px; | |
| height: 10px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--bg-primary); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--border-color); | |
| border-radius: 5px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--primary-color); | |
| } | |
| /* Animation for loading states */ | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .loading { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| /* Responsive adjustments */ | |
| @media (max-width: 768px) { | |
| .main-header { | |
| font-size: 2rem; | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| padding: 0 8px; | |
| font-size: 0.8rem; | |
| } | |
| .card { | |
| padding: 1rem; | |
| } | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Header | |
| st.markdown(""" | |
| <div class="main-header"> | |
| Manim Animation Studio | |
| </div> | |
| <p class="subtitle">Create stunning mathematical animations with the power of AI</p> | |
| """, unsafe_allow_html=True) | |
| # Check for packages ONLY ONCE per session | |
| if not st.session_state.packages_checked: | |
| if ensure_packages(): | |
| st.session_state.packages_checked = True | |
| else: | |
| st.error("Failed to install required packages. Please try again.") | |
| st.stop() | |
| # Create main tabs | |
| tab_names = ["✨ Editor", "🤖 AI Assistant", "🎨 Assets", "🎞️ Timeline", "🎓 Educational Export", "🐍 Python Runner", "🔧 C/C++ Runner"] | |
| tabs = st.tabs(tab_names) | |
| # Sidebar for rendering settings and custom libraries | |
| with st.sidebar: | |
| # Sidebar header with branding | |
| st.markdown(""" | |
| <div style="text-align: center; padding: 1rem 0; margin-bottom: 1.5rem; border-bottom: 1px solid var(--border-color);"> | |
| <h2 style="margin: 0; font-size: 1.5rem; background: linear-gradient(135deg, #6366f1, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">Control Panel</h2> | |
| <p style="margin: 0.5rem 0 0 0; font-size: 0.8rem; color: var(--text-secondary);">Configure your animation settings</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Rendering settings section with improved layout | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 1rem;"> | |
| <span style="font-size: 1.3rem;">⚙️</span> | |
| <span style="font-size: 1.1rem; font-weight: 600; color: var(--text-primary);">Rendering Settings</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| quality = st.selectbox( | |
| "Quality", | |
| options=list(QUALITY_PRESETS.keys()), | |
| index=list(QUALITY_PRESETS.keys()).index(st.session_state.settings["quality"]), | |
| key="quality_select", | |
| help="Select output video quality" | |
| ) | |
| with col2: | |
| format_type_display = st.selectbox( | |
| "Format", | |
| options=list(EXPORT_FORMATS.keys()), | |
| index=list(EXPORT_FORMATS.values()).index(st.session_state.settings["format_type"]) | |
| if st.session_state.settings["format_type"] in EXPORT_FORMATS.values() else 0, | |
| key="format_select_display", | |
| help="Select output file format" | |
| ) | |
| # Convert display name to actual format value | |
| format_type = EXPORT_FORMATS[format_type_display] | |
| col3, col4 = st.columns(2) | |
| with col3: | |
| # Add FPS control | |
| fps = st.selectbox( | |
| "Frame Rate", | |
| options=FPS_OPTIONS, | |
| index=FPS_OPTIONS.index(st.session_state.settings["fps"]) if st.session_state.settings["fps"] in FPS_OPTIONS else 2, | |
| key="fps_select", | |
| help="Frames per second for the animation" | |
| ) | |
| with col4: | |
| animation_speed = st.selectbox( | |
| "Speed", | |
| options=list(ANIMATION_SPEEDS.keys()), | |
| index=list(ANIMATION_SPEEDS.keys()).index(st.session_state.settings["animation_speed"]), | |
| key="speed_select", | |
| help="Animation playback speed" | |
| ) | |
| # Apply the settings without requiring a button | |
| st.session_state.settings = { | |
| "quality": quality, | |
| "format_type": format_type, | |
| "animation_speed": animation_speed, | |
| "fps": fps | |
| } | |
| # Divider | |
| st.markdown("<hr style='margin: 1.5rem 0; border-color: var(--border-color); opacity: 0.3;'>", unsafe_allow_html=True) | |
| # Generate Animation Button | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 1rem;"> | |
| <span style="font-size: 1.3rem;">🚀</span> | |
| <span style="font-size: 1.1rem; font-weight: 600; color: var(--text-primary);">Generate</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| generate_btn = st.button("🚀 Generate Animation", use_container_width=True, key="generate_btn", type="primary") | |
| if generate_btn: | |
| if not st.session_state.code: | |
| st.error("Please enter some code first") | |
| else: | |
| # Extract scene class name | |
| scene_class = extract_scene_class_name(st.session_state.code) | |
| # If no valid scene class found, add a basic one | |
| if scene_class == "MyScene" and "class MyScene" not in st.session_state.code: | |
| default_scene = """ | |
| class MyScene(Scene): | |
| def construct(self): | |
| text = Text("Default Scene") | |
| self.play(Write(text)) | |
| self.wait(2) | |
| """ | |
| st.session_state.code += default_scene | |
| st.session_state.temp_code = st.session_state.code | |
| st.warning("No scene class found. Added a default scene.") | |
| with st.spinner("Generating animation..."): | |
| video_data, status = generate_manim_video( | |
| st.session_state.code, | |
| st.session_state.settings["format_type"], | |
| st.session_state.settings["quality"], | |
| ANIMATION_SPEEDS[st.session_state.settings["animation_speed"]], | |
| st.session_state.audio_path, | |
| st.session_state.settings["fps"] | |
| ) | |
| st.session_state.video_data = video_data | |
| st.session_state.status = status | |
| # Divider | |
| st.markdown("<hr style='margin: 1.5rem 0; border-color: var(--border-color); opacity: 0.3;'>", unsafe_allow_html=True) | |
| # Custom libraries section with improved styling | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 1rem;"> | |
| <span style="font-size: 1.3rem;">📚</span> | |
| <span style="font-size: 1.1rem; font-weight: 600; color: var(--text-primary);">Custom Libraries</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("<p style='font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem;'>Add Python packages for your animations</p>", unsafe_allow_html=True) | |
| custom_libraries = st.text_area( | |
| "Libraries to install", | |
| placeholder="e.g., scipy, networkx, matplotlib", | |
| key="custom_libraries" | |
| ) | |
| if st.button("Install Libraries", key="install_libraries_btn"): | |
| success, result = install_custom_packages(custom_libraries) | |
| st.session_state.custom_library_result = result | |
| if success: | |
| st.success("Installation complete!") | |
| else: | |
| st.error("Installation failed for some packages.") | |
| if st.session_state.custom_library_result: | |
| with st.expander("Installation Results"): | |
| st.code(st.session_state.custom_library_result) | |
| # System Package Management section | |
| with st.sidebar.expander("🛠️ System Package Management"): | |
| st.markdown("## System Dependencies") | |
| st.markdown("Manage system packages and libraries") | |
| # Auto-detect C/C++ libraries | |
| if st.button("Detect Installed Libraries", key="detect_system_libs"): | |
| with st.spinner("Detecting installed libraries..."): | |
| libraries = detect_cpp_libraries() | |
| # Display results | |
| st.markdown("### Detected Libraries") | |
| for lib, installed in libraries.items(): | |
| if installed: | |
| st.success(f"✅ {lib}: Installed") | |
| else: | |
| st.warning(f"⚠️ {lib}: Not detected") | |
| # Install C/C++ libraries | |
| st.markdown("### Install C/C++ Libraries") | |
| cpp_libs_to_install = st.multiselect( | |
| "Select libraries to install", | |
| options=["Eigen", "Boost", "OpenCV", "FFTW", "SDL2", "SFML", "OpenGL"], | |
| default=[] | |
| ) | |
| if st.button("Install Selected Libraries", key="install_cpp_libs"): | |
| success, result = install_cpp_libraries(cpp_libs_to_install) | |
| if success: | |
| st.success("Libraries installed successfully!") | |
| else: | |
| st.error("Failed to install some libraries") | |
| st.code(result) | |
| # System package installation | |
| st.markdown("### Install System Packages") | |
| system_packages = st.text_area( | |
| "Enter system packages to install (comma separated)", | |
| placeholder="e.g., ffmpeg, git, cmake" | |
| ) | |
| if st.button("Install System Packages", key="install_system_packages"): | |
| if not system_packages.strip(): | |
| st.warning("No packages specified") | |
| else: | |
| packages = [pkg.strip() for pkg in system_packages.split(',') if pkg.strip()] | |
| # Detect package manager | |
| package_manager = None | |
| install_cmd = [] | |
| if platform.system() == "Linux": | |
| which_apt = subprocess.run(["which", "apt-get"], capture_output=True, text=True) | |
| which_dnf = subprocess.run(["which", "dnf"], capture_output=True, text=True) | |
| which_yum = subprocess.run(["which", "yum"], capture_output=True, text=True) | |
| which_pacman = subprocess.run(["which", "pacman"], capture_output=True, text=True) | |
| if which_apt.returncode == 0: | |
| package_manager = "apt-get" | |
| install_cmd = ["apt-get", "install", "-y"] | |
| elif which_dnf.returncode == 0: | |
| package_manager = "dnf" | |
| install_cmd = ["dnf", "install", "-y"] | |
| elif which_yum.returncode == 0: | |
| package_manager = "yum" | |
| install_cmd = ["yum", "install", "-y"] | |
| elif which_pacman.returncode == 0: | |
| package_manager = "pacman" | |
| install_cmd = ["pacman", "-S", "--noconfirm"] | |
| elif platform.system() == "Darwin": | |
| which_brew = subprocess.run(["which", "brew"], capture_output=True, text=True) | |
| if which_brew.returncode == 0: | |
| package_manager = "brew" | |
| install_cmd = ["brew", "install"] | |
| if not package_manager: | |
| st.error(f"Could not detect package manager for {platform.system()}. Please install packages manually.") | |
| else: | |
| # Ask for sudo password if needed | |
| sudo_password = None | |
| if is_sudo_available() and platform.system() != "Darwin": # macOS Homebrew doesn't need sudo | |
| sudo_password = st.text_input("Enter sudo password for system package installation:", type="password") | |
| # Update package lists if needed | |
| if package_manager in ["apt-get", "apt"]: | |
| with st.spinner("Updating package lists..."): | |
| try: | |
| if is_sudo_available() and sudo_password: | |
| result = run_with_sudo(["apt-get", "update"], sudo_password) | |
| elif is_sudo_available(): | |
| result = run_with_sudo(["apt-get", "update"]) | |
| else: | |
| result = subprocess.run(["apt-get", "update"], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| st.warning(f"Failed to update package lists: {result.stderr}") | |
| except Exception as e: | |
| st.warning(f"Error updating package lists: {str(e)}") | |
| # Install packages | |
| results = [] | |
| success = True | |
| progress_bar = st.sidebar.progress(0) | |
| status_text = st.sidebar.empty() | |
| for i, package in enumerate(packages): | |
| try: | |
| progress = (i / len(packages)) | |
| progress_bar.progress(progress) | |
| status_text.text(f"Installing {package}...") | |
| cmd = install_cmd + [package] | |
| if is_sudo_available() and platform.system() != "Darwin": # macOS Homebrew doesn't need sudo | |
| if sudo_password: | |
| result = run_with_sudo(cmd, sudo_password) | |
| else: | |
| result = run_with_sudo(cmd) | |
| else: | |
| result = subprocess.run(cmd, capture_output=True, text=True) | |
| if result.returncode != 0: | |
| error_msg = f"Failed to install {package}: {result.stderr}" | |
| results.append(error_msg) | |
| success = False | |
| else: | |
| results.append(f"Successfully installed {package}") | |
| except Exception as e: | |
| error_msg = f"Error installing {package}: {str(e)}" | |
| results.append(error_msg) | |
| success = False | |
| progress_bar.progress(1.0) | |
| time.sleep(0.5) | |
| progress_bar.empty() | |
| status_text.empty() | |
| if success: | |
| st.success("All packages installed successfully!") | |
| else: | |
| st.error("Failed to install some packages") | |
| st.code("\n".join(results)) | |
| # C/C++ Library Options | |
| with st.sidebar.expander("C/C++ Library Options"): | |
| st.markdown("### Advanced C/C++ Settings") | |
| cpp_libs = st.multiselect( | |
| "Include Libraries", | |
| options=["Eigen", "Boost", "OpenCV", "FFTW", "Matplotlib-cpp"], | |
| default=st.session_state.cpp_settings.get("libraries", []) | |
| ) | |
| st.session_state.cpp_settings["libraries"] = cpp_libs | |
| custom_include = st.text_input("Custom Include Path:") | |
| custom_lib = st.text_input("Custom Library Path:") | |
| if custom_include and custom_include not in st.session_state.cpp_settings.get("include_paths", []): | |
| if "include_paths" not in st.session_state.cpp_settings: | |
| st.session_state.cpp_settings["include_paths"] = [] | |
| st.session_state.cpp_settings["include_paths"].append(custom_include) | |
| if custom_lib and custom_lib not in st.session_state.cpp_settings.get("library_paths", []): | |
| if "library_paths" not in st.session_state.cpp_settings: | |
| st.session_state.cpp_settings["library_paths"] = [] | |
| st.session_state.cpp_settings["library_paths"].append(custom_lib) | |
| if st.button("Update Library Settings"): | |
| st.success("Library settings updated!") | |
| # EDITOR TAB | |
| with tabs[0]: | |
| col1, col2 = st.columns([3, 2]) | |
| with col1: | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 40px; height: 40px; background: linear-gradient(135deg, #6366f1, #8b5cf6); border-radius: 10px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.3rem;">📝</span> | |
| </div> | |
| <div> | |
| <h3 style="margin: 0; font-size: 1.3rem; color: var(--text-primary);">Animation Editor</h3> | |
| <p style="margin: 0; font-size: 0.85rem; color: var(--text-secondary);">Write your Manim code below</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Toggle between upload and type | |
| editor_mode = st.radio( | |
| "Input method:", | |
| ["Type Code", "Upload File"], | |
| key="editor_mode", | |
| horizontal=True | |
| ) | |
| # File upload section | |
| if editor_mode == "Upload File": | |
| uploaded_file = st.file_uploader( | |
| "Upload Manim Python File", | |
| type=["py"], | |
| key="code_uploader", | |
| help="Upload a .py file containing your Manim animation code" | |
| ) | |
| if uploaded_file: | |
| try: | |
| code_content = uploaded_file.getvalue().decode("utf-8") | |
| if code_content.strip(): | |
| # Check if this is a new file upload by comparing content | |
| if st.session_state.code != code_content: | |
| st.session_state.code = code_content | |
| st.session_state.temp_code = code_content | |
| # Increment editor key to force refresh | |
| st.session_state.editor_key += 1 | |
| st.success(f"Successfully loaded: {uploaded_file.name} ({len(code_content)} characters)") | |
| st.rerun() | |
| else: | |
| st.warning("The uploaded file appears to be empty.") | |
| except UnicodeDecodeError: | |
| st.error("Error: Could not decode the file. Please ensure it's a valid text file.") | |
| except Exception as e: | |
| st.error(f"Error reading file: {str(e)}") | |
| # Get current code for editor | |
| current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else "" | |
| # Code editor with enhanced settings | |
| if ACE_EDITOR_AVAILABLE: | |
| st.session_state.temp_code = st_ace( | |
| value=current_code, | |
| language="python", | |
| theme="dracula", | |
| min_lines=25, | |
| max_lines=50, | |
| font_size=14, | |
| tab_size=4, | |
| wrap=True, | |
| show_gutter=True, | |
| show_print_margin=False, | |
| auto_update=True, | |
| readonly=False, | |
| key=f"ace_editor_{st.session_state.editor_key}", | |
| annotations=[], | |
| markers=[], | |
| keybinding="vscode" | |
| ) | |
| else: | |
| st.session_state.temp_code = st.text_area( | |
| "Manim Python Code", | |
| value=current_code, | |
| height=500, | |
| key=f"code_textarea_{st.session_state.editor_key}", | |
| placeholder="# Enter your Manim code here...\nfrom manim import *\n\nclass MyScene(Scene):\n def construct(self):\n # Your animation code\n pass" | |
| ) | |
| # Update code in session state if it changed | |
| if st.session_state.temp_code != st.session_state.code: | |
| st.session_state.code = st.session_state.temp_code | |
| with col2: | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 40px; height: 40px; background: linear-gradient(135deg, #06b6d4, #10b981); border-radius: 10px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.3rem;">🖥️</span> | |
| </div> | |
| <div> | |
| <h3 style="margin: 0; font-size: 1.3rem; color: var(--text-primary);">Preview & Output</h3> | |
| <p style="margin: 0; font-size: 0.85rem; color: var(--text-secondary);">View your animation here</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Preview container | |
| if st.session_state.code: | |
| with st.container(): | |
| st.markdown("<div class='preview-container'>", unsafe_allow_html=True) | |
| preview_html = generate_manim_preview(st.session_state.code) | |
| components.html(preview_html, height=250) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| # Generated output display | |
| if st.session_state.video_data: | |
| # Different handling based on format type | |
| format_type = st.session_state.settings["format_type"] | |
| if format_type == "png_sequence": | |
| st.info("PNG sequence generated successfully. Use the download button to get the ZIP file.") | |
| # Add download button for ZIP | |
| st.download_button( | |
| label="⬇️ Download PNG Sequence (ZIP)", | |
| data=st.session_state.video_data, | |
| file_name=f"manim_pngs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip", | |
| mime="application/zip", | |
| use_container_width=True | |
| ) | |
| elif format_type == "svg": | |
| # Display SVG preview | |
| try: | |
| svg_data = st.session_state.video_data.decode('utf-8') | |
| components.html(svg_data, height=400) | |
| except Exception as e: | |
| st.error(f"Error displaying SVG: {str(e)}") | |
| # Download button for SVG | |
| st.download_button( | |
| label="⬇️ Download SVG", | |
| data=st.session_state.video_data, | |
| file_name=f"manim_animation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.svg", | |
| mime="image/svg+xml", | |
| use_container_width=True | |
| ) | |
| else: | |
| # Standard video display for MP4, GIF, WebM | |
| try: | |
| st.video(st.session_state.video_data, format=format_type) | |
| except Exception as e: | |
| st.error(f"Error displaying video: {str(e)}") | |
| # Fallback for GIF if st.video fails | |
| if format_type == "gif": | |
| st.markdown("GIF preview:") | |
| gif_b64 = base64.b64encode(st.session_state.video_data).decode() | |
| st.markdown(f'<img src="data:image/gif;base64,{gif_b64}" alt="animation" style="width:100%">', unsafe_allow_html=True) | |
| # Add download button | |
| st.download_button( | |
| label=f"⬇️ Download {format_type.upper()}", | |
| data=st.session_state.video_data, | |
| file_name=f"manim_animation_{datetime.now().strftime('%Y%m%d_%H%M%S')}.{format_type}", | |
| mime=f"{'image' if format_type == 'gif' else 'video'}/{format_type}", | |
| use_container_width=True | |
| ) | |
| if st.session_state.status: | |
| if "Error" in st.session_state.status: | |
| st.error(st.session_state.status) | |
| # Show troubleshooting tips | |
| with st.expander("🔍 Troubleshooting Tips"): | |
| st.markdown(""" | |
| ### Common Issues: | |
| 1. **Syntax Errors**: Check your Python code for any syntax issues | |
| 2. **Missing Scene Class**: Ensure your code contains a scene class that extends Scene | |
| 3. **High Resolution Issues**: Try a lower quality preset for complex animations | |
| 4. **Memory Issues**: For 4K animations, reduce complexity or try again | |
| 5. **Format Issues**: Some formats require specific Manim configurations | |
| 6. **GIF Generation**: If GIF doesn't work, try MP4 and we'll convert it automatically | |
| ### Example Code: | |
| ```python | |
| from manim import * | |
| class MyScene(Scene): | |
| def construct(self): | |
| circle = Circle(color=RED) | |
| self.play(Create(circle)) | |
| self.wait(1) | |
| ``` | |
| """) | |
| else: | |
| st.success(st.session_state.status) | |
| # AI ASSISTANT TAB | |
| with tabs[1]: | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 48px; height: 48px; background: linear-gradient(135deg, #8b5cf6, #ec4899); border-radius: 12px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.5rem;">🤖</span> | |
| </div> | |
| <div> | |
| <h2 style="margin: 0; font-size: 1.5rem; color: var(--text-primary);">AI Animation Assistant</h2> | |
| <p style="margin: 0; font-size: 0.9rem; color: var(--text-secondary);">Let AI help you create stunning Manim animations</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Check password before allowing access | |
| if check_password(): | |
| # Debug section | |
| with st.expander("🔧 Debug Connection"): | |
| st.markdown("Test the AI model connection directly") | |
| if st.button("Test API Connection", key="test_api_btn"): | |
| with st.spinner("Testing API connection..."): | |
| try: | |
| # Get token from secrets | |
| token = get_secret("github_token_api") | |
| if not token: | |
| st.error("GitHub token not found in secrets") | |
| st.stop() | |
| # Get model details | |
| model_name = st.session_state.custom_model | |
| config = MODEL_CONFIGS.get(model_name, MODEL_CONFIGS["default"]) | |
| category = config.get("category", "Other") | |
| if category == "OpenAI": | |
| # Use OpenAI client for GitHub AI models | |
| try: | |
| from openai import OpenAI | |
| except ImportError: | |
| st.error("OpenAI package not installed. Please run 'pip install openai'") | |
| st.stop() | |
| # Create OpenAI client with GitHub AI endpoint | |
| client = OpenAI( | |
| base_url="https://models.github.ai/inference", | |
| api_key=token, | |
| ) | |
| # For GitHub AI models, ensure the model_name includes the publisher | |
| # If it doesn't have a publisher prefix, add "openai/" | |
| if "/" not in model_name: | |
| full_model_name = f"openai/{model_name}" | |
| st.info(f"Using full model name: {full_model_name}") | |
| else: | |
| full_model_name = model_name | |
| # Prepare parameters based on model configuration | |
| params = { | |
| "messages": [ | |
| {"role": "system", "content": "You are a helpful assistant."}, | |
| {"role": "user", "content": "Hello, this is a connection test."} | |
| ], | |
| "model": full_model_name | |
| } | |
| # Add appropriate token parameter | |
| token_param = config["param_name"] | |
| params[token_param] = config[token_param] | |
| # Make API call | |
| response = client.chat.completions.create(**params) | |
| # Check if response is valid | |
| if response and response.choices and len(response.choices) > 0: | |
| test_response = response.choices[0].message.content | |
| st.success(f"✅ Connection successful! Response: {test_response[:50]}...") | |
| # Save working connection to session state | |
| st.session_state.ai_models = { | |
| "openai_client": client, | |
| "model_name": full_model_name, # Store the full model name | |
| "endpoint": "https://models.github.ai/inference", | |
| "last_loaded": datetime.now().isoformat(), | |
| "category": category | |
| } | |
| else: | |
| st.error("❌ API returned an empty response") | |
| elif category == "Azure" or category in ["DeepSeek", "Meta", "Microsoft", "Mistral", "Other"]: | |
| # Use Azure client for Azure API models | |
| try: | |
| from azure.ai.inference import ChatCompletionsClient | |
| from azure.ai.inference.models import SystemMessage, UserMessage | |
| from azure.core.credentials import AzureKeyCredential | |
| except ImportError: | |
| st.error("Azure AI packages not installed. Please run 'pip install azure-ai-inference azure-core'") | |
| st.stop() | |
| # Define endpoint | |
| endpoint = "https://models.inference.ai.azure.com" | |
| # Prepare API parameters | |
| messages = [UserMessage("Hello, this is a connection test.")] | |
| api_params, config = prepare_api_params(messages, model_name) | |
| # Create client with appropriate API version | |
| api_version = config.get("api_version") | |
| if api_version: | |
| client = ChatCompletionsClient( | |
| endpoint=endpoint, | |
| credential=AzureKeyCredential(token), | |
| api_version=api_version | |
| ) | |
| else: | |
| client = ChatCompletionsClient( | |
| endpoint=endpoint, | |
| credential=AzureKeyCredential(token), | |
| ) | |
| # Test with the prepared parameters | |
| response = client.complete(**api_params) | |
| # Check if response is valid | |
| if response and response.choices and len(response.choices) > 0: | |
| test_response = response.choices[0].message.content | |
| st.success(f"✅ Connection successful! Response: {test_response[:50]}...") | |
| # Save working connection to session state | |
| st.session_state.ai_models = { | |
| "client": client, | |
| "model_name": model_name, | |
| "endpoint": endpoint, | |
| "last_loaded": datetime.now().isoformat(), | |
| "category": category, | |
| "api_version": api_version | |
| } | |
| else: | |
| st.error("❌ API returned an empty response") | |
| else: | |
| st.error(f"Unsupported model category: {category}") | |
| except ImportError as ie: | |
| st.error(f"Module import error: {str(ie)}") | |
| st.info("Try installing required packages: openai, azure-ai-inference and azure-core") | |
| except Exception as e: | |
| st.error(f"❌ API test failed: {str(e)}") | |
| import traceback | |
| st.code(traceback.format_exc()) | |
| # Model selection with enhanced UI | |
| st.markdown("### 🤖 Model Selection") | |
| st.markdown("Select an AI model for generating animation code:") | |
| # Group models by category for better organization | |
| model_categories = {} | |
| for model_name in MODEL_CONFIGS: | |
| if model_name != "default": | |
| category = MODEL_CONFIGS[model_name].get("category", "Other") | |
| if category not in model_categories: | |
| model_categories[category] = [] | |
| model_categories[category].append(model_name) | |
| # Create tabbed interface for model categories | |
| category_tabs = st.tabs(sorted(model_categories.keys())) | |
| for i, category in enumerate(sorted(model_categories.keys())): | |
| with category_tabs[i]: | |
| for model_name in sorted(model_categories[category]): | |
| config = MODEL_CONFIGS[model_name] | |
| is_selected = model_name == st.session_state.custom_model | |
| warning = config.get("warning") | |
| # Create styled card for each model | |
| warning_html = f'<p style="color: #ff9800; font-size: 0.8rem; margin-top: 5px;">⚠️ {warning}</p>' if warning else "" | |
| st.markdown(f""" | |
| <div class="model-card {'selected-model' if is_selected else ''}"> | |
| <h4>{model_name}</h4> | |
| <div class="model-details"> | |
| <p>Max Tokens: {config.get(config['param_name'], 'Unknown')}</p> | |
| <p>Category: {config['category']}</p> | |
| <p>API Version: {config['api_version'] if config['api_version'] else 'Default'}</p> | |
| {warning_html} | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Button to select this model | |
| button_label = "Selected ✓" if is_selected else "Select Model" | |
| if st.button(button_label, key=f"model_{model_name}", disabled=is_selected): | |
| st.session_state.custom_model = model_name | |
| if st.session_state.ai_models and 'model_name' in st.session_state.ai_models: | |
| st.session_state.ai_models['model_name'] = model_name | |
| st.rerun() | |
| # Display current model selection | |
| st.info(f"🤖 **Currently using: {st.session_state.custom_model}**") | |
| # Add a refresh button to update model connection | |
| if st.button("🔄 Refresh Model Connection", key="refresh_model_connection"): | |
| if st.session_state.ai_models and 'client' in st.session_state.ai_models: | |
| try: | |
| # Test connection with minimal prompt | |
| from azure.ai.inference.models import UserMessage | |
| model_name = st.session_state.custom_model | |
| # Prepare parameters | |
| messages = [UserMessage("Hello")] | |
| api_params, config = prepare_api_params(messages, model_name) | |
| # Check if we need a new client with specific API version | |
| if config["api_version"] and config["api_version"] != st.session_state.ai_models.get("api_version"): | |
| # Create version-specific client if needed | |
| token = get_secret("github_token_api") | |
| from azure.ai.inference import ChatCompletionsClient | |
| from azure.core.credentials import AzureKeyCredential | |
| client = ChatCompletionsClient( | |
| endpoint=st.session_state.ai_models["endpoint"], | |
| credential=AzureKeyCredential(token), | |
| api_version=config["api_version"] | |
| ) | |
| response = client.complete(**api_params) | |
| # Update session state with the new client | |
| st.session_state.ai_models["client"] = client | |
| st.session_state.ai_models["api_version"] = config["api_version"] | |
| else: | |
| response = st.session_state.ai_models["client"].complete(**api_params) | |
| st.success(f"✅ Connection to {model_name} successful!") | |
| st.session_state.ai_models["model_name"] = model_name | |
| except Exception as e: | |
| st.error(f"❌ Connection error: {str(e)}") | |
| st.info("Please try the Debug Connection section to re-initialize the API connection.") | |
| # AI code generation | |
| if st.session_state.ai_models and "client" in st.session_state.ai_models: | |
| st.markdown("<div class='card'>", unsafe_allow_html=True) | |
| st.markdown("#### Generate Animation from Description") | |
| st.write("Describe the animation you want to create, or provide partial code to complete.") | |
| # Predefined animation ideas dropdown | |
| animation_ideas = [ | |
| "Select an idea...", | |
| "Create a 3D animation showing a sphere morphing into a torus", | |
| "Show a visual proof of the Pythagorean theorem", | |
| "Visualize a Fourier transform converting a signal from time domain to frequency domain", | |
| "Create an animation explaining neural network forward propagation", | |
| "Illustrate the concept of integration with area under a curve" | |
| ] | |
| selected_idea = st.selectbox( | |
| "Try one of these ideas", | |
| options=animation_ideas | |
| ) | |
| prompt_value = selected_idea if selected_idea != "Select an idea..." else "" | |
| code_input = st.text_area( | |
| "Your Prompt or Code", | |
| value=prompt_value, | |
| placeholder="Example: Create an animation that shows a circle morphing into a square while changing color from red to blue", | |
| height=150 | |
| ) | |
| if st.button("Generate Animation Code", key="gen_ai_code"): | |
| if code_input: | |
| with st.spinner("AI is generating your animation code..."): | |
| try: | |
| # Get the client and model name | |
| client = st.session_state.ai_models["client"] | |
| model_name = st.session_state.ai_models["model_name"] | |
| # Create the prompt | |
| prompt = f"""Write a complete Manim animation scene based on this code or idea: | |
| {code_input} | |
| The code should be a complete, working Manim animation that includes: | |
| - Proper Scene class definition | |
| - Constructor with animations | |
| - Proper use of self.play() for animations | |
| - Proper wait times between animations | |
| Here's the complete Manim code: | |
| """ | |
| # Prepare API parameters | |
| from azure.ai.inference.models import UserMessage | |
| messages = [UserMessage(prompt)] | |
| api_params, config = prepare_api_params(messages, model_name) | |
| # Make the API call with proper parameters | |
| response = client.complete(**api_params) | |
| # Process the response | |
| if response and response.choices and len(response.choices) > 0: | |
| completed_code = response.choices[0].message.content | |
| # Extract code from markdown if present | |
| if "```python" in completed_code: | |
| completed_code = completed_code.split("```python")[1].split("```")[0] | |
| elif "```" in completed_code: | |
| completed_code = completed_code.split("```")[1].split("```")[0] | |
| # Add Scene class if missing | |
| if "Scene" not in completed_code: | |
| completed_code = f"""from manim import * | |
| class MyScene(Scene): | |
| def construct(self): | |
| {completed_code}""" | |
| # Store the generated code | |
| st.session_state.generated_code = completed_code | |
| else: | |
| st.error("Failed to generate code. API returned an empty response.") | |
| except Exception as e: | |
| st.error(f"Error generating code: {str(e)}") | |
| import traceback | |
| st.code(traceback.format_exc()) | |
| else: | |
| st.warning("Please enter a description or prompt first") | |
| # AI generated code display and actions | |
| if "generated_code" in st.session_state and st.session_state.generated_code: | |
| st.markdown("<div class='card'>", unsafe_allow_html=True) | |
| st.markdown("#### Generated Animation Code") | |
| st.code(st.session_state.generated_code, language="python") | |
| col_ai1, col_ai2 = st.columns(2) | |
| with col_ai1: | |
| if st.button("Use This Code", key="use_gen_code"): | |
| st.session_state.code = st.session_state.generated_code | |
| st.session_state.temp_code = st.session_state.generated_code | |
| # Set pending tab switch to editor tab | |
| st.session_state.pending_tab_switch = 0 | |
| st.rerun() | |
| with col_ai2: | |
| if st.button("Render Preview", key="render_preview"): | |
| with st.spinner("Rendering preview..."): | |
| video_data, status = generate_manim_video( | |
| st.session_state.generated_code, | |
| "mp4", | |
| "480p", # Use lowest quality for preview | |
| ANIMATION_SPEEDS["Normal"], | |
| fps=st.session_state.settings["fps"] | |
| ) | |
| if video_data: | |
| st.video(video_data) | |
| st.download_button( | |
| label="Download Preview", | |
| data=video_data, | |
| file_name=f"manim_preview_{int(time.time())}.mp4", | |
| mime="video/mp4" | |
| ) | |
| else: | |
| st.error(f"Failed to generate preview: {status}") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| else: | |
| st.warning("AI models not initialized. Please use the Debug Connection section to test API connectivity.") | |
| else: | |
| st.info("Please enter the correct password to access AI features") | |
| # ASSETS TAB | |
| with tabs[2]: | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 48px; height: 48px; background: linear-gradient(135deg, #ec4899, #f97316); border-radius: 12px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.5rem;">🎨</span> | |
| </div> | |
| <div> | |
| <h2 style="margin: 0; font-size: 1.5rem; color: var(--text-primary);">Asset Management</h2> | |
| <p style="margin: 0; font-size: 0.9rem; color: var(--text-secondary);">Manage images, audio, and other assets for your animations</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| asset_col1, asset_col2 = st.columns([1, 1]) | |
| with asset_col1: | |
| # Image uploader section | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 0.5rem;"> | |
| <span style="font-size: 1.2rem;">📸</span> | |
| <span style="font-size: 1rem; font-weight: 600; color: var(--text-primary);">Image Assets</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("<p style='font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 1rem;'>Upload images to use in your animations</p>", unsafe_allow_html=True) | |
| # Allow multiple image uploads | |
| uploaded_images = st.file_uploader( | |
| "Upload Images", | |
| type=["jpg", "png", "jpeg", "svg", "gif", "webp"], | |
| accept_multiple_files=True, | |
| key="image_uploader_tab", | |
| help="Supported formats: JPG, PNG, JPEG, SVG, GIF, WebP" | |
| ) | |
| if uploaded_images: | |
| try: | |
| # Create a unique image directory if it doesn't exist | |
| image_dir = os.path.join(os.getcwd(), "manim_assets", "images") | |
| os.makedirs(image_dir, exist_ok=True) | |
| new_images_count = 0 | |
| # Process each uploaded image | |
| for uploaded_image in uploaded_images: | |
| try: | |
| # Generate a unique filename and save the image | |
| file_extension = uploaded_image.name.split(".")[-1].lower() | |
| unique_filename = f"image_{int(time.time())}_{uuid.uuid4().hex[:8]}.{file_extension}" | |
| image_path = os.path.join(image_dir, unique_filename) | |
| with open(image_path, "wb") as f: | |
| f.write(uploaded_image.getvalue()) | |
| # Store the path in session state | |
| if "image_paths" not in st.session_state: | |
| st.session_state.image_paths = [] | |
| # Check if this image was already added | |
| image_already_added = False | |
| for img in st.session_state.image_paths: | |
| if img["name"] == uploaded_image.name: | |
| image_already_added = True | |
| break | |
| if not image_already_added: | |
| st.session_state.image_paths.append({ | |
| "name": uploaded_image.name, | |
| "path": image_path | |
| }) | |
| new_images_count += 1 | |
| except Exception as e: | |
| st.error(f"Error uploading {uploaded_image.name}: {str(e)}") | |
| # Show success message | |
| if new_images_count > 0: | |
| st.success(f"Successfully uploaded {new_images_count} image(s)") | |
| elif len(uploaded_images) > 0: | |
| st.info("Images already in library") | |
| except Exception as e: | |
| st.error(f"Error processing images: {str(e)}") | |
| # Display uploaded images in a grid | |
| st.markdown("##### Uploaded Images:") | |
| image_cols = st.columns(3) | |
| for i, img_info in enumerate(st.session_state.image_paths[-len(uploaded_images):]): | |
| with image_cols[i % 3]: | |
| try: | |
| img = Image.open(img_info["path"]) | |
| st.image(img, caption=img_info["name"], width=150) | |
| # Show code snippet for this specific image | |
| if st.button(f"Use {img_info['name']}", key=f"use_img_{i}"): | |
| image_code = f""" | |
| # Load and display image | |
| image = ImageMobject(r"{img_info['path']}") | |
| image.scale(2) # Adjust size as needed | |
| self.play(FadeIn(image)) | |
| self.wait(1) | |
| """ | |
| if not st.session_state.code: | |
| base_code = """from manim import * | |
| class ImageScene(Scene): | |
| def construct(self): | |
| """ | |
| st.session_state.code = base_code + "\n " + image_code.replace("\n", "\n ") | |
| else: | |
| st.session_state.code += "\n" + image_code | |
| st.session_state.temp_code = st.session_state.code | |
| st.success(f"Added {img_info['name']} to your code!") | |
| # Set pending tab switch to editor tab | |
| st.session_state.pending_tab_switch = 0 | |
| st.rerun() | |
| except Exception as e: | |
| st.error(f"Error loading image {img_info['name']}: {e}") | |
| # Display previously uploaded images | |
| if st.session_state.image_paths: | |
| with st.expander("Previously Uploaded Images"): | |
| # Group images by 3 in each row | |
| for i in range(0, len(st.session_state.image_paths), 3): | |
| prev_cols = st.columns(3) | |
| for j in range(3): | |
| if i+j < len(st.session_state.image_paths): | |
| img_info = st.session_state.image_paths[i+j] | |
| with prev_cols[j]: | |
| try: | |
| img = Image.open(img_info["path"]) | |
| st.image(img, caption=img_info["name"], width=100) | |
| st.markdown(f"<div class='small-text'>Path: {img_info['path']}</div>", unsafe_allow_html=True) | |
| except: | |
| st.markdown(f"**{img_info['name']}**") | |
| st.markdown(f"<div class='small-text'>Path: {img_info['path']}</div>", unsafe_allow_html=True) | |
| with asset_col2: | |
| # Audio uploader section | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 0.5rem;"> | |
| <span style="font-size: 1.2rem;">🎵</span> | |
| <span style="font-size: 1rem; font-weight: 600; color: var(--text-primary);">Audio Assets</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("<p style='font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 1rem;'>Upload audio files for background or narration</p>", unsafe_allow_html=True) | |
| uploaded_audio = st.file_uploader( | |
| "Upload Audio", | |
| type=["mp3", "wav", "ogg", "m4a", "flac"], | |
| key="audio_uploader", | |
| help="Supported formats: MP3, WAV, OGG, M4A, FLAC" | |
| ) | |
| if uploaded_audio: | |
| try: | |
| # Create a unique audio directory if it doesn't exist | |
| audio_dir = os.path.join(os.getcwd(), "manim_assets", "audio") | |
| os.makedirs(audio_dir, exist_ok=True) | |
| # Generate a unique filename and save the audio | |
| file_extension = uploaded_audio.name.split(".")[-1] | |
| unique_filename = f"audio_{int(time.time())}.{file_extension}" | |
| audio_path = os.path.join(audio_dir, unique_filename) | |
| with open(audio_path, "wb") as f: | |
| f.write(uploaded_audio.getvalue()) | |
| # Store the path in session state | |
| st.session_state.audio_path = audio_path | |
| # Success message | |
| st.success(f"Audio uploaded: {uploaded_audio.name}") | |
| # Display audio player | |
| st.audio(uploaded_audio) | |
| st.markdown(f""" | |
| <div class="asset-card"> | |
| <p><strong>Audio: {uploaded_audio.name}</strong></p> | |
| <p class="small-text">Path: {audio_path}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| except Exception as e: | |
| st.error(f"Error uploading audio: {str(e)}") | |
| # Two options for audio usage | |
| st.markdown("#### Add Audio to Your Animation") | |
| option = st.radio( | |
| "Choose how to use audio:", | |
| ["Background Audio", "Generate Audio from Text"] | |
| ) | |
| if option == "Background Audio": | |
| st.markdown("##### Code to add background audio:") | |
| # For with_sound decorator | |
| audio_code1 = f""" | |
| # Add this import at the top of your file | |
| from manim.scene.scene_file_writer import SceneFileWriter | |
| # Add this decorator before your scene class | |
| @with_sound("{audio_path}") | |
| class YourScene(Scene): | |
| def construct(self): | |
| # Your animation code here | |
| """ | |
| st.code(audio_code1, language="python") | |
| if st.button("Use This Audio in Animation", key="use_audio_btn"): | |
| st.success("Audio set for next render!") | |
| elif option == "Generate Audio from Text": | |
| # Text-to-speech input | |
| tts_text = st.text_area( | |
| "Enter text for narration", | |
| placeholder="Type the narration text here...", | |
| height=100 | |
| ) | |
| if st.button("Create Narration", key="create_narration_btn"): | |
| try: | |
| # Use basic TTS (placeholder for actual implementation) | |
| st.warning("Text-to-speech feature requires additional setup. Using uploaded audio instead.") | |
| st.session_state.audio_path = audio_path | |
| st.success("Audio set for next render!") | |
| except Exception as e: | |
| st.error(f"Error creating narration: {str(e)}") | |
| # TIMELINE EDITOR TAB | |
| with tabs[3]: | |
| # New code for reordering animation steps | |
| updated_code = create_timeline_editor(st.session_state.code) | |
| # If code was modified by the timeline editor, update the session state | |
| if updated_code != st.session_state.code: | |
| st.session_state.code = updated_code | |
| st.session_state.temp_code = updated_code | |
| # EDUCATIONAL EXPORT TAB | |
| with tabs[4]: | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 48px; height: 48px; background: linear-gradient(135deg, #3b82f6, #8b5cf6); border-radius: 12px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.5rem;">🎓</span> | |
| </div> | |
| <div> | |
| <h2 style="margin: 0; font-size: 1.5rem; color: var(--text-primary);">Educational Export</h2> | |
| <p style="margin: 0; font-size: 0.9rem; color: var(--text-secondary);">Create educational materials from your animations</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Check if we have an animation to export | |
| if not st.session_state.video_data: | |
| st.warning("Generate an animation first before using educational export features.") | |
| else: | |
| st.markdown("<p style='color: var(--text-secondary); margin-bottom: 1rem;'>Create various educational assets from your animation:</p>", unsafe_allow_html=True) | |
| # Animation title and explanation | |
| animation_title = st.text_input("Animation Title", value="Manim Animation", key="edu_title") | |
| st.markdown("#### Explanation Text") | |
| st.markdown("Add explanatory text to accompany your animation. Use markdown formatting.") | |
| st.markdown("Use ## to separate explanation sections for step-by-step sequence export.") | |
| explanation_text = st.text_area( | |
| "Explanation (markdown supported)", | |
| height=150, | |
| placeholder="Explain your animation here...\n\n## Step 1\nIntroduction to the concept...\n\n## Step 2\nNext, we demonstrate..." | |
| ) | |
| # Export format selection | |
| edu_format = st.selectbox( | |
| "Export Format", | |
| options=["PowerPoint Presentation", "Interactive HTML", "Explanation Sequence PDF"] | |
| ) | |
| # Format-specific options | |
| if edu_format == "PowerPoint Presentation": | |
| st.info("Creates a PowerPoint file with your animation and explanation text.") | |
| elif edu_format == "Interactive HTML": | |
| st.info("Creates an interactive HTML webpage with playback controls and explanation.") | |
| include_controls = st.checkbox("Include interactive controls", value=True) | |
| elif edu_format == "Explanation Sequence PDF": | |
| st.info("Creates a PDF with key frames and step-by-step explanations.") | |
| frame_count = st.slider("Number of key frames", min_value=3, max_value=10, value=5) | |
| # Export button | |
| if st.button("Export Educational Material", key="export_edu_btn"): | |
| with st.spinner(f"Creating {edu_format}..."): | |
| # Map selected format to internal format type | |
| format_map = { | |
| "PowerPoint Presentation": "powerpoint", | |
| "Interactive HTML": "html", | |
| "Explanation Sequence PDF": "sequence" | |
| } | |
| # Create a temporary directory for export | |
| temp_export_dir = tempfile.mkdtemp(prefix="manim_edu_export_") | |
| # Process the export | |
| exported_data, file_type = export_to_educational_format( | |
| st.session_state.video_data, | |
| format_map[edu_format], | |
| animation_title, | |
| explanation_text, | |
| temp_export_dir | |
| ) | |
| if exported_data: | |
| # File extension mapping | |
| ext_map = { | |
| "powerpoint": "pptx", | |
| "html": "html", | |
| "pdf": "pdf" | |
| } | |
| # Download button | |
| ext = ext_map.get(file_type, "zip") | |
| filename = f"{animation_title.replace(' ', '_')}.{ext}" | |
| st.success(f"{edu_format} created successfully!") | |
| st.download_button( | |
| label=f"⬇️ Download {edu_format}", | |
| data=exported_data, | |
| file_name=filename, | |
| mime=f"application/{ext}", | |
| use_container_width=True | |
| ) | |
| # For HTML, also offer to open in browser | |
| if file_type == "html": | |
| html_path = os.path.join(temp_export_dir, filename) | |
| st.markdown(f"[🌐 Open in browser](file://{html_path})", unsafe_allow_html=True) | |
| else: | |
| st.error(f"Failed to create {edu_format}. Check logs for details.") | |
| # Show usage examples and tips | |
| with st.expander("Usage Tips"): | |
| st.markdown(""" | |
| ### Educational Export Tips | |
| **PowerPoint Presentations** | |
| - Great for lectures and classroom presentations | |
| - Animation will autoplay when clicked | |
| - Add detailed explanations in notes section | |
| **Interactive HTML** | |
| - Perfect for websites and online learning platforms | |
| - Students can control playback speed and navigation | |
| - Mobile-friendly for learning on any device | |
| **Explanation Sequence** | |
| - Ideal for printed materials and study guides | |
| - Use ## headers to mark different explanation sections | |
| - Each section will be paired with a key frame | |
| """) | |
| # PYTHON RUNNER TAB | |
| with tabs[5]: | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 48px; height: 48px; background: linear-gradient(135deg, #22c55e, #10b981); border-radius: 12px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.5rem;">🐍</span> | |
| </div> | |
| <div> | |
| <h2 style="margin: 0; font-size: 1.5rem; color: var(--text-primary);">Python Script Runner</h2> | |
| <p style="margin: 0; font-size: 0.9rem; color: var(--text-secondary);">Execute Python scripts and visualize the results directly</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # New UI elements for advanced features | |
| with st.expander("🔧 Advanced Python Features"): | |
| py_feature_col1, py_feature_col2 = st.columns(2) | |
| with py_feature_col1: | |
| enable_debugging = st.checkbox("Enable Debugging", value=False, key="py_debug_enable") | |
| enable_profiling = st.checkbox("Enable Profiling", value=False, key="py_profile_enable") | |
| with py_feature_col2: | |
| py_libs = st.multiselect( | |
| "Additional Libraries", | |
| options=["numpy", "scipy", "pandas", "matplotlib", "seaborn", "plotly", "scikit-learn", "tensorflow", "pytorch", "sympy"], | |
| default=["numpy", "matplotlib"], | |
| key="py_additional_libs" | |
| ) | |
| # Multi-file project support | |
| with st.expander("📁 Multi-file Project"): | |
| st.markdown("Add multiple Python files to your project") | |
| # File manager | |
| if "py_project_files" not in st.session_state: | |
| st.session_state.py_project_files = {"main.py": st.session_state.python_script} | |
| # File selector | |
| current_file = st.selectbox( | |
| "Select File", | |
| options=list(st.session_state.py_project_files.keys()), | |
| key="py_current_file" | |
| ) | |
| # New file creation | |
| new_file_col1, new_file_col2 = st.columns([3, 1]) | |
| with new_file_col1: | |
| new_filename = st.text_input("New File Name", value="", key="py_new_filename") | |
| with new_file_col2: | |
| if st.button("Add File", key="py_add_file_btn"): | |
| if new_filename and new_filename not in st.session_state.py_project_files: | |
| if not new_filename.endswith(".py"): | |
| new_filename += ".py" | |
| st.session_state.py_project_files[new_filename] = "# New Python file\n\n" | |
| st.session_state.py_current_file = new_filename | |
| st.experimental_rerun() | |
| # Update the current file content in session state | |
| if current_file in st.session_state.py_project_files: | |
| st.session_state.py_project_files[current_file] = st.session_state.python_script | |
| # Update main script if we're editing the main file | |
| if current_file == "main.py": | |
| st.session_state.python_script = st.session_state.python_script | |
| # Real-time visualization toggle | |
| real_time_viz = st.checkbox("Enable Real-time Visualization", value=False, key="py_realtime_viz") | |
| # Predefined example scripts | |
| example_scripts = { | |
| "Select an example...": "", | |
| "Basic Matplotlib Plot": """import matplotlib.pyplot as plt | |
| import numpy as np | |
| # Create data | |
| x = np.linspace(0, 10, 100) | |
| y = np.sin(x) | |
| # Create plot | |
| plt.figure(figsize=(10, 6)) | |
| plt.plot(x, y, 'b-', label='sin(x)') | |
| plt.title('Sine Wave') | |
| plt.xlabel('x') | |
| plt.ylabel('sin(x)') | |
| plt.grid(True) | |
| plt.legend() | |
| """, | |
| "User Input Example": """# This example demonstrates how to handle user input | |
| name = input("Enter your name: ") | |
| age = int(input("Enter your age: ")) | |
| print(f"Hello, {name}! In 10 years, you'll be {age + 10} years old.") | |
| # Let's get some numbers and calculate the average | |
| num_count = int(input("How many numbers would you like to average? ")) | |
| total = 0 | |
| for i in range(num_count): | |
| num = float(input(f"Enter number {i+1}: ")) | |
| total += num | |
| average = total / num_count | |
| print(f"The average of your {num_count} numbers is: {average}") | |
| """, | |
| "Pandas DataFrame": """import pandas as pd | |
| import numpy as np | |
| # Create a sample dataframe | |
| data = { | |
| 'Name': ['Alice', 'Bob', 'Charlie', 'David', 'Emma'], | |
| 'Age': [25, 30, 35, 40, 45], | |
| 'Salary': [50000, 60000, 70000, 80000, 90000], | |
| 'Department': ['HR', 'IT', 'Finance', 'Marketing', 'Engineering'] | |
| } | |
| df = pd.DataFrame(data) | |
| # Display the dataframe | |
| print("Sample DataFrame:") | |
| print(df) | |
| # Basic statistics | |
| print("\\nSummary Statistics:") | |
| print(df.describe()) | |
| # Filtering | |
| print("\\nEmployees older than 30:") | |
| print(df[df['Age'] > 30]) | |
| """, | |
| "Seaborn Visualization": """import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| import numpy as np | |
| import pandas as pd | |
| # Set the style | |
| sns.set_style("whitegrid") | |
| # Create sample data | |
| np.random.seed(42) | |
| data = np.random.randn(100, 3) | |
| df = pd.DataFrame(data, columns=['A', 'B', 'C']) | |
| df['category'] = pd.Categorical(['Group 1'] * 50 + ['Group 2'] * 50) | |
| # Create a paired plot | |
| sns.pairplot(df, hue='category', palette='viridis') | |
| # Create another plot | |
| plt.figure(figsize=(10, 6)) | |
| sns.violinplot(x='category', y='A', data=df, palette='magma') | |
| plt.title('Distribution of A by Category') | |
| """ | |
| } | |
| # Select example script | |
| selected_example = st.selectbox("Select an example script:", options=list(example_scripts.keys())) | |
| # Python code editor | |
| if selected_example != "Select an example..." and selected_example in example_scripts: | |
| python_code = example_scripts[selected_example] | |
| else: | |
| python_code = st.session_state.python_script | |
| if ACE_EDITOR_AVAILABLE: | |
| python_code = st_ace( | |
| value=python_code, | |
| language="python", | |
| theme="dracula", | |
| min_lines=20, | |
| max_lines=45, | |
| font_size=14, | |
| tab_size=4, | |
| wrap=True, | |
| show_gutter=True, | |
| show_print_margin=False, | |
| auto_update=True, | |
| keybinding="vscode", | |
| key=f"python_editor_{st.session_state.editor_key}" | |
| ) | |
| else: | |
| python_code = st.text_area( | |
| "Python Code", | |
| value=python_code, | |
| height=500, | |
| key=f"python_textarea_{st.session_state.editor_key}", | |
| placeholder="# Enter your Python script here...\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Your code here" | |
| ) | |
| # Store script in session state (without clearing existing code) | |
| st.session_state.python_script = python_code | |
| # Check for input() calls | |
| input_calls = detect_input_calls(python_code) | |
| user_inputs = [] | |
| if input_calls: | |
| st.markdown("### Input Values") | |
| st.info(f"This script contains {len(input_calls)} input() calls. Please provide values below:") | |
| for i, input_call in enumerate(input_calls): | |
| user_input = st.text_input( | |
| f"{input_call['prompt']} (Line {input_call['line']})", | |
| key=f"input_{i}" | |
| ) | |
| user_inputs.append(user_input) | |
| # Options and execution | |
| col1, col2 = st.columns([2, 1]) | |
| with col1: | |
| timeout_seconds = st.slider("Execution Timeout (seconds)", 5, 3600, 30) | |
| with col2: | |
| run_btn = st.button("▶️ Run Script", use_container_width=True) | |
| if run_btn: | |
| with st.spinner("Executing Python script..."): | |
| # Use the enhanced function | |
| result = run_python_script_enhanced( | |
| python_code, | |
| inputs=user_inputs, | |
| timeout=timeout_seconds, | |
| enable_debug=enable_debugging, | |
| enable_profile=enable_profiling, | |
| additional_libs=py_libs, | |
| project_files=st.session_state.py_project_files if "py_project_files" in st.session_state else None, | |
| realtime_viz=real_time_viz | |
| ) | |
| st.session_state.python_result = result | |
| # Display results | |
| if st.session_state.python_result: | |
| display_python_script_results_enhanced(st.session_state.python_result) | |
| # Provide option to save the script | |
| if st.button("📄 Save This Script", key="save_script_btn"): | |
| # Generate a unique filename | |
| timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
| script_filename = f"script_{timestamp}.py" | |
| # Offer download button for the script | |
| st.download_button( | |
| label="⬇️ Download Script", | |
| data=python_code, | |
| file_name=script_filename, | |
| mime="text/plain" | |
| ) | |
| # Show advanced examples and tips | |
| with st.expander("Python Script Runner Tips"): | |
| st.markdown(""" | |
| ### Python Script Runner Tips | |
| **What can I run?** | |
| - Any Python code that doesn't require direct UI interaction | |
| - Libraries like Matplotlib, NumPy, Pandas, SciPy, etc. | |
| - Data processing and visualization code | |
| - Scripts that ask for user input (now supported!) | |
| **What can't I run?** | |
| - Streamlit, Gradio, Dash, or other web UIs | |
| - Long-running operations (timeout will occur) | |
| - Code that requires file access outside the temporary environment | |
| **Working with visualizations:** | |
| - All Matplotlib/Seaborn plots will be automatically captured | |
| - Pandas DataFrames are detected and displayed as tables | |
| - Use `print()` to show text output | |
| **Handling user input:** | |
| - The app detects input() calls and automatically creates text fields | |
| - Input values you provide will be passed to the script when it runs | |
| - Type conversion (like int(), float()) is preserved | |
| **Adding to animations:** | |
| - Charts and plots can be directly added to your Manim animations | |
| - Generated images will be properly scaled for your animation | |
| - Perfect for educational content combining data and animations | |
| """) | |
| # C/C++ RUNNER TAB | |
| with tabs[6]: # Assuming this is the 7th tab (index 6) | |
| st.markdown(""" | |
| <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;"> | |
| <div style="width: 48px; height: 48px; background: linear-gradient(135deg, #f59e0b, #ef4444); border-radius: 12px; display: flex; align-items: center; justify-content: center;"> | |
| <span style="font-size: 1.5rem;">🔧</span> | |
| </div> | |
| <div> | |
| <h2 style="margin: 0; font-size: 1.5rem; color: var(--text-primary);">C/C++ Runner</h2> | |
| <p style="margin: 0; font-size: 0.9rem; color: var(--text-secondary);">Write, compile, and run C/C++ code with advanced features</p> | |
| </div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Create a tabbed interface for different C++ features | |
| cpp_tabs = st.tabs(["Code Editor", "Project Files", "Libraries", "Build Settings", "Debugger"]) | |
| with cpp_tabs[0]: # Code Editor tab | |
| # Compiler options | |
| cpp_col1, cpp_col2, cpp_col3 = st.columns(3) | |
| with cpp_col1: | |
| compiler = st.selectbox( | |
| "Compiler", | |
| options=["g++", "clang++", "gcc", "msvc"], | |
| index=["g++", "clang++", "gcc", "msvc"].index(st.session_state.cpp_settings["compiler"]), | |
| key="cpp_compiler" | |
| ) | |
| st.session_state.cpp_settings["compiler"] = compiler | |
| with cpp_col2: | |
| std_version = st.selectbox( | |
| "Standard", | |
| options=["c++11", "c++14", "c++17", "c++20"], | |
| index=["c++11", "c++14", "c++17", "c++20"].index(st.session_state.cpp_settings["std"]), | |
| key="cpp_std" | |
| ) | |
| st.session_state.cpp_settings["std"] = std_version | |
| with cpp_col3: | |
| optimization = st.selectbox( | |
| "Optimization", | |
| options=["-O0", "-O1", "-O2", "-O3"], | |
| index=["-O0", "-O1", "-O2", "-O3"].index(st.session_state.cpp_settings["optimization"]), | |
| key="cpp_opt" | |
| ) | |
| st.session_state.cpp_settings["optimization"] = optimization | |
| # Example code templates | |
| cpp_examples = { | |
| "Select an example...": "", | |
| "Hello World": """#include <iostream> | |
| int main() { | |
| std::cout << "Hello, World!" << std::endl; | |
| return 0; | |
| }""", | |
| "Calculate Prime Numbers": """#include <iostream> | |
| #include <vector> | |
| #include <chrono> | |
| bool isPrime(int n) { | |
| if (n <= 1) return false; | |
| if (n <= 3) return true; | |
| if (n % 2 == 0 || n % 3 == 0) return false; | |
| for (int i = 5; i * i <= n; i += 6) { | |
| if (n % i == 0 || n % (i + 2) == 0) | |
| return false; | |
| } | |
| return true; | |
| } | |
| int main() { | |
| int limit = 10000; | |
| std::vector<int> primes; | |
| auto start = std::chrono::high_resolution_clock::now(); | |
| for (int i = 2; i <= limit; i++) { | |
| if (isPrime(i)) { | |
| primes.push_back(i); | |
| } | |
| } | |
| auto end = std::chrono::high_resolution_clock::now(); | |
| auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); | |
| std::cout << "Found " << primes.size() << " prime numbers up to " << limit << std::endl; | |
| std::cout << "First 10 primes: "; | |
| for (int i = 0; i < std::min(10, (int)primes.size()); i++) { | |
| std::cout << primes[i] << " "; | |
| } | |
| std::cout << std::endl; | |
| std::cout << "Computation time: " << duration.count() << " ms" << std::endl; | |
| return 0; | |
| }""", | |
| "Image Generation (PPM)": """#include <iostream> | |
| #include <fstream> | |
| #include <cmath> | |
| // Generate a simple gradient image in PPM format | |
| int main() { | |
| const int width = 800; | |
| const int height = 600; | |
| // Create a PPM file (P3 format - ASCII) | |
| std::ofstream image("output.ppm"); | |
| image << "P3\\n" << width << " " << height << "\\n255\\n"; | |
| for (int y = 0; y < height; y++) { | |
| for (int x = 0; x < width; x++) { | |
| // Create a gradient based on position | |
| int r = static_cast<int>(255.0 * x / width); | |
| int g = static_cast<int>(255.0 * y / height); | |
| int b = static_cast<int>(255.0 * (x + y) / (width + height)); | |
| // Write RGB values | |
| image << r << " " << g << " " << b << "\\n"; | |
| } | |
| } | |
| image.close(); | |
| std::cout << "Generated gradient image: output.ppm" << std::endl; | |
| return 0; | |
| }""", | |
| "Data Processing with Vectors": """#include <iostream> | |
| #include <vector> | |
| #include <algorithm> | |
| #include <numeric> | |
| #include <random> | |
| #include <iomanip> | |
| int main() { | |
| const int data_size = 1000; | |
| // Generate random data | |
| std::vector<double> data(data_size); | |
| std::random_device rd; | |
| std::mt19937 gen(rd()); | |
| std::normal_distribution<double> dist(100.0, 15.0); | |
| std::cout << "Generating " << data_size << " random values..." << std::endl; | |
| for (auto& value : data) { | |
| value = dist(gen); | |
| } | |
| // Calculate statistics | |
| double sum = std::accumulate(data.begin(), data.end(), 0.0); | |
| double mean = sum / data.size(); | |
| std::vector<double> deviations(data_size); | |
| std::transform(data.begin(), data.end(), deviations.begin(), | |
| [mean](double x) { return x - mean; }); | |
| double sq_sum = std::inner_product(deviations.begin(), deviations.end(), | |
| deviations.begin(), 0.0); | |
| double stddev = std::sqrt(sq_sum / data.size()); | |
| // Sort data | |
| std::sort(data.begin(), data.end()); | |
| double median = data.size() % 2 == 0 ? | |
| (data[data.size()/2 - 1] + data[data.size()/2]) / 2 : | |
| data[data.size()/2]; | |
| // Output results | |
| std::cout << std::fixed << std::setprecision(2); | |
| std::cout << "Data analysis results:" << std::endl; | |
| std::cout << "Mean: " << mean << std::endl; | |
| std::cout << "Median: " << median << std::endl; | |
| std::cout << "StdDev: " << stddev << std::endl; | |
| std::cout << "Min: " << data.front() << std::endl; | |
| std::cout << "Max: " << data.back() << std::endl; | |
| return 0; | |
| }""", | |
| "Interactive User Input": """#include <iostream> | |
| #include <string> | |
| #include <vector> | |
| int main() { | |
| std::string name; | |
| int age; | |
| // Get user input | |
| std::cout << "Enter your name: "; | |
| std::getline(std::cin, name); | |
| std::cout << "Enter your age: "; | |
| std::cin >> age; | |
| std::cin.ignore(); // Clear the newline from the buffer | |
| std::cout << "Hello, " << name << "! "; | |
| std::cout << "In 10 years, you will be " << age + 10 << " years old." << std::endl; | |
| // Get multiple numbers | |
| int num_count; | |
| std::cout << "How many numbers would you like to enter? "; | |
| std::cin >> num_count; | |
| std::vector<double> numbers; | |
| double total = 0.0; | |
| for (int i = 0; i < num_count; i++) { | |
| double num; | |
| std::cout << "Enter number " << (i+1) << ": "; | |
| std::cin >> num; | |
| numbers.push_back(num); | |
| total += num; | |
| } | |
| if (!numbers.empty()) { | |
| double average = total / numbers.size(); | |
| std::cout << "The average of your numbers is: " << average << std::endl; | |
| } | |
| return 0; | |
| }""", | |
| "Eigen Matrix Operations": """#include <iostream> | |
| #include <Eigen/Dense> | |
| using Eigen::MatrixXd; | |
| using Eigen::VectorXd; | |
| int main() { | |
| // Create a 3x3 matrix | |
| MatrixXd A(3, 3); | |
| A << 1, 2, 3, | |
| 4, 5, 6, | |
| 7, 8, 9; | |
| // Create a 3D vector | |
| VectorXd b(3); | |
| b << 1, 2, 3; | |
| // Perform operations | |
| std::cout << "Matrix A:\\n" << A << std::endl; | |
| std::cout << "Vector b:\\n" << b << std::endl; | |
| std::cout << "A * b:\\n" << A * b << std::endl; | |
| std::cout << "A transpose:\\n" << A.transpose() << std::endl; | |
| // Solve a linear system Ax = b | |
| VectorXd x = A.colPivHouseholderQr().solve(b); | |
| std::cout << "Solution to Ax = b:\\n" << x << std::endl; | |
| // Compute eigenvalues and eigenvectors | |
| Eigen::EigenSolver<MatrixXd> solver(A); | |
| std::cout << "Eigenvalues:\\n" << solver.eigenvalues() << std::endl; | |
| std::cout << "Eigenvectors:\\n" << solver.eigenvectors() << std::endl; | |
| return 0; | |
| }""", | |
| "OpenCV Image Processing": """#include <iostream> | |
| #include <opencv2/opencv.hpp> | |
| int main() { | |
| // Load an image (this will create a blank image if no file is found) | |
| cv::Mat image = cv::Mat::zeros(500, 500, CV_8UC3); | |
| // Draw a circle | |
| cv::circle(image, cv::Point(250, 250), 100, cv::Scalar(0, 0, 255), 5); | |
| // Draw a rectangle | |
| cv::rectangle(image, cv::Point(150, 150), cv::Point(350, 350), cv::Scalar(0, 255, 0), 3); | |
| // Add text | |
| cv::putText(image, "OpenCV Example", cv::Point(100, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2); | |
| // Save the image | |
| cv::imwrite("opencv_output.png", image); | |
| std::cout << "Image created and saved as 'opencv_output.png'" << std::endl; | |
| return 0; | |
| }""" | |
| } | |
| # Example selection | |
| selected_cpp_example = st.selectbox("Example code:", options=list(cpp_examples.keys())) | |
| # Set initial code from example or session state | |
| if selected_cpp_example != "Select an example..." and cpp_examples[selected_cpp_example] != "": | |
| initial_code = cpp_examples[selected_cpp_example] | |
| else: | |
| if "cpp_current_file" in st.session_state and st.session_state.cpp_current_file in st.session_state.cpp_project_files: | |
| initial_code = st.session_state.cpp_project_files[st.session_state.cpp_current_file] | |
| else: | |
| initial_code = st.session_state.cpp_code | |
| # Code editor for C++ | |
| if ACE_EDITOR_AVAILABLE: | |
| cpp_code = st_ace( | |
| value=initial_code, | |
| language="c_cpp", | |
| theme="dracula", | |
| min_lines=20, | |
| max_lines=45, | |
| font_size=14, | |
| tab_size=4, | |
| wrap=True, | |
| show_gutter=True, | |
| show_print_margin=False, | |
| auto_update=True, | |
| keybinding="vscode", | |
| key=f"cpp_editor_{st.session_state.editor_key}" | |
| ) | |
| else: | |
| cpp_code = st.text_area( | |
| "C/C++ Code", | |
| value=initial_code, | |
| height=500, | |
| key=f"cpp_textarea_{st.session_state.editor_key}", | |
| placeholder="#include <iostream>\nusing namespace std;\n\nint main() {\n // Your code here\n return 0;\n}" | |
| ) | |
| # Save the code to session state | |
| st.session_state.cpp_code = cpp_code | |
| # Update project files | |
| if "cpp_current_file" in st.session_state and st.session_state.cpp_current_file in st.session_state.cpp_project_files: | |
| st.session_state.cpp_project_files[st.session_state.cpp_current_file] = cpp_code | |
| # Check for standard input in the code | |
| has_cin = "std::cin" in cpp_code or "cin" in cpp_code | |
| # Input values section if needed | |
| cpp_inputs = [] | |
| if has_cin: | |
| with st.expander("Input Values"): | |
| st.info("This program uses standard input. Please provide input values below:") | |
| num_inputs = st.number_input("Number of input lines:", min_value=1, max_value=10, value=1) | |
| for i in range(int(num_inputs)): | |
| cpp_input = st.text_input(f"Input line {i+1}:", key=f"cpp_input_{i}") | |
| cpp_inputs.append(cpp_input) | |
| with cpp_tabs[1]: # Project Files tab | |
| st.markdown("### Project Files") | |
| st.markdown("Manage multiple source files for your C/C++ project") | |
| # File selector | |
| cpp_current_file = st.selectbox( | |
| "Current File", | |
| options=list(st.session_state.cpp_project_files.keys()), | |
| index=list(st.session_state.cpp_project_files.keys()).index(st.session_state.cpp_current_file) if "cpp_current_file" in st.session_state else 0, | |
| key="cpp_file_selector" | |
| ) | |
| # Update the current file in session state | |
| st.session_state.cpp_current_file = cpp_current_file | |
| # Create new file form | |
| new_file_col1, new_file_col2 = st.columns([3, 1]) | |
| with new_file_col1: | |
| new_cpp_filename = st.text_input("New File Name", placeholder="e.g., utils.h, helper.cpp", key="new_cpp_file") | |
| with new_file_col2: | |
| if st.button("Add File", key="add_cpp_file"): | |
| if new_cpp_filename and new_cpp_filename not in st.session_state.cpp_project_files: | |
| # Add file extension if missing | |
| if not new_cpp_filename.endswith((".cpp", ".h", ".hpp", ".c", ".cc")): | |
| new_cpp_filename += ".cpp" | |
| # Create a template based on file type | |
| if new_cpp_filename.endswith((".h", ".hpp")): | |
| template = f"""#ifndef {new_cpp_filename.split('.')[0].upper()}_H | |
| #define {new_cpp_filename.split('.')[0].upper()}_H | |
| // Your header content here | |
| #endif // {new_cpp_filename.split('.')[0].upper()}_H | |
| """ | |
| else: | |
| template = f"""#include <iostream> | |
| // Your implementation here | |
| """ | |
| st.session_state.cpp_project_files[new_cpp_filename] = template | |
| st.session_state.cpp_current_file = new_cpp_filename | |
| st.experimental_rerun() | |
| # File actions | |
| file_action_col1, file_action_col2 = st.columns(2) | |
| with file_action_col1: | |
| if st.button("Delete Current File", key="delete_cpp_file"): | |
| if cpp_current_file != "main.cpp" and cpp_current_file in st.session_state.cpp_project_files: | |
| del st.session_state.cpp_project_files[cpp_current_file] | |
| st.session_state.cpp_current_file = "main.cpp" | |
| st.experimental_rerun() | |
| else: | |
| st.error("Cannot delete main.cpp") | |
| with file_action_col2: | |
| if st.button("Download Project Files", key="download_cpp_project"): | |
| # Create a zip file with all project files | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".zip") as tmp: | |
| with zipfile.ZipFile(tmp.name, 'w') as zipf: | |
| for filename, content in st.session_state.cpp_project_files.items(): | |
| # Add file to zip | |
| zipf.writestr(filename, content) | |
| # Download the zip file | |
| with open(tmp.name, "rb") as f: | |
| zip_data = f.read() | |
| st.download_button( | |
| label="Download ZIP", | |
| data=zip_data, | |
| file_name="cpp_project.zip", | |
| mime="application/zip" | |
| ) | |
| # Project structure visualization | |
| st.markdown("### Project Structure") | |
| # Group files by type | |
| headers = [] | |
| sources = [] | |
| others = [] | |
| for filename in st.session_state.cpp_project_files: | |
| if filename.endswith((".h", ".hpp")): | |
| headers.append(filename) | |
| elif filename.endswith((".cpp", ".c", ".cc")): | |
| sources.append(filename) | |
| else: | |
| others.append(filename) | |
| # Display structure | |
| st.markdown("#### Header Files") | |
| if headers: | |
| for header in sorted(headers): | |
| st.markdown(f"- `{header}`") | |
| else: | |
| st.markdown("No header files") | |
| st.markdown("#### Source Files") | |
| if sources: | |
| for source in sorted(sources): | |
| st.markdown(f"- `{source}`") | |
| else: | |
| st.markdown("No source files") | |
| if others: | |
| st.markdown("#### Other Files") | |
| for other in sorted(others): | |
| st.markdown(f"- `{other}`") | |
| with cpp_tabs[2]: # Libraries tab | |
| st.markdown("### Library Manager") | |
| st.markdown("Configure libraries and dependencies for your C/C++ project") | |
| # Common library selection | |
| common_libs = st.multiselect( | |
| "Common Libraries", | |
| options=["Eigen", "Boost", "OpenCV", "FFTW", "SDL2", "SFML", "OpenGL", "stb_image", "nlohmann_json", "fmt"], | |
| default=st.session_state.cpp_settings.get("libraries", []), | |
| key="cpp_common_libs" | |
| ) | |
| # Update libraries in settings | |
| st.session_state.cpp_settings["libraries"] = common_libs | |
| # Include paths | |
| st.markdown("#### Include Paths") | |
| include_paths = st.text_area( | |
| "Include Directories (one per line)", | |
| value="\n".join(st.session_state.cpp_settings.get("include_paths", [])), | |
| height=100, | |
| key="cpp_include_paths" | |
| ) | |
| # Update include paths in settings | |
| st.session_state.cpp_settings["include_paths"] = [path for path in include_paths.split("\n") if path.strip()] | |
| # Library paths | |
| st.markdown("#### Library Paths") | |
| library_paths = st.text_area( | |
| "Library Directories (one per line)", | |
| value="\n".join(st.session_state.cpp_settings.get("library_paths", [])), | |
| height=100, | |
| key="cpp_library_paths" | |
| ) | |
| # Update library paths in settings | |
| st.session_state.cpp_settings["library_paths"] = [path for path in library_paths.split("\n") if path.strip()] | |
| # Additional libraries | |
| st.markdown("#### Additional Libraries") | |
| additional_libs = st.text_area( | |
| "Additional Libraries (one per line, without -l prefix)", | |
| value="\n".join(st.session_state.cpp_settings.get("additional_libs", [])), | |
| height=100, | |
| key="cpp_additional_libs" | |
| ) | |
| # Update additional libraries in settings | |
| st.session_state.cpp_settings["additional_libs"] = [lib for lib in additional_libs.split("\n") if lib.strip()] | |
| # Library detection | |
| if st.button("Detect Installed Libraries", key="detect_libs"): | |
| with st.spinner("Detecting libraries..."): | |
| # This is a placeholder - in a real implementation, you'd scan the system | |
| detected_libs = [] | |
| # Check for Eigen | |
| try: | |
| result = subprocess.run( | |
| ["find", "/usr/include", "-name", "Eigen"], | |
| capture_output=True, | |
| text=True, | |
| timeout=5 | |
| ) | |
| if "Eigen" in result.stdout: | |
| detected_libs.append("Eigen") | |
| except: | |
| pass | |
| # Check for Boost | |
| try: | |
| result = subprocess.run( | |
| ["find", "/usr/include", "-name", "boost"], | |
| capture_output=True, | |
| text=True, | |
| timeout=5 | |
| ) | |
| if "boost" in result.stdout: | |
| detected_libs.append("Boost") | |
| except: | |
| pass | |
| # Check for OpenCV | |
| try: | |
| result = subprocess.run( | |
| ["pkg-config", "--exists", "opencv4"], | |
| capture_output=True, | |
| timeout=5 | |
| ) | |
| if result.returncode == 0: | |
| detected_libs.append("OpenCV") | |
| except: | |
| pass | |
| # Display detected libraries | |
| if detected_libs: | |
| st.success(f"Detected libraries: {', '.join(detected_libs)}") | |
| # Add to selected libraries if not already present | |
| for lib in detected_libs: | |
| if lib not in st.session_state.cpp_settings["libraries"]: | |
| st.session_state.cpp_settings["libraries"].append(lib) | |
| else: | |
| st.warning("No common libraries detected") | |
| with cpp_tabs[3]: # Build Settings tab | |
| st.markdown("### Build Configuration") | |
| # Build type | |
| build_type = st.radio( | |
| "Build Type", | |
| options=["Debug", "Release", "RelWithDebInfo"], | |
| index=1, # Default to Release | |
| key="cpp_build_type" | |
| ) | |
| # Update build type in settings | |
| st.session_state.cpp_settings["build_type"] = build_type | |
| # Advanced compiler flags | |
| st.markdown("#### Advanced Compiler Flags") | |
| advanced_flags = st.text_area( | |
| "Additional Compiler Flags", | |
| value=st.session_state.cpp_settings.get("advanced_flags", ""), | |
| height=100, | |
| key="cpp_advanced_flags" | |
| ) | |
| # Update advanced flags in settings | |
| st.session_state.cpp_settings["advanced_flags"] = advanced_flags | |
| # Preprocessor definitions | |
| st.markdown("#### Preprocessor Definitions") | |
| definitions = st.text_area( | |
| "Preprocessor Definitions (one per line)", | |
| value="\n".join(st.session_state.cpp_settings.get("definitions", [])), | |
| height=100, | |
| placeholder="Example:\nDEBUG\nVERSION=1.0\nUSE_FEATURE_X", | |
| key="cpp_definitions" | |
| ) | |
| # Update definitions in settings | |
| st.session_state.cpp_settings["definitions"] = [d for d in definitions.split("\n") if d.strip()] | |
| # Generate CMakeLists.txt | |
| if st.button("Generate CMakeLists.txt", key="gen_cmake"): | |
| # Create CMakeLists.txt content | |
| cmake_content = f"""cmake_minimum_required(VERSION 3.10) | |
| project(ManimCppProject) | |
| set(CMAKE_CXX_STANDARD {st.session_state.cpp_settings["std"].replace("c++", "")}) | |
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | |
| set(CMAKE_CXX_EXTENSIONS OFF) | |
| # Build type | |
| set(CMAKE_BUILD_TYPE {build_type}) | |
| # Preprocessor definitions | |
| """ | |
| # Add definitions | |
| for definition in st.session_state.cpp_settings.get("definitions", []): | |
| if "=" in definition: | |
| name, value = definition.split("=", 1) | |
| cmake_content += f'add_definitions(-D{name}="{value}")\n' | |
| else: | |
| cmake_content += f"add_definitions(-D{definition})\n" | |
| # Add include paths | |
| if st.session_state.cpp_settings.get("include_paths", []): | |
| cmake_content += "\n# Include directories\n" | |
| for path in st.session_state.cpp_settings["include_paths"]: | |
| cmake_content += f"include_directories({path})\n" | |
| # Add library paths | |
| if st.session_state.cpp_settings.get("library_paths", []): | |
| cmake_content += "\n# Library directories\n" | |
| for path in st.session_state.cpp_settings["library_paths"]: | |
| cmake_content += f"link_directories({path})\n" | |
| # Add common libraries | |
| if "Eigen" in st.session_state.cpp_settings.get("libraries", []): | |
| cmake_content += "\n# Eigen\n" | |
| cmake_content += "find_package(Eigen3 REQUIRED)\n" | |
| cmake_content += "include_directories(${EIGEN3_INCLUDE_DIR})\n" | |
| if "OpenCV" in st.session_state.cpp_settings.get("libraries", []): | |
| cmake_content += "\n# OpenCV\n" | |
| cmake_content += "find_package(OpenCV REQUIRED)\n" | |
| cmake_content += "include_directories(${OpenCV_INCLUDE_DIRS})\n" | |
| if "Boost" in st.session_state.cpp_settings.get("libraries", []): | |
| cmake_content += "\n# Boost\n" | |
| cmake_content += "find_package(Boost REQUIRED)\n" | |
| cmake_content += "include_directories(${Boost_INCLUDE_DIRS})\n" | |
| # Add source files | |
| cmake_content += "\n# Source files\n" | |
| source_files = [f for f in st.session_state.cpp_project_files.keys() if f.endswith((".cpp", ".c", ".cc"))] | |
| cmake_content += "add_executable(main\n" | |
| for src in source_files: | |
| cmake_content += f" {src}\n" | |
| cmake_content += ")\n" | |
| # Add libraries to link | |
| cmake_content += "\n# Link libraries\n" | |
| cmake_content += "target_link_libraries(main\n" | |
| if "OpenCV" in st.session_state.cpp_settings.get("libraries", []): | |
| cmake_content += " ${OpenCV_LIBS}\n" | |
| if "Boost" in st.session_state.cpp_settings.get("libraries", []): | |
| cmake_content += " ${Boost_LIBRARIES}\n" | |
| # Additional libraries | |
| for lib in st.session_state.cpp_settings.get("additional_libs", []): | |
| cmake_content += f" {lib}\n" | |
| cmake_content += ")\n" | |
| # Save CMakeLists.txt to project files | |
| st.session_state.cpp_project_files["CMakeLists.txt"] = cmake_content | |
| # Show the generated file | |
| st.success("CMakeLists.txt generated!") | |
| st.code(cmake_content, language="cmake") | |
| with cpp_tabs[4]: # Debugger tab | |
| st.markdown("### C++ Debugger") | |
| st.markdown("Debug your C++ code with breakpoints and variable inspection") | |
| # Enable debugging | |
| enable_cpp_debug = st.checkbox("Enable Debugging", value=False, key="cpp_debug_enable") | |
| if enable_cpp_debug: | |
| # Breakpoints | |
| st.markdown("#### Breakpoints") | |
| st.markdown("Enter line numbers for breakpoints (one per line)") | |
| breakpoints = st.text_area( | |
| "Breakpoints", | |
| placeholder="Example:\n10\n15\n20", | |
| height=100, | |
| key="cpp_breakpoints" | |
| ) | |
| breakpoint_lines = [] | |
| for line in breakpoints.split("\n"): | |
| try: | |
| line_num = int(line.strip()) | |
| if line_num > 0: | |
| breakpoint_lines.append(line_num) | |
| except: | |
| pass | |
| # Watch variables | |
| st.markdown("#### Watch Variables") | |
| st.markdown("Enter variable names to watch (one per line)") | |
| watch_vars = st.text_area( | |
| "Watch Variables", | |
| placeholder="Example:\ni\nsum\nresult", | |
| height=100, | |
| key="cpp_watch_vars" | |
| ) | |
| watch_variables = [var.strip() for var in watch_vars.split("\n") if var.strip()] | |
| # Compilation and execution options | |
| st.markdown("### Run Configuration") | |
| run_options_col1, run_options_col2 = st.columns(2) | |
| with run_options_col1: | |
| cpp_timeout = st.slider("Execution Timeout (seconds)", 1, 60, 10) | |
| with run_options_col2: | |
| compile_btn = st.button("🛠️ Compile and Run", use_container_width=True) | |
| # Compile and run the C++ code | |
| if compile_btn: | |
| with st.spinner("Compiling C++ code..."): | |
| cpp_code_to_compile = st.session_state.cpp_code | |
| if "cpp_project_files" in st.session_state and st.session_state.cpp_project_files: | |
| # Use project files | |
| executable_path, compile_error, temp_dir = compile_cpp_code_enhanced( | |
| cpp_code_to_compile, | |
| st.session_state.cpp_settings, | |
| project_files=st.session_state.cpp_project_files, | |
| enable_debug=enable_cpp_debug if "enable_cpp_debug" in locals() else False, | |
| breakpoints=breakpoint_lines if "breakpoint_lines" in locals() else None, | |
| watch_vars=watch_variables if "watch_variables" in locals() else None | |
| ) | |
| else: | |
| # Use single file | |
| executable_path, compile_error, temp_dir = compile_cpp_code_enhanced( | |
| cpp_code_to_compile, | |
| st.session_state.cpp_settings, | |
| enable_debug=enable_cpp_debug if "enable_cpp_debug" in locals() else False, | |
| breakpoints=breakpoint_lines if "breakpoint_lines" in locals() else None, | |
| watch_vars=watch_variables if "watch_variables" in locals() else None | |
| ) | |
| if compile_error: | |
| st.error("Compilation Error:") | |
| st.code(compile_error, language="bash") | |
| else: | |
| st.success("Compilation successful!") | |
| with st.spinner("Running program..."): | |
| result = run_cpp_executable_enhanced( | |
| executable_path, | |
| temp_dir, | |
| inputs=cpp_inputs if "cpp_inputs" in locals() else None, | |
| timeout=cpp_timeout, | |
| enable_debug=enable_cpp_debug if "enable_cpp_debug" in locals() else False, | |
| breakpoints=breakpoint_lines if "breakpoint_lines" in locals() else None, | |
| watch_vars=watch_variables if "watch_variables" in locals() else None | |
| ) | |
| st.session_state.cpp_result = result | |
| # Display results | |
| if "cpp_result" in st.session_state and st.session_state.cpp_result: | |
| result = st.session_state.cpp_result | |
| st.markdown("### Results") | |
| # Execution information | |
| info_cols = st.columns(3) | |
| with info_cols[0]: | |
| st.info(f"Execution Time: {result['execution_time']:.3f} seconds") | |
| with info_cols[1]: | |
| if result.get("memory_usage"): | |
| st.info(f"Memory Usage: {result['memory_usage']:.2f} MB") | |
| with info_cols[2]: | |
| if result["exception"]: | |
| st.error(f"Exception: {result['exception']}") | |
| # Show debug output if available | |
| if result.get("debug_output"): | |
| with st.expander("Debug Output", expanded=True): | |
| st.code(result["debug_output"], language="bash") | |
| # Result tabs | |
| result_tabs = st.tabs(["Output", "Images", "Manim Integration"]) | |
| with result_tabs[0]: # Output tab | |
| # Show stdout if any | |
| if result["stdout"]: | |
| st.markdown("#### Standard Output") | |
| st.code(result["stdout"], language="bash") | |
| # Show stderr if any | |
| if result["stderr"]: | |
| st.markdown("#### Standard Error") | |
| st.code(result["stderr"], language="bash") | |
| with result_tabs[1]: # Images tab | |
| # Show images if any | |
| if result["images"]: | |
| st.markdown("#### Generated Images") | |
| img_cols = st.columns(min(3, len(result["images"]))) | |
| for i, img in enumerate(result["images"]): | |
| with img_cols[i % len(img_cols)]: | |
| st.image(img["data"], caption=img["name"]) | |
| else: | |
| st.info("No images were generated by the program.") | |
| with result_tabs[2]: # Manim Integration tab | |
| st.markdown("#### Integrate C++ Results with Manim") | |
| # Create options for integration | |
| integration_type = st.radio( | |
| "Integration Type", | |
| options=["Data Visualization", "Image Import", "Animation Sequence"], | |
| key="cpp_integration_type" | |
| ) | |
| if integration_type == "Data Visualization": | |
| # Extract numerical data from stdout if possible | |
| lines = result["stdout"].strip().split("\n") | |
| data_options = [] | |
| for i, line in enumerate(lines): | |
| # Check if line contains numbers | |
| numbers = [] | |
| try: | |
| # Try to extract numbers from the line | |
| numbers = [float(x) for x in line.split() if x.replace(".", "").isdigit()] | |
| if numbers: | |
| data_options.append(f"Line {i+1}: {line[:30]}{'...' if len(line) > 30 else ''}") | |
| except: | |
| pass | |
| if data_options: | |
| selected_data_line = st.selectbox( | |
| "Select Data to Visualize", | |
| options=["Select a line..."] + data_options, | |
| key="cpp_data_line" | |
| ) | |
| if selected_data_line != "Select a line...": | |
| line_idx = int(selected_data_line.split(":")[0].replace("Line ", "")) - 1 | |
| line = lines[line_idx] | |
| # Extract numbers | |
| try: | |
| numbers = [float(x) for x in line.split() if x.replace(".", "").isdigit()] | |
| # Preview the data | |
| st.markdown(f"**Extracted Data:** {numbers}") | |
| # Create visualization code | |
| if st.button("Create Manim Visualization", key="cpp_create_viz"): | |
| viz_code = f""" | |
| # Visualize data from C++ output | |
| values = {numbers} | |
| axes = Axes( | |
| x_range=[0, {len(numbers)}, 1], | |
| y_range=[{min(numbers) if numbers else 0}, {max(numbers) if numbers else 10}, {(max(numbers)-min(numbers))/10 if numbers and max(numbers) > min(numbers) else 1}], | |
| axis_config={{"color": BLUE}} | |
| ) | |
| points = [axes.coords_to_point(i, v) for i, v in enumerate(values)] | |
| dots = VGroup(*[Dot(point, color=RED) for point in points]) | |
| graph = VMobject(color=YELLOW) | |
| graph.set_points_as_corners(points) | |
| self.play(Create(axes)) | |
| self.play(Create(dots), run_time=2) | |
| self.play(Create(graph), run_time=2) | |
| self.wait(1) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + viz_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class CppDataVisualizationScene(Scene): | |
| def construct(self): | |
| {viz_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success("Added C++ data visualization to your Manim code!") | |
| # Set pending tab switch to editor tab | |
| st.session_state.pending_tab_switch = 0 | |
| st.rerun() | |
| except Exception as e: | |
| st.error(f"Error extracting numbers: {str(e)}") | |
| else: | |
| st.warning("No numeric data detected in the output.") | |
| elif integration_type == "Image Import": | |
| # Handle image import | |
| if result["images"]: | |
| st.markdown("#### Select Images to Import") | |
| for i, img in enumerate(result["images"]): | |
| st.markdown(f"**{img['name']}**") | |
| st.image(img["data"], width=300) | |
| if st.button(f"Use in Manim", key=f"use_cpp_img_{i}"): | |
| # Save image to a temporary file | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{img['name']}") as tmp: | |
| tmp.write(img["data"]) | |
| img_path = tmp.name | |
| # Generate Manim code | |
| image_code = f""" | |
| # Load and display image generated from C++ | |
| cpp_image = ImageMobject(r"{img_path}") | |
| cpp_image.scale(2) # Adjust size as needed | |
| self.play(FadeIn(cpp_image)) | |
| self.wait(1) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + image_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class CppImageScene(Scene): | |
| def construct(self): | |
| {image_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success(f"Added C++ generated image to your Manim code!") | |
| st.session_state.pending_tab_switch = 0 # Switch to editor tab | |
| st.rerun() | |
| else: | |
| st.warning("No images were generated by the C++ program.") | |
| elif integration_type == "Animation Sequence": | |
| st.markdown("#### Create Animation Sequence") | |
| st.info("This will create a Manim animation that visualizes the execution of your C++ program.") | |
| # Animation type options | |
| animation_style = st.selectbox( | |
| "Animation Style", | |
| options=["Algorithm Visualization", "Data Flow", "Memory Model"], | |
| key="cpp_anim_style" | |
| ) | |
| if st.button("Generate Animation Sequence", key="cpp_gen_anim_seq"): | |
| # Create different animations based on selected style | |
| if animation_style == "Algorithm Visualization": | |
| # Example code for algorithm visualization | |
| algo_code = f""" | |
| # C++ Algorithm Visualization | |
| title = Text("C++ Algorithm Visualization") | |
| self.play(Write(title)) | |
| self.play(title.animate.to_edge(UP)) | |
| self.wait(0.5) | |
| # Create an array representation | |
| values = [5, 2, 8, 1, 9, 3, 7, 4, 6] # Example values | |
| squares = VGroup(*[Square(side_length=0.7, fill_opacity=0.8, fill_color=BLUE) for _ in values]) | |
| squares.arrange(RIGHT, buff=0.1) | |
| labels = VGroup(*[Text(str(v), font_size=24) for v in values]) | |
| for label, square in zip(labels, squares): | |
| label.move_to(square.get_center()) | |
| array = VGroup(squares, labels) | |
| array_label = Text("Array", font_size=20).next_to(array, UP) | |
| self.play(FadeIn(array), Write(array_label)) | |
| self.wait(1) | |
| # Simulate sorting algorithm | |
| for i in range(len(values)-1): | |
| # Highlight current element | |
| self.play(squares[i].animate.set_fill(RED)) | |
| for j in range(i+1, len(values)): | |
| # Highlight comparison element | |
| self.play(squares[j].animate.set_fill(YELLOW)) | |
| # Simulate comparison | |
| if values[i] > values[j]: | |
| # Swap animation | |
| self.play( | |
| labels[i].animate.move_to(squares[j].get_center()), | |
| labels[j].animate.move_to(squares[i].get_center()) | |
| ) | |
| # Update values and labels | |
| labels[i], labels[j] = labels[j], labels[i] | |
| values[i], values[j] = values[j], values[i] | |
| # Reset comparison element | |
| self.play(squares[j].animate.set_fill(BLUE)) | |
| # Mark current element as processed | |
| self.play(squares[i].animate.set_fill(GREEN)) | |
| # Mark the last element as processed | |
| self.play(squares[-1].animate.set_fill(GREEN)) | |
| # Show sorted array | |
| sorted_label = Text("Sorted Array", font_size=20).next_to(array, DOWN) | |
| self.play(Write(sorted_label)) | |
| self.wait(2) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + algo_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class CppAlgorithmScene(Scene): | |
| def construct(self): | |
| {algo_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success("Added C++ algorithm visualization to your Manim code!") | |
| st.session_state.pending_tab_switch = 0 # Switch to editor tab | |
| st.rerun() | |
| elif animation_style == "Data Flow": | |
| # Example code for data flow visualization | |
| data_flow_code = f""" | |
| # C++ Data Flow Visualization | |
| title = Text("C++ Data Flow") | |
| self.play(Write(title)) | |
| self.play(title.animate.to_edge(UP)) | |
| self.wait(0.5) | |
| # Create nodes for data flow | |
| input_node = Circle(radius=0.5, fill_opacity=0.8, fill_color=BLUE) | |
| process_node = Square(side_length=1, fill_opacity=0.8, fill_color=GREEN) | |
| output_node = Circle(radius=0.5, fill_opacity=0.8, fill_color=RED) | |
| # Position nodes | |
| input_node.move_to(LEFT*4) | |
| process_node.move_to(ORIGIN) | |
| output_node.move_to(RIGHT*4) | |
| # Add labels | |
| input_label = Text("Input", font_size=20).next_to(input_node, DOWN) | |
| process_label = Text("Process", font_size=20).next_to(process_node, DOWN) | |
| output_label = Text("Output", font_size=20).next_to(output_node, DOWN) | |
| # Create arrows | |
| arrow1 = Arrow(input_node.get_right(), process_node.get_left(), buff=0.2) | |
| arrow2 = Arrow(process_node.get_right(), output_node.get_left(), buff=0.2) | |
| # Display nodes and arrows | |
| self.play(FadeIn(input_node), Write(input_label)) | |
| self.wait(0.5) | |
| self.play(FadeIn(process_node), Write(process_label)) | |
| self.wait(0.5) | |
| self.play(FadeIn(output_node), Write(output_label)) | |
| self.wait(0.5) | |
| self.play(Create(arrow1), Create(arrow2)) | |
| self.wait(1) | |
| # Simulate data flow | |
| data = Text("Data", font_size=16).move_to(input_node.get_center()) | |
| self.play(FadeIn(data)) | |
| self.wait(0.5) | |
| # Move data along the flow | |
| self.play(data.animate.move_to(arrow1.get_center())) | |
| self.wait(0.5) | |
| self.play(data.animate.move_to(process_node.get_center())) | |
| self.wait(0.5) | |
| transformed_data = Text("Processed", font_size=16, color=YELLOW) | |
| transformed_data.move_to(process_node.get_center()) | |
| self.play(Transform(data, transformed_data)) | |
| self.wait(0.5) | |
| self.play(data.animate.move_to(arrow2.get_center())) | |
| self.wait(0.5) | |
| self.play(data.animate.move_to(output_node.get_center())) | |
| self.wait(1) | |
| result_text = Text("Final Result", font_size=24).to_edge(DOWN) | |
| self.play(Write(result_text)) | |
| self.wait(2) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + data_flow_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class CppDataFlowScene(Scene): | |
| def construct(self): | |
| {data_flow_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success("Added C++ data flow visualization to your Manim code!") | |
| st.session_state.pending_tab_switch = 0 # Switch to editor tab | |
| st.rerun() | |
| elif animation_style == "Memory Model": | |
| # Example code for memory model visualization | |
| memory_code = f""" | |
| # C++ Memory Model Visualization | |
| title = Text("C++ Memory Model") | |
| self.play(Write(title)) | |
| self.play(title.animate.to_edge(UP)) | |
| self.wait(0.5) | |
| # Create memory blocks | |
| stack_rect = Rectangle(height=3, width=4, fill_opacity=0.2, fill_color=BLUE) | |
| stack_rect.move_to(LEFT*3.5) | |
| stack_label = Text("Stack", font_size=20).next_to(stack_rect, UP) | |
| heap_rect = Rectangle(height=3, width=4, fill_opacity=0.2, fill_color=RED) | |
| heap_rect.move_to(RIGHT*3.5) | |
| heap_label = Text("Heap", font_size=20).next_to(heap_rect, UP) | |
| # Display memory areas | |
| self.play( | |
| Create(stack_rect), Write(stack_label), | |
| Create(heap_rect), Write(heap_label) | |
| ) | |
| self.wait(1) | |
| # Create variables on the stack | |
| int_var = Rectangle(height=0.5, width=1.5, fill_opacity=0.8, fill_color=BLUE_C) | |
| int_var.move_to(stack_rect.get_center() + UP*1) | |
| int_label = Text("int x = 5", font_size=16).next_to(int_var, RIGHT) | |
| pointer_var = Rectangle(height=0.5, width=1.5, fill_opacity=0.8, fill_color=BLUE_D) | |
| pointer_var.move_to(stack_rect.get_center()) | |
| pointer_label = Text("int* ptr", font_size=16).next_to(pointer_var, RIGHT) | |
| # Display stack variables | |
| self.play(FadeIn(int_var), Write(int_label)) | |
| self.wait(0.5) | |
| self.play(FadeIn(pointer_var), Write(pointer_label)) | |
| self.wait(1) | |
| # Create heap allocation | |
| heap_alloc = Rectangle(height=0.8, width=2, fill_opacity=0.8, fill_color=RED_C) | |
| heap_alloc.move_to(heap_rect.get_center() + UP*0.5) | |
| heap_label = Text("new int[4]", font_size=16).next_to(heap_alloc, LEFT) | |
| # Display heap allocation | |
| self.play(FadeIn(heap_alloc), Write(heap_label)) | |
| self.wait(1) | |
| # Create arrow from pointer to heap | |
| arrow = Arrow(pointer_var.get_right(), heap_alloc.get_left(), buff=0.2, color=YELLOW) | |
| self.play(Create(arrow)) | |
| self.wait(0.5) | |
| # Simulate pointer assignment | |
| assign_text = Text("ptr = new int[4]", font_size=24).to_edge(DOWN) | |
| self.play(Write(assign_text)) | |
| self.wait(1) | |
| # Simulate memory deallocation | |
| delete_text = Text("delete[] ptr", font_size=24).to_edge(DOWN) | |
| self.play(Transform(assign_text, delete_text)) | |
| self.play(FadeOut(arrow), FadeOut(heap_alloc), FadeOut(heap_label)) | |
| self.wait(1) | |
| # Simulate end of scope | |
| end_scope = Text("End of scope", font_size=24).to_edge(DOWN) | |
| self.play(Transform(assign_text, end_scope)) | |
| self.play(FadeOut(int_var), FadeOut(int_label), FadeOut(pointer_var), FadeOut(pointer_label)) | |
| self.wait(2) | |
| """ | |
| if st.session_state.code: | |
| st.session_state.code += "\n" + memory_code | |
| else: | |
| st.session_state.code = f"""from manim import * | |
| class CppMemoryModelScene(Scene): | |
| def construct(self): | |
| {memory_code} | |
| """ | |
| st.session_state.temp_code = st.session_state.code | |
| st.success("Added C++ memory model visualization to your Manim code!") | |
| st.session_state.pending_tab_switch = 0 # Switch to editor tab | |
| st.rerun() | |
| # C++ Information and tips | |
| with st.expander("C/C++ Runner Information"): | |
| st.markdown(""" | |
| ### C/C++ Runner Tips | |
| **Compilation Options:** | |
| - Choose the appropriate compiler based on your platform | |
| - Select the C++ standard version for your code | |
| - Optimization levels affect performance and debugging | |
| **Library Support:** | |
| - Common libraries like Eigen, OpenCV, and Boost are supported | |
| - Add custom include paths and library paths as needed | |
| - Use the library detection feature to find installed libraries | |
| **Input/Output:** | |
| - Standard input/output (cin/cout) is fully supported | |
| - File I/O works within the execution directory | |
| - For interactive programs, provide input values in advance | |
| **Debugging:** | |
| - Set breakpoints at specific line numbers | |
| - Watch variables to track their values | |
| - Debug with GDB for detailed analysis | |
| **Project Management:** | |
| - Create multi-file projects with headers and source files | |
| - Generate CMakeLists.txt for complex projects | |
| - Download project files as a ZIP archive | |
| **Images and Visualization:** | |
| - Generate images in PPM, PNG, JPG formats | |
| - Use OpenCV for more advanced image processing | |
| - All generated images can be used in Manim animations | |
| **Manim Integration:** | |
| - Create algorithm visualizations from C++ code | |
| - Import C++ generated images into Manim scenes | |
| - Visualize data structures and memory models | |
| **Performance:** | |
| - Use release mode for best performance | |
| - Profile your code to identify bottlenecks | |
| - C++ is ideal for computationally intensive tasks | |
| """) | |
| # Help section | |
| with st.sidebar.expander("ℹ️ Help & Info"): | |
| st.markdown(""" | |
| ### About Manim Animation Studio | |
| This app allows you to create mathematical animations using Manim, | |
| an animation engine for explanatory math videos. | |
| ### Example Code | |
| ```python | |
| from manim import * | |
| class SimpleExample(Scene): | |
| def construct(self): | |
| circle = Circle(color=BLUE) | |
| self.play(Create(circle)) | |
| square = Square(color=RED).next_to(circle, RIGHT) | |
| self.play(Create(square)) | |
| text = Text("Manim Animation").next_to(VGroup(circle, square), DOWN) | |
| self.play(Write(text)) | |
| self.wait(2) | |
| ``` | |
| """) | |
| # Handle tab switching with session state to prevent refresh loop | |
| if st.session_state.pending_tab_switch is not None: | |
| st.session_state.active_tab = st.session_state.pending_tab_switch | |
| st.session_state.pending_tab_switch = None | |
| # Set tabs active state | |
| for i, tab in enumerate(tabs): | |
| if i == st.session_state.active_tab: | |
| tab.active = True | |
| # Mark first load as complete to prevent unnecessary refreshes | |
| if not st.session_state.first_load_complete: | |
| st.session_state.first_load_complete = True | |
| if __name__ == "__main__": | |
| main() |