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 from pygments.formatters import HtmlFormatter import base64 import re import shutil import time from datetime import datetime, timedelta import streamlit.components.v1 as components import uuid import pandas as pd import plotly.express as px import markdown import zipfile from azure.ai.inference import ChatCompletionsClient from azure.ai.inference.models import SystemMessage, UserMessage from azure.core.credentials import AzureKeyCredential from openai import OpenAI from transformers import pipeline import torch import traceback # ────────────────────────────────────────────────────────────────────────────── # Logging # ────────────────────────────────────────────────────────────────────────────── logging.basicConfig( level=logging.INFO, format="%(asctime)s • %(name)s • %(levelname)s • %(message)s", handlers=[logging.StreamHandler()] ) logger = logging.getLogger(__name__) # ────────────────────────────────────────────────────────────────────────────── # Model & Render Configuration # ────────────────────────────────────────────────────────────────────────────── 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}, "Llama-4-Scout-17B-16E-Instruct": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Meta", "warning": None}, "Llama-4-Maverick-17B-128E-Instruct-FP8": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Meta", "warning": None}, "gpt-4o-mini": {"max_tokens": 15000, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "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-mini": {"max_completion_tokens": 100000, "param_name": "max_completion_tokens", "api_version": "2024-12-01-preview", "category": "OpenAI", "warning": None}, "o1": {"max_completion_tokens": 100000, "param_name": "max_completion_tokens", "api_version": "2024-12-01-preview", "category": "OpenAI", "warning": None}, "o1-mini": {"max_completion_tokens": 66000, "param_name": "max_completion_tokens", "api_version": "2024-12-01-preview", "category": "OpenAI", "warning": None}, "o1-preview": {"max_tokens": 33000, "param_name": "max_tokens", "api_version": None, "category": "OpenAI", "warning": None}, "Phi-4-multimodal-instruct": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Microsoft", "warning": None}, "Mistral-large-2407": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Mistral", "warning": None}, "Codestral-2501": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Mistral", "warning": None}, "default": {"max_tokens": 4000, "param_name": "max_tokens", "api_version": None, "category": "Other", "warning": None} } QUALITY_PRESETS = { "480p": {"flag": "-ql", "fps": 30}, "720p": {"flag": "-qm", "fps": 30}, "1080p": {"flag": "-qh", "fps": 60}, "4K": {"flag": "-qk", "fps": 60}, "8K": {"flag": "-qp", "fps": 60}, } ANIMATION_SPEEDS = { "Slow": 0.5, "Normal": 1.0, "Fast": 2.0, "Very Fast": 3.0 } EXPORT_FORMATS = { "MP4 Video": "mp4", "GIF Animation": "gif", "WebM Video": "webm", "PNG Sequence": "png_sequence", "SVG": "svg" } # ────────────────────────────────────────────────────────────────────────────── # 1. prepare_api_params # ────────────────────────────────────────────────────────────────────────────── def prepare_api_params(messages, model_name): """Lookup MODEL_CONFIGS and build API call parameters.""" config = MODEL_CONFIGS.get(model_name, MODEL_CONFIGS["default"]) params = { "messages": messages, "model": model_name, config["param_name"]: config.get(config["param_name"]) } return params, config # ────────────────────────────────────────────────────────────────────────────── # 2. get_secret # ────────────────────────────────────────────────────────────────────────────── def get_secret(key): """Read an environment variable (e.g. password, API token).""" val = os.environ.get(key) if not val: logger.warning(f"Secret '{key}' not found") return val or "" # ────────────────────────────────────────────────────────────────────────────── # 3. check_password # ────────────────────────────────────────────────────────────────────────────── def check_password(): """Prompt for admin password and gate AI features.""" correct = get_secret("password") if not correct: st.error("Admin password not configured in secrets") return False if "auth_ok" not in st.session_state: st.session_state.auth_ok = False if not st.session_state.auth_ok: pwd = st.text_input("🔒 Enter admin password", type="password", help="Protects AI assistant") if pwd: if pwd == correct: st.session_state.auth_ok = True st.success("Access granted") else: st.error("Incorrect password") return False return True # ────────────────────────────────────────────────────────────────────────────── # 4. ensure_packages # ────────────────────────────────────────────────────────────────────────────── def ensure_packages(): """Check & install core dependencies on first run.""" required = { 'streamlit':'1.25.0','manim':'0.17.3','numpy':'1.22.0','Pillow':'9.0.0', 'transformers':'4.30.0','torch':'2.0.0','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':'' } missing = [] for pkg, ver in required.items(): try: __import__(pkg if pkg!='Pillow' else 'PIL') except ImportError: missing.append(f"{pkg}>={ver}" if ver else pkg) if missing: st.sidebar.info("Installing required packages...") prog = st.sidebar.progress(0) for i, pkg in enumerate(missing, 1): subprocess.run([sys.executable, "-m", "pip", "install", pkg], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) prog.progress(i/len(missing)) st.sidebar.success("All packages installed") # ────────────────────────────────────────────────────────────────────────────── # 5. install_custom_packages # ────────────────────────────────────────────────────────────────────────────── def install_custom_packages(package_list): """Install user-specified pip packages on the fly.""" packages = [p.strip() for p in package_list.split(",") if p.strip()] if not packages: return True, "No packages specified" results = [] success = True for pkg in packages: res = subprocess.run([sys.executable, "-m", "pip", "install", pkg], capture_output=True, text=True) ok = (res.returncode == 0) results.append(f"{pkg}: {'✅' if ok else '❌'}") if not ok: success = False return success, "\n".join(results) # ────────────────────────────────────────────────────────────────────────────── # 6. init_ai_models_direct # ────────────────────────────────────────────────────────────────────────────── @st.cache_resource(ttl=3600) def init_ai_models_direct(): """Initialize Azure ChatCompletionsClient for AI code generation.""" token = get_secret("github_token_api") if not token: st.error("GitHub token not found in secrets") return None endpoint = "https://models.inference.ai.azure.com" client = ChatCompletionsClient(endpoint=endpoint, credential=AzureKeyCredential(token)) return {"client": client, "model_name": "gpt-4o", "endpoint": endpoint} # ────────────────────────────────────────────────────────────────────────────── # 7. suggest_code_completion # ────────────────────────────────────────────────────────────────────────────── def suggest_code_completion(code_snippet, models): """Use the initialized AI model to generate complete Manim code.""" if not models: st.error("AI models not initialized") return None prompt = f"""Write a complete Manim animation scene based on this code or idea: {code_snippet} The code should include: - A Scene subclass - self.play() animations - wait times Return only valid Python code. """ config = MODEL_CONFIGS.get(models["model_name"].split("/")[-1], MODEL_CONFIGS["default"]) if config["category"] == "OpenAI": client = models.get("openai_client") or OpenAI(base_url="https://models.github.ai/inference", api_key=get_secret("github_token_api")) models["openai_client"] = client messages = [{"role":"developer","content":"Expert in Manim."}, {"role":"user","content":prompt}] params = {"messages": messages, "model": models["model_name"], config["param_name"]: config.get(config["param_name"])} resp = client.chat.completions.create(**params) content = resp.choices[0].message.content else: client = models["client"] msgs = [UserMessage(prompt)] params, _ = prepare_api_params(msgs, models["model_name"]) resp = client.complete(**params) content = resp.choices[0].message.content # extract code block if "```python" in content: content = content.split("```python")[1].split("```")[0] elif "```" in content: content = content.split("```")[1].split("```")[0] if "class" not in content: content = f"from manim import *\n\nclass MyScene(Scene):\n def construct(self):\n {content}" return content # ────────────────────────────────────────────────────────────────────────────── # 8. check_model_freshness # ────────────────────────────────────────────────────────────────────────────── def check_model_freshness(): """Return True if AI client was loaded within the past hour.""" if not st.session_state.get("ai_models"): return False last = st.session_state.ai_models.get("last_loaded") if not last: return False return datetime.fromisoformat(last) + timedelta(hours=1) > datetime.now() # ────────────────────────────────────────────────────────────────────────────── # 9. extract_scene_class_name # ────────────────────────────────────────────────────────────────────────────── def extract_scene_class_name(python_code): """Regex for the first class inheriting from Scene.""" m = re.findall(r"class\s+(\w+)\s*\([^)]*Scene[^)]*\)", python_code) return m[0] if m else "MyScene" # ────────────────────────────────────────────────────────────────────────────── # 10. highlight_code # ────────────────────────────────────────────────────────────────────────────── def highlight_code(code): """Return HTML+CSS highlighted Python code.""" formatter = HtmlFormatter(style="monokai", full=True, noclasses=True) return highlight(code, PythonLexer(), formatter) # ────────────────────────────────────────────────────────────────────────────── # 11. generate_manim_preview # ────────────────────────────────────────────────────────────────────────────── def generate_manim_preview(python_code): """Show icons for detected Manim objects in code.""" icons = [] mapping = { "Circle":"⭕","Square":"🔲","MathTex":"📊","Tex":"📊", "Text":"📝","Axes":"📈","ThreeDScene":"🧊","Sphere":"🌐","Cube":"🧊" } for key,icon in mapping.items(): if key in python_code: icons.append(icon) icons = icons or ["🎬"] html = f"""
Accurate preview requires full render