Spaces:
Running
Running
Add Docker configuration and deployment files
Browse files- Dockerfile +22 -5
- app.py +41 -50
- nginx.conf +63 -0
- start.sh +29 -0
Dockerfile
CHANGED
@@ -1,14 +1,31 @@
|
|
1 |
FROM python:3.12-slim
|
2 |
|
|
|
|
|
|
|
|
|
|
|
3 |
RUN useradd -m -u 1000 user
|
4 |
-
USER user
|
5 |
-
ENV PATH="/home/user/.local/bin:$PATH"
|
6 |
|
|
|
7 |
WORKDIR /app
|
8 |
-
|
9 |
-
COPY --chown=user ./requirements.txt requirements.txt
|
10 |
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
COPY --chown=user . /app
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
CMD ["
|
|
|
1 |
FROM python:3.12-slim
|
2 |
|
3 |
+
# Install nginx
|
4 |
+
RUN apt-get update && apt-get install -y nginx && \
|
5 |
+
apt-get clean && rm -rf /var/lib/apt/lists/*
|
6 |
+
|
7 |
+
# Create user
|
8 |
RUN useradd -m -u 1000 user
|
|
|
|
|
9 |
|
10 |
+
# Install Python packages globally so they're accessible to all users
|
11 |
WORKDIR /app
|
12 |
+
COPY ./requirements.txt requirements.txt
|
|
|
13 |
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
14 |
|
15 |
+
# Copy nginx configuration as main config
|
16 |
+
COPY nginx.conf /etc/nginx/nginx.conf
|
17 |
+
|
18 |
+
# Create all nginx directories and set permissions for user
|
19 |
+
RUN mkdir -p /var/run/nginx /var/log/nginx /var/lib/nginx/body /var/lib/nginx/fastcgi \
|
20 |
+
/var/lib/nginx/proxy /var/lib/nginx/scgi /var/lib/nginx/uwsgi && \
|
21 |
+
chown -R user:user /var/run/nginx /var/log/nginx /var/lib/nginx
|
22 |
+
|
23 |
+
# Copy application files and make script executable
|
24 |
COPY --chown=user . /app
|
25 |
+
RUN chmod +x /app/start.sh
|
26 |
+
|
27 |
+
# Switch to user for execution
|
28 |
+
USER user
|
29 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
30 |
|
31 |
+
CMD ["/app/start.sh"]
|
app.py
CHANGED
@@ -11,15 +11,13 @@ from src.utils import load_file
|
|
11 |
from ui_helpers import stream_to_gradio
|
12 |
|
13 |
preview_process = None
|
14 |
-
PREVIEW_PORT = 7861 #
|
15 |
|
16 |
|
17 |
def get_preview_url():
|
18 |
"""Get the appropriate preview URL based on environment."""
|
19 |
-
#
|
20 |
-
|
21 |
-
return "about:blank"
|
22 |
-
return f"http://127.0.0.1:{PREVIEW_PORT}"
|
23 |
|
24 |
|
25 |
PREVIEW_URL = get_preview_url()
|
@@ -58,11 +56,6 @@ def save_file(path, new_text):
|
|
58 |
|
59 |
def stop_preview_app():
|
60 |
"""Stop the preview app subprocess if it's running."""
|
61 |
-
# In HF Spaces, this logic is simplified
|
62 |
-
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
63 |
-
print("ℹ️ Preview subprocess management is disabled in Hugging Face Spaces.")
|
64 |
-
return
|
65 |
-
|
66 |
global preview_process
|
67 |
if preview_process and preview_process.poll() is None:
|
68 |
print(f"🛑 Stopping preview app process (PID: {preview_process.pid})...")
|
@@ -81,11 +74,6 @@ def stop_preview_app():
|
|
81 |
|
82 |
def start_preview_app():
|
83 |
"""Start the preview app in a subprocess if it's not already running."""
|
84 |
-
# In HF Spaces, this logic is simplified
|
85 |
-
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
86 |
-
print("✅ Preview functionality simplified for HF Spaces.")
|
87 |
-
return True, "Preview is integrated and available."
|
88 |
-
|
89 |
global preview_process
|
90 |
# Stop any existing process before starting a new one
|
91 |
stop_preview_app()
|
@@ -125,35 +113,32 @@ def start_preview_app():
|
|
125 |
|
126 |
def create_iframe_preview():
|
127 |
"""Create an iframe that loads the sandbox app."""
|
128 |
-
|
129 |
-
|
130 |
-
return (
|
131 |
-
'<div style="padding: 20px; text-align: center; '
|
132 |
-
"background-color: #f8f9fa; border: 1px solid #e9ecef; "
|
133 |
-
'border-radius: 8px;">'
|
134 |
-
"<h3>📱 Preview</h3>"
|
135 |
-
"<p>Code preview is available after the AI generates an application.</p>"
|
136 |
-
"</div>"
|
137 |
-
)
|
138 |
-
|
139 |
-
# For local execution, attempt to start the preview and show an iframe
|
140 |
success, message = start_preview_app()
|
|
|
141 |
if success:
|
142 |
-
|
|
|
|
|
|
|
|
|
143 |
else:
|
144 |
-
|
|
|
|
|
145 |
|
146 |
|
147 |
def is_preview_running():
|
148 |
"""Check if the preview app is running and accessible."""
|
149 |
-
|
150 |
-
return
|
151 |
|
152 |
|
153 |
def ensure_preview_running():
|
154 |
"""Ensure the preview app is running, start it if needed."""
|
155 |
-
|
156 |
-
|
157 |
|
158 |
|
159 |
def get_default_model_for_provider(provider: str) -> str:
|
@@ -355,11 +340,12 @@ class GradioUI:
|
|
355 |
with gr.Row(elem_classes="main-container"):
|
356 |
# Left side - Chat Interface
|
357 |
with gr.Column(scale=1, elem_classes="chat-container"):
|
|
|
|
|
|
|
|
|
358 |
chatbot = gr.Chatbot(
|
359 |
-
avatar_images=(
|
360 |
-
None,
|
361 |
-
"http://em-content.zobj.net/source/apple/419/growing-heart_1f497.png",
|
362 |
-
),
|
363 |
type="messages",
|
364 |
resizable=True,
|
365 |
height="70vh",
|
@@ -376,10 +362,12 @@ class GradioUI:
|
|
376 |
# Right side - Preview/Code/Settings Toggle
|
377 |
with gr.Column(scale=4, elem_classes="preview-container"):
|
378 |
with gr.Tab("Preview"):
|
|
|
|
|
|
|
|
|
379 |
preview_html = gr.HTML(
|
380 |
-
value=
|
381 |
-
"Preview will load here."
|
382 |
-
"</div>",
|
383 |
elem_id="preview-container",
|
384 |
)
|
385 |
|
@@ -594,16 +582,11 @@ class GradioUI:
|
|
594 |
[text_input, submit_btn],
|
595 |
)
|
596 |
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
demo.load(fn=on_app_load, outputs=[preview_html])
|
601 |
|
602 |
# Clean up on app close
|
603 |
-
|
604 |
-
stop_preview_app()
|
605 |
-
|
606 |
-
demo.unload(cleanup)
|
607 |
|
608 |
return demo
|
609 |
|
@@ -642,7 +625,15 @@ if __name__ == "__main__":
|
|
642 |
|
643 |
agent = KISSAgent()
|
644 |
|
645 |
-
#
|
646 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
647 |
|
648 |
GradioUI(agent).launch(share=False, server_name="0.0.0.0", server_port=port)
|
|
|
11 |
from ui_helpers import stream_to_gradio
|
12 |
|
13 |
preview_process = None
|
14 |
+
PREVIEW_PORT = 7861 # Internal port for preview apps
|
15 |
|
16 |
|
17 |
def get_preview_url():
|
18 |
"""Get the appropriate preview URL based on environment."""
|
19 |
+
# In Docker/HF Spaces with nginx proxy, use the proxy path
|
20 |
+
return "/preview/"
|
|
|
|
|
21 |
|
22 |
|
23 |
PREVIEW_URL = get_preview_url()
|
|
|
56 |
|
57 |
def stop_preview_app():
|
58 |
"""Stop the preview app subprocess if it's running."""
|
|
|
|
|
|
|
|
|
|
|
59 |
global preview_process
|
60 |
if preview_process and preview_process.poll() is None:
|
61 |
print(f"🛑 Stopping preview app process (PID: {preview_process.pid})...")
|
|
|
74 |
|
75 |
def start_preview_app():
|
76 |
"""Start the preview app in a subprocess if it's not already running."""
|
|
|
|
|
|
|
|
|
|
|
77 |
global preview_process
|
78 |
# Stop any existing process before starting a new one
|
79 |
stop_preview_app()
|
|
|
113 |
|
114 |
def create_iframe_preview():
|
115 |
"""Create an iframe that loads the sandbox app."""
|
116 |
+
print("🔍 create_iframe_preview() called")
|
117 |
+
# Try to start the preview app and show an iframe
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
success, message = start_preview_app()
|
119 |
+
print(f"🔍 start_preview_app() result: success={success}, message={message}")
|
120 |
if success:
|
121 |
+
iframe_html = (
|
122 |
+
f'<iframe src="{PREVIEW_URL}" ' 'width="100%" height="500px"></iframe>'
|
123 |
+
)
|
124 |
+
print(f"🔍 Creating iframe: {iframe_html}")
|
125 |
+
return iframe_html
|
126 |
else:
|
127 |
+
error_html = f'<div style="color: red; padding: 20px;">{message}</div>'
|
128 |
+
print(f"🔍 Error in preview: {error_html}")
|
129 |
+
return error_html
|
130 |
|
131 |
|
132 |
def is_preview_running():
|
133 |
"""Check if the preview app is running and accessible."""
|
134 |
+
global preview_process
|
135 |
+
return preview_process is not None and preview_process.poll() is None
|
136 |
|
137 |
|
138 |
def ensure_preview_running():
|
139 |
"""Ensure the preview app is running, start it if needed."""
|
140 |
+
if not is_preview_running():
|
141 |
+
start_preview_app()
|
142 |
|
143 |
|
144 |
def get_default_model_for_provider(provider: str) -> str:
|
|
|
340 |
with gr.Row(elem_classes="main-container"):
|
341 |
# Left side - Chat Interface
|
342 |
with gr.Column(scale=1, elem_classes="chat-container"):
|
343 |
+
avatar_url = (
|
344 |
+
"http://em-content.zobj.net/source/apple/419/"
|
345 |
+
"growing-heart_1f497.png"
|
346 |
+
)
|
347 |
chatbot = gr.Chatbot(
|
348 |
+
avatar_images=(None, avatar_url),
|
|
|
|
|
|
|
349 |
type="messages",
|
350 |
resizable=True,
|
351 |
height="70vh",
|
|
|
362 |
# Right side - Preview/Code/Settings Toggle
|
363 |
with gr.Column(scale=4, elem_classes="preview-container"):
|
364 |
with gr.Tab("Preview"):
|
365 |
+
iframe_url = (
|
366 |
+
f'<iframe src="{PREVIEW_URL}" '
|
367 |
+
'width="100%" height="500px"></iframe>'
|
368 |
+
)
|
369 |
preview_html = gr.HTML(
|
370 |
+
value=iframe_url,
|
|
|
|
|
371 |
elem_id="preview-container",
|
372 |
)
|
373 |
|
|
|
582 |
[text_input, submit_btn],
|
583 |
)
|
584 |
|
585 |
+
# Load the preview iframe when the app starts
|
586 |
+
demo.load(fn=create_iframe_preview, outputs=[preview_html])
|
|
|
|
|
587 |
|
588 |
# Clean up on app close
|
589 |
+
demo.unload(stop_preview_app)
|
|
|
|
|
|
|
590 |
|
591 |
return demo
|
592 |
|
|
|
625 |
|
626 |
agent = KISSAgent()
|
627 |
|
628 |
+
# Start the preview app automatically when the main app starts
|
629 |
+
print("🚀 Starting preview app automatically...")
|
630 |
+
success, message = start_preview_app()
|
631 |
+
if success:
|
632 |
+
print(f"✅ Preview app started: {message}")
|
633 |
+
else:
|
634 |
+
print(f"❌ Failed to start preview app: {message}")
|
635 |
+
|
636 |
+
# Main app runs on internal port 7862, nginx proxies from 7860
|
637 |
+
port = 7862
|
638 |
|
639 |
GradioUI(agent).launch(share=False, server_name="0.0.0.0", server_port=port)
|
nginx.conf
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Basic nginx configuration for running as non-root user
|
2 |
+
worker_processes auto;
|
3 |
+
pid /var/run/nginx/nginx.pid;
|
4 |
+
error_log /var/log/nginx/error.log;
|
5 |
+
|
6 |
+
events {
|
7 |
+
worker_connections 768;
|
8 |
+
}
|
9 |
+
|
10 |
+
http {
|
11 |
+
include /etc/nginx/mime.types;
|
12 |
+
default_type application/octet-stream;
|
13 |
+
|
14 |
+
# Temp directories that user can write to
|
15 |
+
client_body_temp_path /var/lib/nginx/body;
|
16 |
+
proxy_temp_path /var/lib/nginx/proxy;
|
17 |
+
fastcgi_temp_path /var/lib/nginx/fastcgi;
|
18 |
+
uwsgi_temp_path /var/lib/nginx/uwsgi;
|
19 |
+
scgi_temp_path /var/lib/nginx/scgi;
|
20 |
+
|
21 |
+
access_log /var/log/nginx/access.log;
|
22 |
+
|
23 |
+
server {
|
24 |
+
listen 7860 default_server;
|
25 |
+
listen [::]:7860 default_server;
|
26 |
+
|
27 |
+
server_name _;
|
28 |
+
|
29 |
+
# Main Gradio app - serve on root path, proxy to internal port 7862
|
30 |
+
location / {
|
31 |
+
proxy_pass http://localhost:7862;
|
32 |
+
proxy_http_version 1.1;
|
33 |
+
proxy_set_header Upgrade $http_upgrade;
|
34 |
+
proxy_set_header Connection 'upgrade';
|
35 |
+
proxy_set_header Host $host;
|
36 |
+
proxy_set_header X-Real-IP $remote_addr;
|
37 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
38 |
+
proxy_set_header X-Forwarded-Host $host;
|
39 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
40 |
+
proxy_cache_bypass $http_upgrade;
|
41 |
+
proxy_read_timeout 86400;
|
42 |
+
proxy_redirect off;
|
43 |
+
}
|
44 |
+
|
45 |
+
# Preview apps - route to internal port 7861
|
46 |
+
location /preview/ {
|
47 |
+
# Remove /preview prefix and pass to the sandbox app
|
48 |
+
rewrite /preview/(.*) /$1 break;
|
49 |
+
proxy_pass http://localhost:7861;
|
50 |
+
proxy_http_version 1.1;
|
51 |
+
proxy_set_header Upgrade $http_upgrade;
|
52 |
+
proxy_set_header Connection 'upgrade';
|
53 |
+
proxy_set_header Host $host;
|
54 |
+
proxy_set_header X-Real-IP $remote_addr;
|
55 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
56 |
+
proxy_set_header X-Forwarded-Host $host;
|
57 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
58 |
+
proxy_cache_bypass $http_upgrade;
|
59 |
+
proxy_read_timeout 86400;
|
60 |
+
proxy_redirect off;
|
61 |
+
}
|
62 |
+
}
|
63 |
+
}
|
start.sh
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Start nginx in background as non-root user
|
4 |
+
nginx -g 'daemon off;' &
|
5 |
+
NGINX_PID=$!
|
6 |
+
|
7 |
+
# Wait a moment for nginx to start
|
8 |
+
sleep 2
|
9 |
+
|
10 |
+
# Start the main Gradio app on port 7862 (internal)
|
11 |
+
python app.py --server-port 7862 --server-name 0.0.0.0 &
|
12 |
+
APP_PID=$!
|
13 |
+
|
14 |
+
# Function to handle shutdown
|
15 |
+
cleanup() {
|
16 |
+
echo "Shutting down..."
|
17 |
+
kill $NGINX_PID $APP_PID 2>/dev/null
|
18 |
+
wait $NGINX_PID $APP_PID 2>/dev/null
|
19 |
+
exit 0
|
20 |
+
}
|
21 |
+
|
22 |
+
# Set up signal handlers
|
23 |
+
trap cleanup SIGTERM SIGINT
|
24 |
+
|
25 |
+
# Wait for any process to exit
|
26 |
+
wait -n
|
27 |
+
|
28 |
+
# If we get here, one process exited, so clean up
|
29 |
+
cleanup
|