Spaces:
Running
Running
Add Docker support with nginx and supervisor configuration, implement .dockerignore, and enhance local development script.
Browse files- .dockerignore +15 -0
- Dockerfile +32 -0
- README.md +26 -14
- app.py +39 -9
- nginx.conf +50 -0
- run_local.py +25 -0
- supervisord.conf +27 -0
.dockerignore
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.git
|
2 |
+
.gitignore
|
3 |
+
.venv
|
4 |
+
__pycache__
|
5 |
+
*.pyc
|
6 |
+
*.pyo
|
7 |
+
*.pyd
|
8 |
+
.Python
|
9 |
+
.pytest_cache
|
10 |
+
.coverage
|
11 |
+
.ruff_cache
|
12 |
+
.gradio
|
13 |
+
*.md
|
14 |
+
!README.md
|
15 |
+
run_local.py
|
Dockerfile
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.12-slim
|
2 |
+
|
3 |
+
# Install nginx and other dependencies
|
4 |
+
RUN apt-get update && apt-get install -y \
|
5 |
+
nginx \
|
6 |
+
supervisor \
|
7 |
+
&& rm -rf /var/lib/apt/lists/*
|
8 |
+
|
9 |
+
# Set working directory
|
10 |
+
WORKDIR /app
|
11 |
+
|
12 |
+
# Copy requirements and install Python dependencies
|
13 |
+
COPY requirements.txt .
|
14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
15 |
+
|
16 |
+
# Copy application code
|
17 |
+
COPY . .
|
18 |
+
|
19 |
+
# Copy nginx configuration
|
20 |
+
COPY nginx.conf /etc/nginx/nginx.conf
|
21 |
+
|
22 |
+
# Copy supervisor configuration
|
23 |
+
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
24 |
+
|
25 |
+
# Create necessary directories
|
26 |
+
RUN mkdir -p /var/log/supervisor
|
27 |
+
|
28 |
+
# Expose port 7860 (the only port HF Spaces allows)
|
29 |
+
EXPOSE 7860
|
30 |
+
|
31 |
+
# Start supervisor (which will start nginx, main app, and preview app)
|
32 |
+
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
README.md
CHANGED
@@ -1,19 +1,31 @@
|
|
1 |
---
|
2 |
-
title: Likable
|
3 |
emoji: 💗
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
-
sdk:
|
7 |
-
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
-
license: mit
|
11 |
-
short_description: 💗Likable - it's almost lovable
|
12 |
---
|
13 |
|
14 |
-
# 💗 Likable
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Likable GitHub
|
3 |
emoji: 💗
|
4 |
+
colorFrom: purple
|
5 |
+
colorTo: pink
|
6 |
+
sdk: docker
|
7 |
+
app_port: 7860
|
|
|
|
|
|
|
|
|
8 |
---
|
9 |
|
10 |
+
# 💗 Likable GitHub
|
11 |
|
12 |
+
A powerful AI coding assistant that can create and preview Gradio applications in real-time.
|
13 |
+
|
14 |
+
## Features
|
15 |
+
|
16 |
+
- **Real-time Code Generation**: AI agent that can write and modify code
|
17 |
+
- **Live Preview**: See your applications running instantly in an iframe
|
18 |
+
- **Multiple AI Providers**: Support for Anthropic, OpenAI, Mistral, and more
|
19 |
+
- **File Management**: Edit and save files directly in the interface
|
20 |
+
- **API Key Management**: Secure configuration for different AI providers
|
21 |
+
|
22 |
+
## Usage
|
23 |
+
|
24 |
+
1. Configure your API keys in the Settings tab
|
25 |
+
2. Ask the AI to create or modify applications
|
26 |
+
3. View the live preview in the Preview tab
|
27 |
+
4. Edit code directly in the Code tab
|
28 |
+
|
29 |
+
## Architecture
|
30 |
+
|
31 |
+
This Space uses nginx as a reverse proxy to serve both the main application and preview applications on a single port, making it compatible with Hugging Face Spaces' single-port limitation.
|
app.py
CHANGED
@@ -18,11 +18,13 @@ PREVIEW_PORT = 7861 # Different port from main app
|
|
18 |
|
19 |
def get_preview_url():
|
20 |
"""Get the appropriate preview URL based on environment."""
|
|
|
|
|
|
|
21 |
# Check if running in Docker
|
22 |
-
|
23 |
-
# In Docker
|
24 |
-
|
25 |
-
return f"http://{host_ip}:{PREVIEW_PORT}"
|
26 |
else:
|
27 |
# Local development
|
28 |
return f"http://localhost:{PREVIEW_PORT}"
|
@@ -64,6 +66,10 @@ def save_file(path, new_text):
|
|
64 |
|
65 |
def stop_preview_app():
|
66 |
"""Stop the preview app subprocess if it's running."""
|
|
|
|
|
|
|
|
|
67 |
global preview_process
|
68 |
if preview_process and preview_process.poll() is None:
|
69 |
print("🛑 Stopping preview app...")
|
@@ -90,6 +96,11 @@ def stop_preview_app():
|
|
90 |
|
91 |
def start_preview_app():
|
92 |
"""Start the preview app in a subprocess."""
|
|
|
|
|
|
|
|
|
|
|
93 |
global preview_process
|
94 |
|
95 |
# Stop any existing preview app
|
@@ -134,9 +145,12 @@ def start_preview_app():
|
|
134 |
max_retries = 10
|
135 |
for i in range(max_retries):
|
136 |
try:
|
137 |
-
response = requests.get(
|
138 |
if response.status_code == 200:
|
139 |
-
print(
|
|
|
|
|
|
|
140 |
return True, "App started successfully"
|
141 |
except requests.exceptions.RequestException:
|
142 |
print(
|
@@ -145,7 +159,10 @@ def start_preview_app():
|
|
145 |
pass
|
146 |
time.sleep(0.5)
|
147 |
|
148 |
-
print(
|
|
|
|
|
|
|
149 |
return True, "App started successfully"
|
150 |
|
151 |
except Exception as e:
|
@@ -173,12 +190,16 @@ def create_iframe_preview():
|
|
173 |
|
174 |
def is_preview_running():
|
175 |
"""Check if the preview app is running and accessible."""
|
|
|
|
|
|
|
|
|
176 |
global preview_process
|
177 |
if preview_process is None or preview_process.poll() is not None:
|
178 |
return False
|
179 |
|
180 |
try:
|
181 |
-
response = requests.get(
|
182 |
return response.status_code == 200
|
183 |
except requests.exceptions.RequestException:
|
184 |
return False
|
@@ -674,4 +695,13 @@ if __name__ == "__main__":
|
|
674 |
from kiss_agent import KISSAgent
|
675 |
|
676 |
agent = KISSAgent()
|
677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
def get_preview_url():
|
20 |
"""Get the appropriate preview URL based on environment."""
|
21 |
+
# For Hugging Face Spaces with nginx proxy, use the /preview/ path
|
22 |
+
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
23 |
+
return "/preview/"
|
24 |
# Check if running in Docker
|
25 |
+
elif os.path.exists("/.dockerenv") or os.getenv("DOCKER_CONTAINER"):
|
26 |
+
# In Docker with nginx proxy, use relative path
|
27 |
+
return "/preview/"
|
|
|
28 |
else:
|
29 |
# Local development
|
30 |
return f"http://localhost:{PREVIEW_PORT}"
|
|
|
66 |
|
67 |
def stop_preview_app():
|
68 |
"""Stop the preview app subprocess if it's running."""
|
69 |
+
# In HF Spaces, preview app runs as separate service, no need to manage subprocess
|
70 |
+
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
71 |
+
return
|
72 |
+
|
73 |
global preview_process
|
74 |
if preview_process and preview_process.poll() is None:
|
75 |
print("🛑 Stopping preview app...")
|
|
|
96 |
|
97 |
def start_preview_app():
|
98 |
"""Start the preview app in a subprocess."""
|
99 |
+
# In HF Spaces, preview app runs as separate service via supervisor
|
100 |
+
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
101 |
+
print("✅ Preview app managed by supervisor in HF Spaces")
|
102 |
+
return True, "Preview app running via supervisor"
|
103 |
+
|
104 |
global preview_process
|
105 |
|
106 |
# Stop any existing preview app
|
|
|
145 |
max_retries = 10
|
146 |
for i in range(max_retries):
|
147 |
try:
|
148 |
+
response = requests.get(f"http://localhost:{PREVIEW_PORT}", timeout=1)
|
149 |
if response.status_code == 200:
|
150 |
+
print(
|
151 |
+
f"✅ Preview app started successfully on \
|
152 |
+
localhost:{PREVIEW_PORT}"
|
153 |
+
)
|
154 |
return True, "App started successfully"
|
155 |
except requests.exceptions.RequestException:
|
156 |
print(
|
|
|
159 |
pass
|
160 |
time.sleep(0.5)
|
161 |
|
162 |
+
print(
|
163 |
+
f"⚠️ Preview app started but may not be fully ready. \
|
164 |
+
Check localhost:{PREVIEW_PORT}"
|
165 |
+
)
|
166 |
return True, "App started successfully"
|
167 |
|
168 |
except Exception as e:
|
|
|
190 |
|
191 |
def is_preview_running():
|
192 |
"""Check if the preview app is running and accessible."""
|
193 |
+
# In HF Spaces, assume preview app is always running via supervisor
|
194 |
+
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
195 |
+
return True
|
196 |
+
|
197 |
global preview_process
|
198 |
if preview_process is None or preview_process.poll() is not None:
|
199 |
return False
|
200 |
|
201 |
try:
|
202 |
+
response = requests.get(f"http://localhost:{PREVIEW_PORT}", timeout=2)
|
203 |
return response.status_code == 200
|
204 |
except requests.exceptions.RequestException:
|
205 |
return False
|
|
|
695 |
from kiss_agent import KISSAgent
|
696 |
|
697 |
agent = KISSAgent()
|
698 |
+
|
699 |
+
# Determine port based on environment
|
700 |
+
if os.getenv("SPACE_ID") or os.getenv("HF_SPACE"):
|
701 |
+
# In HF Spaces, run on 7862 (nginx will proxy to 7860)
|
702 |
+
port = 7862
|
703 |
+
else:
|
704 |
+
# Local development
|
705 |
+
port = 7860
|
706 |
+
|
707 |
+
GradioUI(agent).launch(share=False, server_name="0.0.0.0", server_port=port)
|
nginx.conf
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
events {
|
2 |
+
worker_connections 1024;
|
3 |
+
}
|
4 |
+
|
5 |
+
http {
|
6 |
+
upstream main_app {
|
7 |
+
server 127.0.0.1:7862;
|
8 |
+
}
|
9 |
+
|
10 |
+
upstream preview_app {
|
11 |
+
server 127.0.0.1:7861;
|
12 |
+
}
|
13 |
+
|
14 |
+
server {
|
15 |
+
listen 7860;
|
16 |
+
server_name localhost;
|
17 |
+
|
18 |
+
# Main app - all requests except /preview/*
|
19 |
+
location / {
|
20 |
+
proxy_pass http://main_app;
|
21 |
+
proxy_set_header Host $host;
|
22 |
+
proxy_set_header X-Real-IP $remote_addr;
|
23 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
24 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
25 |
+
|
26 |
+
# WebSocket support for Gradio
|
27 |
+
proxy_http_version 1.1;
|
28 |
+
proxy_set_header Upgrade $http_upgrade;
|
29 |
+
proxy_set_header Connection "upgrade";
|
30 |
+
proxy_read_timeout 86400;
|
31 |
+
}
|
32 |
+
|
33 |
+
# Preview app - all requests to /preview/*
|
34 |
+
location /preview/ {
|
35 |
+
# Remove /preview from the path when forwarding
|
36 |
+
rewrite ^/preview/(.*) /$1 break;
|
37 |
+
proxy_pass http://preview_app;
|
38 |
+
proxy_set_header Host $host;
|
39 |
+
proxy_set_header X-Real-IP $remote_addr;
|
40 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
41 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
42 |
+
|
43 |
+
# WebSocket support for Gradio
|
44 |
+
proxy_http_version 1.1;
|
45 |
+
proxy_set_header Upgrade $http_upgrade;
|
46 |
+
proxy_set_header Connection "upgrade";
|
47 |
+
proxy_read_timeout 86400;
|
48 |
+
}
|
49 |
+
}
|
50 |
+
}
|
run_local.py
ADDED
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Local development runner for Likable GitHub.
|
4 |
+
This script runs the app in local mode without Docker/nginx setup.
|
5 |
+
"""
|
6 |
+
|
7 |
+
import os
|
8 |
+
|
9 |
+
if __name__ == "__main__":
|
10 |
+
# Ensure we're not in HF Spaces mode for local development
|
11 |
+
if "SPACE_ID" in os.environ:
|
12 |
+
del os.environ["SPACE_ID"]
|
13 |
+
if "HF_SPACE" in os.environ:
|
14 |
+
del os.environ["HF_SPACE"]
|
15 |
+
|
16 |
+
# Run the main app
|
17 |
+
from app import GradioUI
|
18 |
+
from kiss_agent import KISSAgent
|
19 |
+
|
20 |
+
agent = KISSAgent()
|
21 |
+
print("🚀 Starting Likable GitHub in local development mode...")
|
22 |
+
print("📱 Main app will be available at: http://localhost:7860")
|
23 |
+
print("🔍 Preview apps will be available at: http://localhost:7861")
|
24 |
+
|
25 |
+
GradioUI(agent).launch(share=False, server_name="0.0.0.0", server_port=7860)
|
supervisord.conf
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[supervisord]
|
2 |
+
nodaemon=true
|
3 |
+
user=root
|
4 |
+
|
5 |
+
[program:nginx]
|
6 |
+
command=nginx -g "daemon off;"
|
7 |
+
autostart=true
|
8 |
+
autorestart=true
|
9 |
+
stdout_logfile=/var/log/supervisor/nginx.log
|
10 |
+
stderr_logfile=/var/log/supervisor/nginx.log
|
11 |
+
|
12 |
+
[program:main_app]
|
13 |
+
command=python app.py
|
14 |
+
directory=/app
|
15 |
+
autostart=true
|
16 |
+
autorestart=true
|
17 |
+
stdout_logfile=/var/log/supervisor/main_app.log
|
18 |
+
stderr_logfile=/var/log/supervisor/main_app.log
|
19 |
+
environment=GRADIO_SERVER_PORT="7862",GRADIO_SERVER_NAME="0.0.0.0"
|
20 |
+
|
21 |
+
[program:preview_app]
|
22 |
+
command=python sandbox/app.py --server-port 7861 --server-name 0.0.0.0
|
23 |
+
directory=/app
|
24 |
+
autostart=true
|
25 |
+
autorestart=true
|
26 |
+
stdout_logfile=/var/log/supervisor/preview_app.log
|
27 |
+
stderr_logfile=/var/log/supervisor/preview_app.log
|