| """SciVisual-Agent Hugging Face Space entrypoint.""" |
|
|
| from __future__ import annotations |
|
|
| import html |
| import traceback |
| from typing import Tuple |
|
|
| import gradio as gr |
|
|
| from generator import build_default_agent |
|
|
|
|
| try: |
| import spaces |
| except ImportError: |
| class _SpacesFallback: |
| @staticmethod |
| def GPU(*args, **kwargs): |
| def decorator(func): |
| return func |
|
|
| return decorator |
|
|
| spaces = _SpacesFallback() |
|
|
|
|
| agent = None |
|
|
| custom_css = """ |
| @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap'); |
| |
| :root { |
| --cyber-bg: #0b0f19; |
| --cyber-panel: rgba(15, 24, 42, 0.72); |
| --cyber-panel-strong: rgba(4, 10, 20, 0.86); |
| --cyber-cyan: #00ffcc; |
| --cyber-pink: #ff0055; |
| --cyber-blue: #3b82f6; |
| --cyber-text: #e6fff9; |
| --cyber-muted: #8aa8b3; |
| --cyber-line: rgba(0, 255, 204, 0.35); |
| } |
| |
| * { |
| box-sizing: border-box; |
| letter-spacing: 0; |
| } |
| |
| body, |
| .gradio-container { |
| min-height: 100vh; |
| margin: 0; |
| color: var(--cyber-text) !important; |
| font-family: 'Share Tech Mono', 'Courier New', monospace !important; |
| background: |
| linear-gradient(rgba(0, 255, 204, 0.035) 1px, transparent 1px), |
| linear-gradient(90deg, rgba(0, 255, 204, 0.035) 1px, transparent 1px), |
| radial-gradient(circle at 18% 18%, rgba(0, 255, 204, 0.16), transparent 28%), |
| radial-gradient(circle at 82% 10%, rgba(255, 0, 85, 0.11), transparent 30%), |
| #0b0f19 !important; |
| background-size: 34px 34px, 34px 34px, auto, auto, auto !important; |
| } |
| |
| .gradio-container { |
| max-width: none !important; |
| padding: 0 !important; |
| } |
| |
| footer, |
| .footer, |
| .built-with { |
| display: none !important; |
| } |
| |
| #sci-shell { |
| margin: 0 auto; |
| padding: 18px 0 26px; |
| } |
| |
| .cyber-hero { |
| position: relative; |
| min-height: 128px; |
| padding: 18px 22px; |
| border: 1px solid var(--cyber-line); |
| background: linear-gradient(135deg, rgba(0, 255, 204, 0.12), rgba(255, 0, 85, 0.06) 52%, rgba(59, 130, 246, 0.1)); |
| box-shadow: 0 0 34px rgba(0, 255, 204, 0.12), inset 0 0 24px rgba(0, 255, 204, 0.08); |
| overflow: hidden; |
| } |
| |
| .cyber-hero::before { |
| content: ""; |
| position: absolute; |
| inset: 0; |
| background: repeating-linear-gradient(0deg, transparent 0 9px, rgba(0, 255, 204, 0.045) 10px); |
| pointer-events: none; |
| animation: scan 6s linear infinite; |
| } |
| |
| @keyframes scan { |
| from { transform: translateY(-18px); } |
| to { transform: translateY(18px); } |
| } |
| |
| .brand-row { |
| position: relative; |
| display: flex; |
| justify-content: space-between; |
| gap: 18px; |
| align-items: flex-start; |
| } |
| |
| .kicker { |
| color: var(--cyber-cyan); |
| font-size: 13px; |
| text-transform: uppercase; |
| } |
| |
| .cyber-title { |
| margin: 4px 0 8px; |
| color: #ffffff; |
| font-size: clamp(34px, 5vw, 74px); |
| line-height: 0.92; |
| text-shadow: 0 0 16px rgba(0, 255, 204, 0.75), 2px 0 0 rgba(255, 0, 85, 0.55); |
| } |
| |
| .cyber-subtitle { |
| max-width: 900px; |
| margin: 0; |
| color: var(--cyber-muted); |
| font-size: 16px; |
| } |
| |
| .status-pill { |
| flex: 0 0 auto; |
| min-width: 210px; |
| padding: 10px 12px; |
| border: 1px solid rgba(255, 0, 85, 0.55); |
| background: rgba(255, 0, 85, 0.08); |
| color: var(--cyber-text); |
| box-shadow: 0 0 18px rgba(255, 0, 85, 0.18); |
| } |
| |
| .status-pill.ready { |
| border-color: rgba(0, 255, 204, 0.6); |
| background: rgba(0, 255, 204, 0.08); |
| box-shadow: 0 0 18px rgba(0, 255, 204, 0.18); |
| } |
| |
| .panel-title { |
| margin: 0 0 12px; |
| color: var(--cyber-cyan); |
| font-size: 14px; |
| text-transform: uppercase; |
| border-bottom: 1px solid var(--cyber-line); |
| padding-bottom: 8px; |
| } |
| |
| .dashboard-grid { |
| gap: 18px !important; |
| margin-top: 18px; |
| } |
| |
| .panel-left, |
| .panel-right { |
| position: relative; |
| padding: 16px !important; |
| border: 1px solid var(--cyber-line); |
| background: var(--cyber-panel); |
| backdrop-filter: blur(18px); |
| box-shadow: 0 0 30px rgba(0, 255, 204, 0.10), inset 0 0 24px rgba(255, 255, 255, 0.025); |
| } |
| |
| .panel-right { |
| min-height: 710px; |
| } |
| |
| .panel-left::after, |
| .panel-right::after { |
| content: ""; |
| position: absolute; |
| top: -1px; |
| right: 16px; |
| width: 70px; |
| height: 2px; |
| background: var(--cyber-pink); |
| box-shadow: 0 0 12px var(--cyber-pink); |
| } |
| |
| .module-strip { |
| display: grid; |
| grid-template-columns: repeat(3, 1fr); |
| gap: 8px; |
| margin-bottom: 12px; |
| } |
| |
| .module-strip span { |
| display: block; |
| padding: 8px; |
| border: 1px solid rgba(0, 255, 204, 0.22); |
| background: rgba(0, 0, 0, 0.22); |
| color: var(--cyber-muted); |
| font-size: 12px; |
| text-align: center; |
| } |
| |
| .viewport-frame { |
| position: relative; |
| margin-bottom: 12px; |
| padding: 10px; |
| border: 1px solid rgba(0, 255, 204, 0.45); |
| background: linear-gradient(180deg, rgba(0, 255, 204, 0.05), rgba(255, 0, 85, 0.035)); |
| box-shadow: inset 0 0 22px rgba(0, 255, 204, 0.08); |
| } |
| |
| .viewport-frame::after { |
| content: ""; |
| position: absolute; |
| inset: 10px; |
| pointer-events: none; |
| border: 1px solid rgba(255, 0, 85, 0.26); |
| } |
| |
| .gr-form, |
| .block, |
| .form, |
| .wrap, |
| .contain { |
| background: transparent !important; |
| border: 0 !important; |
| box-shadow: none !important; |
| } |
| |
| label, |
| .label-wrap span { |
| color: var(--cyber-cyan) !important; |
| font-family: 'Share Tech Mono', 'Courier New', monospace !important; |
| text-transform: uppercase; |
| } |
| |
| textarea, |
| input, |
| select, |
| .input, |
| .gr-text-input, |
| .gradio-dropdown, |
| .token, |
| .wrap textarea { |
| color: var(--cyber-text) !important; |
| background: rgba(2, 6, 16, 0.88) !important; |
| border: 1px solid rgba(0, 255, 204, 0.48) !important; |
| border-radius: 0 !important; |
| box-shadow: inset 0 0 14px rgba(0, 255, 204, 0.06), 0 0 12px rgba(0, 255, 204, 0.08) !important; |
| font-family: 'Share Tech Mono', 'Courier New', monospace !important; |
| } |
| |
| textarea:focus, |
| input:focus { |
| border-color: var(--cyber-pink) !important; |
| box-shadow: 0 0 18px rgba(255, 0, 85, 0.25), inset 0 0 16px rgba(0, 255, 204, 0.08) !important; |
| } |
| |
| button, |
| .gr-button { |
| min-height: 44px !important; |
| border: 1px solid var(--cyber-cyan) !important; |
| border-radius: 0 !important; |
| color: #001713 !important; |
| background: linear-gradient(90deg, var(--cyber-cyan), #78ffe7) !important; |
| box-shadow: 0 0 18px rgba(0, 255, 204, 0.35) !important; |
| font-family: 'Share Tech Mono', 'Courier New', monospace !important; |
| font-weight: 700 !important; |
| text-transform: uppercase !important; |
| transition: transform 140ms ease, box-shadow 140ms ease, filter 140ms ease !important; |
| } |
| |
| button:hover, |
| .gr-button:hover { |
| transform: translateY(-1px); |
| filter: brightness(1.08); |
| box-shadow: 0 0 26px rgba(0, 255, 204, 0.55) !important; |
| } |
| |
| #clear-btn button { |
| color: var(--cyber-text) !important; |
| border-color: var(--cyber-pink) !important; |
| background: rgba(255, 0, 85, 0.12) !important; |
| box-shadow: 0 0 16px rgba(255, 0, 85, 0.22) !important; |
| } |
| |
| #terminal textarea { |
| min-height: 220px !important; |
| color: #9fffe9 !important; |
| background: |
| linear-gradient(rgba(0, 255, 204, 0.03) 50%, rgba(255, 0, 85, 0.025) 50%), |
| rgba(0, 0, 0, 0.72) !important; |
| background-size: 100% 8px !important; |
| font-size: 13px !important; |
| } |
| |
| #source-code textarea { |
| min-height: 430px !important; |
| color: #d9fff8 !important; |
| font-size: 13px !important; |
| overflow-y: scroll !important; |
| } |
| |
| .tabs, |
| .tab-nav, |
| .tabitem { |
| background: transparent !important; |
| border-color: rgba(0, 255, 204, 0.25) !important; |
| } |
| |
| .tab-nav button { |
| color: var(--cyber-text) !important; |
| background: rgba(3, 10, 20, 0.78) !important; |
| border: 1px solid rgba(0, 255, 204, 0.26) !important; |
| } |
| |
| .tab-nav button.selected { |
| color: #001713 !important; |
| background: var(--cyber-cyan) !important; |
| } |
| |
| #render-video { |
| border: 0 !important; |
| background: #020610 !important; |
| } |
| |
| #render-video video { |
| width: 100% !important; |
| max-height: 560px !important; |
| object-fit: contain !important; |
| background: #020610 !important; |
| } |
| |
| .examples-row { |
| margin-top: 12px; |
| color: var(--cyber-muted); |
| font-size: 12px; |
| } |
| |
| #quick-examples .wrap { |
| gap: 8px !important; |
| } |
| |
| #quick-examples .wrap label { |
| position: relative !important; |
| min-height: 38px !important; |
| padding: 9px 40px 9px 12px !important; |
| border: 1px solid rgba(0, 255, 204, 0.22) !important; |
| background: rgba(2, 6, 16, 0.58) !important; |
| color: var(--cyber-muted) !important; |
| transition: border-color 140ms ease, box-shadow 140ms ease, color 140ms ease, background 140ms ease !important; |
| } |
| |
| #quick-examples .wrap label:hover { |
| border-color: rgba(0, 255, 204, 0.55) !important; |
| color: var(--cyber-text) !important; |
| } |
| |
| #quick-examples .wrap label:has(input[type="radio"]:checked) { |
| border-color: var(--cyber-cyan) !important; |
| background: linear-gradient(90deg, rgba(0, 255, 204, 0.16), rgba(255, 0, 85, 0.08)) !important; |
| color: #ffffff !important; |
| box-shadow: 0 0 18px rgba(0, 255, 204, 0.20), inset 0 0 18px rgba(0, 255, 204, 0.06) !important; |
| } |
| |
| #quick-examples .wrap label:has(input[type="radio"]:checked)::after { |
| content: "✓"; |
| position: absolute; |
| right: 12px; |
| top: 50%; |
| transform: translateY(-50%); |
| color: var(--cyber-cyan); |
| font-size: 18px; |
| text-shadow: 0 0 10px rgba(0, 255, 204, 0.75); |
| } |
| |
| @media (max-width: 900px) { |
| #sci-shell { |
| width: calc(100vw - 16px); |
| padding-top: 8px; |
| } |
| |
| .brand-row { |
| flex-direction: column; |
| } |
| |
| .status-pill { |
| width: 100%; |
| } |
| |
| .module-strip { |
| grid-template-columns: 1fr; |
| } |
| |
| .panel-right { |
| min-height: auto; |
| } |
| } |
| """ |
|
|
|
|
| def startup_status_html() -> str: |
| return '<div class="status-pill ready">MODEL CORE: READY<br>ZEROGPU LOADS ON DEMAND</div>' |
|
|
|
|
| def initial_terminal_log() -> str: |
| return ( |
| "[boot] SciVisual-Agent interface online\n" |
| "[model] Adapter loads on demand inside the ZeroGPU render callback\n" |
| "[render] Waiting for prompt" |
| ) |
|
|
|
|
| def get_agent(): |
| global agent |
| if agent is None: |
| agent = build_default_agent() |
| return agent |
|
|
|
|
| @spaces.GPU(duration=300) |
| def run_visual_lab(prompt: str, retries: int) -> Tuple[str | None, str, str]: |
| try: |
| visual_agent = get_agent() |
| success, video_path, code, log = visual_agent.generate_and_fix( |
| user_prompt=prompt, |
| domain="Mathematics", |
| max_retries=int(retries), |
| ) |
| status = "[complete] Video render available" if success else "[failed] Render unavailable" |
| return video_path if success else None, code, f"{log}\n{status}" |
| except Exception: |
| error_log = traceback.format_exc() |
| return None, "", f"[fatal] Application exception\n{error_log}" |
|
|
|
|
| def load_example(example: str) -> str: |
| examples = { |
| "Spring Mass": """Create a professional 2D physics simulation of a horizontal spring-mass system with a real-time synchronized position graph using ManimCE. Fix the velocity easing issue. |
| |
| Requirements: |
| 1. Layout & Axes: |
| - Shift the entire Spring-Mass system to the upper half (around y = 1.5). |
| - In the lower half (centered at y = -2), create a coordinate system `Axes` using `Axes(x_range=[0, 7, 1], y_range=[-2.5, 2.5, 1], x_length=7, y_length=3)`. Color the axes WHITE and add labels "Time (t)" and "Position (x)". |
| |
| 2. Physical Objects: |
| - Vertical wall at x = -4 (from y = 0.5 to y = 2.5), color = WHITE. |
| - Horizontal track from x = -4 to x = 4 at y = 1.2, color = WHITE. |
| - Mass block using `Square(side_length=0.6, color=PURPLE, fill_opacity=0.8)`. |
| - Use a `ValueTracker(0)` for time `t`. |
| - Update the block's center position via `add_updater` to be exactly at `np.array([2 * np.cos(3 * t.get_value()), 1.5, 0])`. |
| - Create the spring using `always_redraw`. Calculate `L = mass.get_left()[0] - (-4)`. Use `ParametricFunction` with `t_range=[0, 1]` where: |
| * x_func(u) = -4 + u * L |
| * y_func(u) = 1.5 + 0.3 * np.sin(2 * np.pi * 8 * u) |
| Color the spring WHITE. |
| |
| 3. Graph Synchronization: |
| - Create an orange dynamic dot on the graph tracker using `add_updater` at `axes.c2p(t.get_value(), 2 * np.cos(3 * t.get_value()), 0)`. |
| - Create the graph line using `always_redraw` plotting `axes.plot(lambda time: 2 * np.cos(3 * time), x_range=[0, max(0.001, t.get_value())], color=ORANGE)`. |
| |
| 4. Strict Animation Speed (CRITICAL SPEED FIX): |
| - Animate the tracker `t` from 0 to 2*PI using `self.play(t.animate.set_value(2 * PI), run_time=6, rate_func=linear)`. |
| - YOU MUST EXPLICITLY INCLUDE `rate_func=linear` to eliminate the default smooth easing effect. This ensures that time 't' updates at a perfectly constant velocity, making the physical oscillation and graph rendering completely uniform from start to finish.""", |
| |
| "Orbit": """Create a beautiful, scientifically accurate 2D physics animation of a satellite in an elliptical orbit around Earth using ManimCE. |
| |
| Requirements: |
| 1. Scaling & Geometry (CRITICAL to avoid overlapping): |
| - Represent Earth as a clear sphere or circle in the center, but keep its radius small (e.g., radius=0.6) so it doesn't swallow the orbit. |
| - Create an explicit, highly elongated elliptical orbit path (`Ellipse(width=6.0, height=3.5)`) shifted slightly so that Earth sits exactly at one of the focal points (Foci) of the ellipse, NOT at the geometric center (Kepler's First Law). |
| |
| 2. Dynamic Vectors & Updaters: |
| - Represent the satellite as a distinct, colored Dot (e.g., Cyber Cyan) moving along the elliptical path using a custom tracker or `ValueTracker` for the orbital angle. |
| - Create two dynamic arrows (`Vector` or `Arrow`) attached to the satellite dot: |
| * Gravitational Force Vector (Color: Neon Red): Must always point directly from the satellite's current position to Earth's center. Its length must dynamically increase when close to Earth and decrease when far away (Inverse-square law). |
| * Velocity Vector (Color: Bright Green): Must always be perfectly tangent to the elliptical orbit path in the direction of motion. Its length must dynamically represent orbital speed (faster at perigee, slower at apogee). |
| - Use `add_updater` on both vectors so their positions, directions, and lengths update smoothly at every single frame based on the satellite's position. |
| |
| 3. Labels & Overlay: |
| - Add text labels for "Velocity (v)" and "Gravity (Fg)" matching the vector colors. |
| - In the top-right corner, add a LaTeX mathematical text displaying Newton's Law of Universal Gravitation: "F_g = G * (M_1 * M_2) / r²". |
| |
| 4. Animation flow: Run the animation for 2 complete orbital periods (about 10-12 seconds) so the viewer can clearly observe the dramatic speeding up at the close approach and slowing down at the far end. Wrap everything inside a single clean Scene class.""", |
| |
| "Simple Pendulum": """Create a high-quality Physics simulation of a Damped Simple Pendulum using ManimCE. |
| |
| Requirements: |
| 1. Physics Setup: Define explicit variables for gravitational acceleration (g=9.81), rod length (L=3.0), initial angle (theta = pi/4), and a damping coefficient (b=0.15) to simulate real-world air resistance. Use a clear numerical integration method (like Euler-Cromer) inside an object updater (`add_updater`) to dynamically recalculate the angular velocity and displacement at every frame (`dt`). |
| |
| 2. Visual Elements (Strict Coordinate Updates): |
| - A fixed ceiling line or pivot dot at the top center. |
| - A colored dot (e.g., Cyber Cyan or Neon Pink) representing the heavy bob. |
| - A clean line representing the pendulum rod that attaches the pivot to the bob. CRITICAL: You must attach an updater to this rod using `add_updater` so that its end position dynamically calls `rod.put_start_and_end_on(pivot.get_center(), bob.get_center())` at every single frame. The rod must always move dynamically with the bob. |
| - Add an elegant fading trace/trail (`TracedPath`) attached to the bob to visually map its decaying sinusoidal path across the screen over time. |
| |
| 3. Mathematical Overlay: In the upper left corner, display the differential equation governing the motion: "d²θ/dt² + (b/m)dθ/dt + (g/L)sin(θ) = 0" rendered beautifully via LaTeX. |
| |
| 4. Animation flow: Let the simulation run smoothly for 12 seconds to clearly demonstrate the kinetic energy converting to thermal energy as the oscillation slowly dampens to a complete halt. Ensure the code is self-contained and wrapped inside a single executable Scene class.""", |
| "Visual proof (a+b)² = a² + 2ab + b²": """Create a perfect 2D geometric animation proving the identity (a+b)² = a² + 2ab + b² using ManimCE based on strict quadrant alignment. Fix the overlapping bug. |
| |
| Requirements: |
| 1. Geometry & Scale Setup: |
| - Define lengths: a = 2.0 and b = 1.0. |
| - Define a central intersection origin point: `origin = np.array([0, 0, 0])`. All blocks must be strictly positioned relative to this point by alignment. |
| |
| 2. Precise Corner Alignment (CRITICAL OVERLAP FIX): |
| - Block 1 (Square a²): Create a Square with side_length=a, color=BLUE. Use `.next_to(origin, UL, buff=0)` or explicitly align its bottom-right corner to `origin` so it sits entirely in the Upper-Left quadrant. |
| - Block 2 (Square b²): Create a Square with side_length=b, color=PINK. Use `.next_to(origin, DR, buff=0)` or explicitly align its top-left corner to `origin` so it sits entirely in the Lower-Right quadrant. |
| - Block 3 (Rectangle ab - Top Right): Create a Rectangle with width=b, height=a, color=GREEN. Use `.next_to(origin, UR, buff=0)` or align its bottom-left corner to `origin` so it sits entirely in the Upper-Right quadrant. |
| - Block 4 (Rectangle ab - Bottom Left): Create a Rectangle with width=a, height=b, color=GREEN. Use `.next_to(origin, DL, buff=0)` or align its top-right corner to `origin` so it sits entirely in the Lower-Left quadrant. |
| - Set fill_opacity=0.5 and stroke_color=WHITE for all 4 blocks. |
| |
| 3. Main Outer Square Framework: |
| - Create a large outer Square with side_length=(a+b) to represent the final boundary. Center it precisely at the combined visual center of the 4 blocks, which is `np.array([(-a+b)/2, (a-b)/2, 0])`. Color it WHITE with a thin stroke width, without fill. |
| |
| 4. Text Labels & Math Mapping: |
| - Place MathTex text labels ("a²", "b²", "ab", "ab") centered inside each corresponding block using `.move_to(block.get_center())`. Note: Labels must be created and positioned AFTER the blocks have been moved to their correct quadrants. |
| - Place the formula "(a + b)² = a² + 2ab + b²" at the top edge of the screen. |
| |
| 5. Animation Sequence: |
| - Step 1: Write the formula at the top and create the thin outer main square framework first (1.5s). |
| - Step 2: Draw Block 1 (a²) and Block 2 (b²) simultaneously using `FadeIn` (1s). |
| - Step 3: Animate the two Rectangle blocks (ab) creating themselves using `Create` to fill the remaining empty corners (1.5s). |
| - Step 4: Display the labels inside each block using `Write`. |
| |
| 6. Constraint: Keep the code clean, modular, and under 5 seconds total run_time. Do not use random pixel offsets; strictly use `next_to` with `buff=0` for perfect grid locking.""", |
| |
| "Fourier Series" : """Animate the geometric construction of a square wave using its first 7 Fourier components (odd harmonics: n=1,3,5,7,9,11,13) in ManimCE. Fix the always_redraw compilation conflict. |
| |
| Requirements: |
| 1. Math Equations: |
| - Base square wave graph: `axes.plot(lambda x: np.sign(np.sin(x)), color=RED)`. (Do NOT wrap this static red wave in always_redraw). |
| - Fourier mathematical sum formula inside the loop: `sum((4 / (n * PI)) * np.sin(n * x) for n in range(1, 2 * int(N_tracker.get_value()), 2))`. |
| |
| 2. Correct Animation Sequence (CRITICAL GRAPHICS FIX): |
| - Step 1: Add the Axes and the static red square wave directly to the scene using `self.add(axes, square_wave)`. |
| - Step 2: Create a STATIC fundamental sine wave curve (where N=1 hardcoded) using `fourier_base = axes.plot(lambda x: (4 / PI) * np.sin(x), color=YELLOW)`. Animate this static line with `self.play(Create(fourier_base), run_time=1.0)`. This guarantees a beautiful, bug-free drawing effect from left to right in the first second. |
| - Step 3: Initialize `N_tracker = ValueTracker(1)`. Now, introduce the dynamic morphing curve using `always_redraw`. Inside its lambda, it must calculate the Fourier sum based on `N_tracker.get_value()`. |
| - Step 4: Use `self.add(fourier_dynamic)` to overlay it, remove the static `fourier_base`, and immediately animate `N_tracker` from 1 to 7 using `self.play(N_tracker.animate.set_value(7), run_time=4.5, rate_func=linear)`. |
| |
| 3. Ensure no while-loops are used. The transition between the static line and the morphing line must be seamless.""", |
|
|
|
|
| |
| } |
| return examples.get(example, "") |
|
|
|
|
| with gr.Blocks(css=custom_css, title="SciVisual-Agent") as demo: |
| gr.HTML( |
| f""" |
| <main id="sci-shell"> |
| <section class="cyber-hero"> |
| <div class="brand-row"> |
| <div> |
| <div class="kicker">Physics & Mathematics Virtual Animation Lab</div> |
| <h1 class="cyber-title">SciVisual-Agent</h1> |
| <p class="cyber-subtitle"> |
| Turn maths and physics ideas into <b>accurate</b> animated Manim videos. |
| </p> |
| </div> |
| |
| </div> |
| </section> |
| """ |
| ) |
|
|
| with gr.Row(elem_classes=["dashboard-grid"]): |
| with gr.Column(scale=5, elem_classes=["panel-left"]): |
| prompt = gr.Textbox( |
| label="Describe the animation", |
| placeholder="Please enter your animation description or select one of the examples below.", |
| lines=8, |
| max_lines=14, |
| interactive=True, |
| ) |
| retries = gr.Slider( |
| minimum=0, |
| maximum=5, |
| value=3, |
| step=1, |
| label="Self-Correction Retries", |
| interactive=True, |
| ) |
| with gr.Row(): |
| generate_btn = gr.Button("Render", variant="primary") |
| clear_btn = gr.Button("Clear", elem_id="clear-btn") |
|
|
| example_choice = gr.Radio( |
| choices=["Simple Pendulum", "Spring Mass", "Visual proof (a+b)² = a² + 2ab + b²", "Fourier Series"], |
| label="Quick Examples", |
| value=None, |
| interactive=True, |
| elem_id="quick-examples", |
| ) |
| terminal = gr.Textbox( |
| label="System Terminal Log", |
| value=initial_terminal_log(), |
| lines=12, |
| max_lines=18, |
| interactive=False, |
| elem_id="terminal", |
| ) |
| gr.HTML('<div class="examples-row">Status bus: prompt -> model adapter code -> temp_scene.py -> Manim render -> MP4 viewport</div>') |
|
|
| with gr.Column(scale=7, elem_classes=["panel-right"]): |
| gr.HTML('<div class="panel-title">Output Observatory</div>') |
| with gr.Tabs(): |
| with gr.Tab("Rendered Video"): |
| video = gr.Video(label=None, elem_id="render-video", height=560, autoplay=True, loop=True) |
| with gr.Tab("Generated Code"): |
| source = gr.Textbox( |
| label="Clean Python Source", |
| lines=24, |
| max_lines=34, |
| interactive=False, |
| elem_id="source-code", |
| ) |
|
|
| gr.HTML("</main>") |
|
|
| generate_btn.click( |
| fn=run_visual_lab, |
| inputs=[prompt, retries], |
| outputs=[video, source, terminal], |
| show_progress=True, |
| ) |
| clear_btn.click( |
| fn=lambda: ("", None, "", initial_terminal_log()), |
| inputs=None, |
| outputs=[prompt, example_choice, source, terminal], |
| ) |
| example_choice.change(fn=load_example, inputs=example_choice, outputs=prompt) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|