Spaces:
Sleeping
Sleeping
jens.luecke commited on
Commit ·
fc46e81
1
Parent(s): 13080f1
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
|