import gradio as gr import requests import re import os import subprocess import tempfile import sys import json import time import threading import queue import atexit # Print Python version and gradio version for debugging print(f"Python version: {sys.version}") print(f"Gradio version: {gr.__version__}") # Track running processes and resources for cleanup running_process = None temp_files = [] # Clean up resources on exit def cleanup_resources(): global running_process, temp_files # Stop any running process if running_process and running_process.poll() is None: try: running_process.terminate() running_process.wait(timeout=5) print(f"Terminated process {running_process.pid}") except Exception as e: print(f"Error terminating process: {e}") # Remove temporary files for file_path in temp_files: if os.path.exists(file_path): try: os.unlink(file_path) print(f"Removed temporary file: {file_path}") except Exception as e: print(f"Error removing file {file_path}: {e}") atexit.register(cleanup_resources) # Queue for capturing subprocess output output_queue = queue.Queue() def read_output(process, output_queue): """Read output from a process and put it in a queue""" for line in iter(process.stdout.readline, ''): if line: output_queue.put(line) process.stdout.close() def read_error(process, output_queue): """Read error output from a process and put it in a queue""" for line in iter(process.stderr.readline, ''): if line: output_queue.put(f"ERROR: {line}") process.stderr.close() def call_openai_api(api_key, prompt): """Call OpenAI API to generate Gradio app code and requirements""" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } system_prompt = """You are an expert at creating Python applications with Gradio. Create a complete, standalone Gradio application based on the user's prompt. Provide your response in the following JSON format: { "app_code": "# Python code here...", "requirements": ["gradio==3.32.0", "other_packages"], "description": "Brief description of what the app does" } Important guidelines: 1. The app_code should be a complete Gradio application 2. Use ONLY gr.Interface (NOT gr.Blocks) 3. Always include server_name="0.0.0.0" and server_port=7861 in the launch parameters 4. First requirement must be exactly "gradio==3.32.0" 5. Keep the app simple and focused on the user's request 6. DO NOT use any flagging callbacks or flagging_dir parameters in launch 7. DO NOT create or use any directories or file paths 8. Do not use features from newer Gradio versions Example: ```python import gradio as gr import numpy as np def process(input_value): return input_value * 2 demo = gr.Interface( fn=process, inputs=gr.Number(label="Input"), outputs=gr.Number(label="Output"), title="Number Doubler" ) demo.launch(server_name="0.0.0.0", server_port=7861) ``` """ data = { "model": "gpt-4o", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} ], "temperature": 0.2, "max_tokens": 4000 } try: response = requests.post( "https://api.openai.com/v1/chat/completions", headers=headers, json=data ) if response.status_code != 200: return None, f"API Error: {response.status_code} - {response.text}" result = response.json() content = result["choices"][0]["message"]["content"] # Try to parse the JSON response try: # Look for JSON object json_pattern = r'({[\s\S]*})' json_match = re.search(json_pattern, content) if json_match: json_str = json_match.group(1) app_info = json.loads(json_str) else: # Try to extract code and requirements manually code_pattern = r'```python\s*([\s\S]*?)```' code_match = re.search(code_pattern, content) if not code_match: return None, "No code found in the response" code = code_match.group(1) # Extract requirements from imports req_pattern = r'import\s+([a-zA-Z0-9_]+)' req_matches = re.findall(req_pattern, code) requirements = ["gradio==3.32.0"] for module in req_matches: if module != "gradio" and module not in requirements and module not in ["os", "sys"]: requirements.append(module) app_info = { "app_code": code, "requirements": requirements, "description": "Generated Gradio application" } # Clean up the code if needed if "```python" in app_info["app_code"]: code_pattern = r'```python\s*([\s\S]*?)```' code_match = re.search(code_pattern, app_info["app_code"]) if code_match: app_info["app_code"] = code_match.group(1) return app_info, None except Exception as e: return None, f"Failed to parse API response: {str(e)}" except Exception as e: return None, f"API call failed: {str(e)}" def install_package(package, output_queue): """Install a package and send output to the queue""" cmd = [sys.executable, "-m", "pip", "install", "--upgrade", package] print(f"Installing: {package}") output_queue.put(f"Installing: {package}\n") process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1 ) # Start threads to read output stdout_thread = threading.Thread(target=read_output, args=(process, output_queue)) stderr_thread = threading.Thread(target=read_error, args=(process, output_queue)) stdout_thread.daemon = True stderr_thread.daemon = True stdout_thread.start() stderr_thread.start() # Wait for the process to complete process.wait() # Wait for threads to finish stdout_thread.join() stderr_thread.join() if process.returncode == 0: output_queue.put(f"Successfully installed {package}\n") return True else: output_queue.put(f"Failed to install {package} (error code: {process.returncode})\n") return False def run_app(app_code, output_queue): """Run the app in a separate process""" global running_process, temp_files # Stop any existing process if running_process and running_process.poll() is None: running_process.terminate() try: running_process.wait(timeout=5) except subprocess.TimeoutExpired: running_process.kill() # Create a temporary file for the app fd, app_file = tempfile.mkstemp(suffix='.py') os.close(fd) with open(app_file, 'w') as f: f.write(app_code) temp_files.append(app_file) # Make sure the code has the correct launch parameters with open(app_file, 'r') as f: app_code = f.read() # Remove flagging_callback if present app_code = re.sub(r'flagging_callback\s*=\s*[^,)]+', '', app_code) # Remove flagging_dir if present app_code = re.sub(r'flagging_dir\s*=\s*[^,)]+', '', app_code) # Make sure it has the correct launch parameters if "demo.launch" not in app_code: app_code += "\n\ndemo.launch(server_name='0.0.0.0', server_port=7861)\n" elif "server_name" not in app_code or "server_port" not in app_code: app_code = re.sub(r'demo\.launch\s*\(', r'demo.launch(server_name="0.0.0.0", server_port=7861, ', app_code) # Write the updated code back to the file with open(app_file, 'w') as f: f.write(app_code) # Run the app output_queue.put(f"Starting the app...\n") running_process = subprocess.Popen( [sys.executable, app_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1 ) # Start threads to read output stdout_thread = threading.Thread(target=read_output, args=(running_process, output_queue)) stderr_thread = threading.Thread(target=read_error, args=(running_process, output_queue)) stdout_thread.daemon = True stderr_thread.daemon = True stdout_thread.start() stderr_thread.start() # Wait a bit for the app to start time.sleep(5) # Check if the process is still running if running_process.poll() is not None: output_queue.put(f"App failed to start (error code: {running_process.returncode})\n") return False, app_file output_queue.put(f"App is running on port 7861\n") return True, app_file # Create the Gradio interface with gr.Blocks(title="Gradio App Generator") as demo: gr.Markdown("# 🤖 Gradio App Generator") gr.Markdown(""" This app generates a Gradio application based on your description, installs required packages, and runs it directly within this container. The generated app will be displayed below. """) with gr.Row(): with gr.Column(scale=1): api_key = gr.Textbox( label="OpenAI API Key", placeholder="sk-...", type="password", info="Your key is used only for this session" ) prompt = gr.Textbox( label="App Description", placeholder="Describe the Gradio app you want to create...", lines=5 ) with gr.Row(): generate_btn = gr.Button("Generate & Run App", variant="primary") stop_btn = gr.Button("Stop Running App", variant="stop", visible=False) with gr.Accordion("Generated Code", open=False): code_output = gr.Code(language="python", label="App Code") progress_output = gr.Textbox( label="Progress Log", lines=10, max_lines=20, interactive=False ) status_output = gr.Markdown("") with gr.Column(scale=2): # Frame to display the running app app_frame = gr.HTML("

