Spaces:
Running
Running
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 | |
st.markdown(""" | |
<style> | |
.main-header { | |
font-size: 2.5rem; | |
font-weight: 700; | |
background: linear-gradient(90deg, #4F46E5, #818CF8); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
margin-bottom: 1rem; | |
text-align: center; | |
} | |
/* Improved Cards */ | |
.card { | |
background-color: #ffffff; | |
border-radius: 12px; | |
padding: 1.8rem; | |
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.08); | |
margin-bottom: 1.8rem; | |
border-left: 5px solid #4F46E5; | |
transition: all 0.3s ease; | |
} | |
.card:hover { | |
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12); | |
transform: translateY(-2px); | |
} | |
/* Tab styling */ | |
.stTabs [data-baseweb="tab-list"] { | |
gap: 2px; | |
} | |
.stTabs [data-baseweb="tab"] { | |
height: 45px; | |
white-space: pre-wrap; | |
border-radius: 4px 4px 0 0; | |
font-weight: 500; | |
} | |
.stTabs [aria-selected="true"] { | |
background-color: #f0f4fd; | |
border-bottom: 2px solid #4F46E5; | |
} | |
/* Buttons */ | |
.stButton button { | |
border-radius: 6px; | |
font-weight: 500; | |
transition: all 0.2s ease; | |
} | |
.stButton button:hover { | |
transform: translateY(-1px); | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
} | |
/* Model selection */ | |
.model-group { | |
margin-bottom: 1.5rem; | |
padding: 15px; | |
border-radius: 8px; | |
background-color: #f8f9fa; | |
} | |
.model-card { | |
background-color: #f8f9fa; | |
border-radius: 10px; | |
padding: 15px; | |
margin-bottom: 10px; | |
border-left: 4px solid #4F46E5; | |
transition: all 0.3s ease; | |
} | |
.model-card:hover { | |
box-shadow: 0 4px 8px rgba(0,0,0,0.1); | |
transform: translateY(-2px); | |
} | |
.model-category { | |
font-size: 1.2rem; | |
font-weight: 600; | |
padding: 10px 5px; | |
margin-top: 15px; | |
border-bottom: 2px solid #e9ecef; | |
color: #333; | |
} | |
.model-details { | |
font-size: 0.8rem; | |
color: #666; | |
margin-top: 5px; | |
} | |
.selected-model { | |
background-color: #e8f4fe; | |
border-left: 4px solid #0d6efd; | |
} | |
.preview-container { | |
border: 1px solid #e0e0e0; | |
border-radius: 10px; | |
padding: 1rem; | |
margin-bottom: 1rem; | |
min-height: 200px; | |
} | |
.small-text { | |
font-size: 0.8rem; | |
color: #6c757d; | |
} | |
.asset-card { | |
background-color: #f0f2f5; | |
border-radius: 8px; | |
padding: 1rem; | |
margin-bottom: 1rem; | |
border-left: 4px solid #4F46E5; | |
} | |
.timeline-container { | |
background-color: #f8f9fa; | |
border-radius: 10px; | |
padding: 1.5rem; | |
margin-bottom: 1.5rem; | |
} | |
.keyframe { | |
width: 12px; | |
height: 12px; | |
border-radius: 50%; | |
background-color: #4F46E5; | |
position: absolute; | |
transform: translate(-50%, -50%); | |
cursor: pointer; | |
} | |
.educational-export-container { | |
background-color: #f0f7ff; | |
border-radius: 10px; | |
padding: 1.5rem; | |
margin-bottom: 1.5rem; | |
border: 1px solid #c2e0ff; | |
} | |
.code-output { | |
background-color: #f8f9fa; | |
border-radius: 8px; | |
padding: 1rem; | |
margin-top: 1rem; | |
border-left: 4px solid #10b981; | |
max-height: 400px; | |
overflow-y: auto; | |
} | |
.error-output { | |
background-color: #fef2f2; | |
border-radius: 8px; | |
padding: 1rem; | |
margin-top: 1rem; | |
border-left: 4px solid #ef4444; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Header | |
st.markdown(""" | |
<div class="main-header"> | |
🎬 Manim Animation Studio | |
</div> | |
<p style="text-align: center; margin-bottom: 2rem;">Create mathematical animations with Manim</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: | |
# Rendering settings section | |
st.markdown("## ⚙️ Rendering Settings") | |
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" | |
) | |
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" | |
) | |
# Convert display name to actual format value | |
format_type = EXPORT_FORMATS[format_type_display] | |
# Add FPS control | |
fps = st.selectbox( | |
"🎞️ FPS", | |
options=FPS_OPTIONS, | |
index=FPS_OPTIONS.index(st.session_state.settings["fps"]) if st.session_state.settings["fps"] in FPS_OPTIONS else 2, # Default to 30 FPS (index 2) | |
key="fps_select" | |
) | |
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" | |
) | |
# Apply the settings without requiring a button | |
st.session_state.settings = { | |
"quality": quality, | |
"format_type": format_type, | |
"animation_speed": animation_speed, | |
"fps": fps | |
} | |
# Custom libraries section | |
st.markdown("## 📚 Custom Libraries") | |
st.markdown("Enter additional Python packages needed for your animations (comma-separated):") | |
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("### 📝 Animation Editor") | |
# Toggle between upload and type | |
editor_mode = st.radio( | |
"Choose how to input your code:", | |
["Type Code", "Upload File"], | |
key="editor_mode" | |
) | |
if editor_mode == "Upload File": | |
uploaded_file = st.file_uploader("Upload Manim Python File", type=["py"], key="code_uploader") | |
if uploaded_file: | |
code_content = uploaded_file.getvalue().decode("utf-8") | |
if code_content.strip(): # Only update if file has content | |
st.session_state.code = code_content | |
st.session_state.temp_code = code_content | |
# Code editor | |
if ACE_EDITOR_AVAILABLE: | |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else "" | |
st.session_state.temp_code = st_ace( | |
value=current_code, | |
language="python", | |
theme="monokai", | |
min_lines=20, | |
key=f"ace_editor_{st.session_state.editor_key}" | |
) | |
else: | |
current_code = st.session_state.code if hasattr(st.session_state, 'code') and st.session_state.code else "" | |
st.session_state.temp_code = st.text_area( | |
"Manim Python Code", | |
value=current_code, | |
height=400, | |
key=f"code_textarea_{st.session_state.editor_key}" | |
) | |
# 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 | |
# Generate button (use a form to prevent page reloads) | |
generate_btn = st.button("🚀 Generate Animation", use_container_width=True, key="generate_btn") | |
if generate_btn: | |
if not st.session_state.code: | |
st.error("Please enter some code before generating animation") | |
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 | |
with col2: | |
st.markdown("### 🖥️ Preview & Output") | |
# 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("### 🤖 AI Animation Assistant") | |
# 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("### 🎨 Asset Management") | |
asset_col1, asset_col2 = st.columns([1, 1]) | |
with asset_col1: | |
# Image uploader section | |
st.markdown("#### 📸 Image Assets") | |
st.markdown("Upload images to use in your animations:") | |
# Allow multiple image uploads | |
uploaded_images = st.file_uploader( | |
"Upload Images", | |
type=["jpg", "png", "jpeg", "svg"], | |
accept_multiple_files=True, | |
key="image_uploader_tab" | |
) | |
if uploaded_images: | |
# 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) | |
# Process each uploaded image | |
for uploaded_image in uploaded_images: | |
# Generate a unique filename and save the image | |
file_extension = uploaded_image.name.split(".")[-1] | |
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 | |
}) | |
# 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("#### 🎵 Audio Assets") | |
st.markdown("Upload audio files for background or narration:") | |
uploaded_audio = st.file_uploader("Upload Audio", type=["mp3", "wav", "ogg"], key="audio_uploader") | |
if uploaded_audio: | |
# 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 | |
# 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) | |
# 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("### 🎓 Educational Export Options") | |
# 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("Create various educational assets from your animation:") | |
# 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("### 🐍 Python Script Runner") | |
st.markdown("Execute Python scripts and visualize the results directly.") | |
# 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="monokai", | |
min_lines=15, | |
key=f"python_editor_{st.session_state.editor_key}" | |
) | |
else: | |
python_code = st.text_area( | |
"Python Code", | |
value=python_code, | |
height=400, | |
key=f"python_textarea_{st.session_state.editor_key}" | |
) | |
# 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("### 🔧 C/C++ Runner") | |
st.markdown("Write, compile, and run C/C++ code with advanced features.") | |
# 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="monokai", | |
min_lines=15, | |
key=f"cpp_editor_{st.session_state.editor_key}" | |
) | |
else: | |
cpp_code = st.text_area( | |
"C/C++ Code", | |
value=initial_code, | |
height=400, | |
key=f"cpp_textarea_{st.session_state.editor_key}" | |
) | |
# 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() |