Spaces:
Sleeping
Sleeping
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": [], | |
"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. DO NOT include any requirements - use ONLY packages that are already included with Gradio | |
5. Keep the app simple and focused on the user's request - NO extra package dependencies | |
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. Only use basic Python libraries and features from Gradio and its default dependencies | |
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) | |
# For simplicity, we're going to avoid additional requirements | |
app_info = { | |
"app_code": code, | |
"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 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, | |
and runs it directly with no additional dependencies. 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("<div style='text-align:center; padding:50px;'><h3>Your generated app will appear here</h3></div>") | |
# 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", | |
"<div style='text-align:center; padding:50px;'><h3>Invalid API key</h3></div>", | |
"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, | |
"<div style='text-align:center; padding:50px;'><h3>Generating code...</h3></div>", | |
"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'}", | |
"<div style='text-align:center; padding:50px;'><h3>Error generating app</h3></div>", | |
f"Error: {error or 'Failed to generate app'}", | |
gr.update(visible=False), None | |
) | |
code = app_info["app_code"] | |
# 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 = "⏳ Starting the app..." | |
yield ( | |
code, status_message, | |
"<div style='text-align:center; padding:50px;'><h3>Starting app...</h3></div>", | |
"Starting the app...", | |
gr.update(visible=False), None | |
) | |
# Run the app | |
success, app_file = run_app(code, output_queue) | |
# Update the progress output | |
progress_text = "" | |
while not output_queue.empty(): | |
progress_text += output_queue.get_nowait() | |
if not success: | |
return ( | |
code, "⚠️ Failed to start the app", | |
"<div style='text-align:center; padding:50px;'><h3>Error starting app</h3></div>", | |
progress_text, | |
gr.update(visible=False), None | |
) | |
# Show the app in an iframe | |
iframe_html = f""" | |
<div style="height:600px; border:1px solid #ddd; border-radius:5px; overflow:hidden;"> | |
<iframe src="http://localhost:7861" width="100%" height="100%" frameborder="0"></iframe> | |
</div> | |
""" | |
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)}", | |
"<div style='text-align:center; padding:50px;'><h3>An error occurred</h3></div>", | |
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", | |
"<div style='text-align:center; padding:50px;'><h3>App stopped</h3></div>", | |
"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__": | |
# Launch without any optional parameters that might cause issues | |
demo.queue().launch(server_name="0.0.0.0", server_port=7860) |