Your generated app will appear here

") # App file storage app_file_state = gr.State(None) def on_generate(api_key_val, prompt_val): # Initialize output queue global output_queue output_queue = queue.Queue() # Validate API key if not api_key_val or len(api_key_val) < 20 or not api_key_val.startswith("sk-"): return ( None, "⚠️ Please provide a valid OpenAI API key", "

Invalid API key

", "Please enter a valid OpenAI API key", gr.update(visible=False), None ) try: # Generate app code and requirements status_message = "⏳ Generating app code..." yield ( None, status_message, "

Generating code...

", "Generating app code...", gr.update(visible=False), None ) app_info, error = call_openai_api(api_key_val, prompt_val) if error or not app_info: return ( None, f"⚠️ {error or 'Failed to generate app'}", "

Error generating app

", f"Error: {error or 'Failed to generate app'}", gr.update(visible=False), None ) code = app_info["app_code"] requirements = app_info["requirements"] # Make sure server_name and server_port are specified if "server_name" not in code or "server_port" not in code: if "demo.launch(" in code: code = code.replace("demo.launch(", "demo.launch(server_name=\"0.0.0.0\", server_port=7861, ") else: code += "\n\ndemo.launch(server_name=\"0.0.0.0\", server_port=7861)" status_message = "⏳ Installing packages..." yield ( code, status_message, "

Installing packages...

", "Starting package installation...", gr.update(visible=False), None ) # Install packages one by one with real-time updates install_results = [] for i, package in enumerate(requirements): success = install_package(package, output_queue) install_results.append(success) # Update the progress output progress_text = "" while not output_queue.empty(): progress_text += output_queue.get_nowait() yield ( code, f"⏳ Installing packages ({i+1}/{len(requirements)})...", "

Installing packages...

", progress_text, gr.update(visible=False), None ) # Check if all packages were installed successfully if not all(install_results): progress_text = "" while not output_queue.empty(): progress_text += output_queue.get_nowait() return ( code, "⚠️ Some packages failed to install", "

Error installing packages

", progress_text, gr.update(visible=False), None ) status_message = "⏳ Starting the app..." yield ( code, status_message, "

Starting app...

", progress_text, gr.update(visible=False), None ) # Run the app success, app_file = run_app(code, output_queue) # Update the progress output again progress_text = "" while not output_queue.empty(): progress_text += output_queue.get_nowait() if not success: return ( code, "⚠️ Failed to start the app", "

Error starting app

", progress_text, gr.update(visible=False), None ) # Show the app in an iframe iframe_html = f"""
""" return ( code, f"✅ App is running! View it below.", iframe_html, progress_text, gr.update(visible=True), app_file ) except Exception as e: import traceback error_details = traceback.format_exc() return ( None, f"⚠️ Error: {str(e)}", "

An error occurred

", f"Error: {str(e)}\n\n{error_details}", gr.update(visible=False), None ) def on_stop(app_file): global running_process if running_process and running_process.poll() is None: running_process.terminate() try: running_process.wait(timeout=5) except subprocess.TimeoutExpired: running_process.kill() running_process = None # Clean up the app file if app_file and os.path.exists(app_file): try: os.unlink(app_file) if app_file in temp_files: temp_files.remove(app_file) except: pass return ( "✅ App stopped", "

App stopped

", "App has been stopped", gr.update(visible=False), None ) generate_btn.click( on_generate, inputs=[api_key, prompt], outputs=[ code_output, status_output, app_frame, progress_output, stop_btn, app_file_state ] ) stop_btn.click( on_stop, inputs=[app_file_state], outputs=[ status_output, app_frame, progress_output, stop_btn, app_file_state ] ) if __name__ == "__main__": # Make sure we're not trying to use flagging - for Gradio 3.50.2 compatibility demo.queue().launch(server_name="0.0.0.0", server_port=7860)