Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,620 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
ComfyUI Hugging Face Space
|
4 |
+
Versão otimizada para execução gratuita no Hugging Face Spaces
|
5 |
+
"""
|
6 |
+
|
7 |
+
import subprocess
|
8 |
+
import os
|
9 |
+
import time
|
10 |
+
import socket
|
11 |
+
import threading
|
12 |
+
import logging
|
13 |
+
import json
|
14 |
+
import gradio as gr
|
15 |
+
from pathlib import Path
|
16 |
+
from typing import Dict, List, Optional, Any
|
17 |
+
import psutil
|
18 |
+
import signal
|
19 |
+
import sys
|
20 |
+
import requests
|
21 |
+
from datetime import datetime
|
22 |
+
|
23 |
+
# Configuração de logging
|
24 |
+
logging.basicConfig(
|
25 |
+
level=logging.INFO,
|
26 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
27 |
+
handlers=[
|
28 |
+
logging.StreamHandler(sys.stdout),
|
29 |
+
logging.FileHandler('/tmp/comfyui.log')
|
30 |
+
]
|
31 |
+
)
|
32 |
+
logger = logging.getLogger(__name__)
|
33 |
+
|
34 |
+
class ComfyUISpaceManager:
|
35 |
+
"""Gerenciador principal do ComfyUI no Hugging Face Space"""
|
36 |
+
|
37 |
+
def __init__(self):
|
38 |
+
self.app_dir = Path("/tmp/ComfyUI")
|
39 |
+
self.models_dir = Path("/data/models") # Persistente no HF Spaces
|
40 |
+
self.port = 7860 # Porta padrão do Gradio
|
41 |
+
self.comfy_port = 8188 # Porta do ComfyUI
|
42 |
+
self.process = None
|
43 |
+
self.setup_complete = False
|
44 |
+
self.setup_logs = []
|
45 |
+
|
46 |
+
# Cria diretórios necessários
|
47 |
+
self.app_dir.mkdir(parents=True, exist_ok=True)
|
48 |
+
self.models_dir.mkdir(parents=True, exist_ok=True)
|
49 |
+
|
50 |
+
def log_setup(self, message: str, level: str = "info"):
|
51 |
+
"""Adiciona mensagem aos logs de setup"""
|
52 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
53 |
+
log_entry = f"[{timestamp}] {message}"
|
54 |
+
self.setup_logs.append(log_entry)
|
55 |
+
|
56 |
+
if level == "info":
|
57 |
+
logger.info(message)
|
58 |
+
elif level == "warning":
|
59 |
+
logger.warning(message)
|
60 |
+
elif level == "error":
|
61 |
+
logger.error(message)
|
62 |
+
|
63 |
+
# Mantém apenas últimas 100 entradas
|
64 |
+
if len(self.setup_logs) > 100:
|
65 |
+
self.setup_logs = self.setup_logs[-100:]
|
66 |
+
|
67 |
+
def get_hf_token(self) -> str:
|
68 |
+
"""Obtém token do Hugging Face"""
|
69 |
+
token = os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_TOKEN")
|
70 |
+
if not token:
|
71 |
+
self.log_setup("⚠️ Token HF não encontrado, alguns modelos podem não baixar", "warning")
|
72 |
+
return ""
|
73 |
+
self.log_setup(f"✅ Token HF configurado: ...{token[-8:]}")
|
74 |
+
return token
|
75 |
+
|
76 |
+
def setup_comfyui(self) -> bool:
|
77 |
+
"""Setup completo do ComfyUI"""
|
78 |
+
try:
|
79 |
+
self.log_setup("🚀 Iniciando setup do ComfyUI...")
|
80 |
+
|
81 |
+
# 1. Clona repositório
|
82 |
+
if not self._clone_repository():
|
83 |
+
return False
|
84 |
+
|
85 |
+
# 2. Instala dependências
|
86 |
+
if not self._install_dependencies():
|
87 |
+
return False
|
88 |
+
|
89 |
+
# 3. Setup de modelos
|
90 |
+
if not self._setup_models():
|
91 |
+
return False
|
92 |
+
|
93 |
+
# 4. Configura custom nodes
|
94 |
+
if not self._setup_custom_nodes():
|
95 |
+
return False
|
96 |
+
|
97 |
+
self.log_setup("✅ Setup completo! ComfyUI pronto para usar")
|
98 |
+
self.setup_complete = True
|
99 |
+
return True
|
100 |
+
|
101 |
+
except Exception as e:
|
102 |
+
self.log_setup(f"❌ Erro no setup: {e}", "error")
|
103 |
+
return False
|
104 |
+
|
105 |
+
def _clone_repository(self) -> bool:
|
106 |
+
"""Clona repositório do ComfyUI"""
|
107 |
+
if (self.app_dir / ".git").exists():
|
108 |
+
self.log_setup("📁 Repositório já existe, atualizando...")
|
109 |
+
try:
|
110 |
+
os.chdir(self.app_dir)
|
111 |
+
subprocess.run(["git", "pull"], check=True, capture_output=True)
|
112 |
+
return True
|
113 |
+
except subprocess.CalledProcessError as e:
|
114 |
+
self.log_setup(f"⚠️ Erro ao atualizar repositório: {e}", "warning")
|
115 |
+
# Continua mesmo se falhar
|
116 |
+
|
117 |
+
self.log_setup("📥 Clonando ComfyUI...")
|
118 |
+
try:
|
119 |
+
subprocess.run([
|
120 |
+
"git", "clone", "https://github.com/comfyanonymous/ComfyUI.git",
|
121 |
+
str(self.app_dir)
|
122 |
+
], check=True, capture_output=True)
|
123 |
+
self.log_setup("✅ ComfyUI clonado com sucesso")
|
124 |
+
return True
|
125 |
+
except subprocess.CalledProcessError as e:
|
126 |
+
self.log_setup(f"❌ Erro ao clonar ComfyUI: {e}", "error")
|
127 |
+
return False
|
128 |
+
|
129 |
+
def _install_dependencies(self) -> bool:
|
130 |
+
"""Instala dependências do ComfyUI"""
|
131 |
+
self.log_setup("📦 Instalando dependências...")
|
132 |
+
|
133 |
+
# Lista de dependências essenciais
|
134 |
+
dependencies = [
|
135 |
+
"torch>=2.0.0",
|
136 |
+
"torchvision",
|
137 |
+
"torchaudio",
|
138 |
+
"numpy<2.0",
|
139 |
+
"pillow",
|
140 |
+
"transformers>=4.28.1",
|
141 |
+
"accelerate",
|
142 |
+
"diffusers",
|
143 |
+
"huggingface-hub[hf_transfer]",
|
144 |
+
"safetensors>=0.4.2",
|
145 |
+
"aiohttp",
|
146 |
+
"psutil",
|
147 |
+
"opencv-python-headless"
|
148 |
+
]
|
149 |
+
|
150 |
+
for dep in dependencies:
|
151 |
+
try:
|
152 |
+
self.log_setup(f"📦 Instalando {dep}...")
|
153 |
+
subprocess.run([
|
154 |
+
"pip", "install", "--upgrade", dep
|
155 |
+
], check=True, capture_output=True)
|
156 |
+
except subprocess.CalledProcessError as e:
|
157 |
+
self.log_setup(f"⚠️ Falha ao instalar {dep}: {e}", "warning")
|
158 |
+
# Continua mesmo se falhar
|
159 |
+
|
160 |
+
self.log_setup("✅ Dependências instaladas")
|
161 |
+
return True
|
162 |
+
|
163 |
+
def _setup_models(self) -> bool:
|
164 |
+
"""Setup de modelos com cache persistente"""
|
165 |
+
self.log_setup("🤖 Configurando modelos...")
|
166 |
+
|
167 |
+
# Verifica se modelos já existem
|
168 |
+
models_exist = self._check_existing_models()
|
169 |
+
|
170 |
+
if models_exist:
|
171 |
+
self.log_setup("✅ Modelos já existem no cache, vinculando...")
|
172 |
+
self._link_models()
|
173 |
+
return True
|
174 |
+
|
175 |
+
# Download de modelos essenciais
|
176 |
+
self.log_setup("📥 Primeira execução - baixando modelos essenciais...")
|
177 |
+
self._download_essential_models()
|
178 |
+
|
179 |
+
return True
|
180 |
+
|
181 |
+
def _check_existing_models(self) -> bool:
|
182 |
+
"""Verifica se modelos já existem no storage persistente"""
|
183 |
+
essential_models = [
|
184 |
+
"checkpoints/flux1-schnell.safetensors",
|
185 |
+
"vae/ae.safetensors",
|
186 |
+
"clip/clip_l.safetensors"
|
187 |
+
]
|
188 |
+
|
189 |
+
for model_path in essential_models:
|
190 |
+
full_path = self.models_dir / model_path
|
191 |
+
if not full_path.exists() or full_path.stat().st_size < 1024*1024: # < 1MB
|
192 |
+
return False
|
193 |
+
|
194 |
+
return True
|
195 |
+
|
196 |
+
def _link_models(self):
|
197 |
+
"""Vincula modelos do storage persistente ao ComfyUI"""
|
198 |
+
comfy_models = self.app_dir / "models"
|
199 |
+
|
200 |
+
# Remove diretório de modelos se existir
|
201 |
+
if comfy_models.exists():
|
202 |
+
import shutil
|
203 |
+
shutil.rmtree(comfy_models)
|
204 |
+
|
205 |
+
# Cria symlink para storage persistente
|
206 |
+
comfy_models.symlink_to(self.models_dir)
|
207 |
+
self.log_setup("🔗 Modelos vinculados ao storage persistente")
|
208 |
+
|
209 |
+
def _download_essential_models(self):
|
210 |
+
"""Download de modelos essenciais"""
|
211 |
+
token = self.get_hf_token()
|
212 |
+
|
213 |
+
# Modelos essenciais (versões menores para HF Spaces)
|
214 |
+
essential_models = [
|
215 |
+
{
|
216 |
+
"repo": "black-forest-labs/FLUX.1-schnell",
|
217 |
+
"file": "flux1-schnell.safetensors",
|
218 |
+
"dir": "checkpoints"
|
219 |
+
},
|
220 |
+
{
|
221 |
+
"repo": "black-forest-labs/FLUX.1-schnell",
|
222 |
+
"file": "ae.safetensors",
|
223 |
+
"dir": "vae"
|
224 |
+
},
|
225 |
+
{
|
226 |
+
"repo": "openai/clip-vit-large-patch14",
|
227 |
+
"file": "pytorch_model.bin",
|
228 |
+
"dir": "clip",
|
229 |
+
"rename": "clip_l.safetensors"
|
230 |
+
}
|
231 |
+
]
|
232 |
+
|
233 |
+
for model in essential_models:
|
234 |
+
self._download_hf_model(
|
235 |
+
model["repo"],
|
236 |
+
model["file"],
|
237 |
+
model["dir"],
|
238 |
+
token,
|
239 |
+
model.get("rename")
|
240 |
+
)
|
241 |
+
|
242 |
+
# LoRAs populares (menores)
|
243 |
+
self._download_popular_loras()
|
244 |
+
|
245 |
+
# Vincula modelos após download
|
246 |
+
self._link_models()
|
247 |
+
|
248 |
+
def _download_hf_model(self, repo: str, filename: str, target_dir: str, token: str, rename: str = None):
|
249 |
+
"""Download de modelo individual do HuggingFace"""
|
250 |
+
target_path = self.models_dir / target_dir
|
251 |
+
target_path.mkdir(parents=True, exist_ok=True)
|
252 |
+
|
253 |
+
final_name = rename if rename else filename
|
254 |
+
file_path = target_path / final_name
|
255 |
+
|
256 |
+
if file_path.exists():
|
257 |
+
self.log_setup(f"✅ {final_name} já existe")
|
258 |
+
return
|
259 |
+
|
260 |
+
self.log_setup(f"📥 Baixando {final_name} de {repo}...")
|
261 |
+
|
262 |
+
try:
|
263 |
+
from huggingface_hub import hf_hub_download
|
264 |
+
|
265 |
+
downloaded_path = hf_hub_download(
|
266 |
+
repo_id=repo,
|
267 |
+
filename=filename,
|
268 |
+
token=token if token else None,
|
269 |
+
cache_dir="/tmp/hf_cache"
|
270 |
+
)
|
271 |
+
|
272 |
+
# Copia para storage persistente
|
273 |
+
import shutil
|
274 |
+
shutil.copy2(downloaded_path, file_path)
|
275 |
+
|
276 |
+
size_mb = file_path.stat().st_size / (1024*1024)
|
277 |
+
self.log_setup(f"✅ {final_name} baixado ({size_mb:.1f}MB)")
|
278 |
+
|
279 |
+
except Exception as e:
|
280 |
+
self.log_setup(f"⚠️ Erro ao baixar {final_name}: {e}", "warning")
|
281 |
+
|
282 |
+
def _download_popular_loras(self):
|
283 |
+
"""Download de LoRAs populares e pequenos"""
|
284 |
+
loras_dir = self.models_dir / "loras"
|
285 |
+
loras_dir.mkdir(exist_ok=True)
|
286 |
+
|
287 |
+
# LoRAs pequenos e populares
|
288 |
+
popular_loras = [
|
289 |
+
"https://huggingface.co/alvdansen/pony-realism/resolve/main/ponyRealismV21MainVAE.safetensors",
|
290 |
+
"https://huggingface.co/Hyper-SD/Hyper-FLUX.1-dev-8steps-lora/resolve/main/Hyper-FLUX.1-dev-8steps-lora.safetensors"
|
291 |
+
]
|
292 |
+
|
293 |
+
for url in popular_loras:
|
294 |
+
try:
|
295 |
+
filename = url.split("/")[-1]
|
296 |
+
file_path = loras_dir / filename
|
297 |
+
|
298 |
+
if file_path.exists():
|
299 |
+
continue
|
300 |
+
|
301 |
+
self.log_setup(f"📥 Baixando LoRA {filename}...")
|
302 |
+
subprocess.run([
|
303 |
+
"wget", "-q", "--timeout=60", url, "-O", str(file_path)
|
304 |
+
], check=True)
|
305 |
+
|
306 |
+
if file_path.stat().st_size > 1024: # > 1KB
|
307 |
+
self.log_setup(f"✅ LoRA {filename} baixado")
|
308 |
+
else:
|
309 |
+
file_path.unlink()
|
310 |
+
|
311 |
+
except Exception as e:
|
312 |
+
self.log_setup(f"⚠️ Erro ao baixar LoRA: {e}", "warning")
|
313 |
+
|
314 |
+
def _setup_custom_nodes(self) -> bool:
|
315 |
+
"""Setup de custom nodes essenciais"""
|
316 |
+
self.log_setup("🔧 Configurando custom nodes...")
|
317 |
+
|
318 |
+
custom_nodes_dir = self.app_dir / "custom_nodes"
|
319 |
+
custom_nodes_dir.mkdir(exist_ok=True)
|
320 |
+
|
321 |
+
# Custom nodes essenciais e leves
|
322 |
+
essential_nodes = [
|
323 |
+
"https://github.com/ltdrdata/ComfyUI-Manager.git",
|
324 |
+
"https://github.com/WASasquatch/was-node-suite-comfyui.git"
|
325 |
+
]
|
326 |
+
|
327 |
+
for repo_url in essential_nodes:
|
328 |
+
try:
|
329 |
+
repo_name = repo_url.split("/")[-1].replace(".git", "")
|
330 |
+
node_dir = custom_nodes_dir / repo_name
|
331 |
+
|
332 |
+
if node_dir.exists():
|
333 |
+
self.log_setup(f"✅ {repo_name} já existe")
|
334 |
+
continue
|
335 |
+
|
336 |
+
self.log_setup(f"📥 Clonando {repo_name}...")
|
337 |
+
subprocess.run([
|
338 |
+
"git", "clone", "--depth", "1", repo_url, str(node_dir)
|
339 |
+
], check=True, capture_output=True, timeout=120)
|
340 |
+
|
341 |
+
# Instala requirements se existir
|
342 |
+
req_file = node_dir / "requirements.txt"
|
343 |
+
if req_file.exists():
|
344 |
+
subprocess.run([
|
345 |
+
"pip", "install", "-r", str(req_file)
|
346 |
+
], check=False, capture_output=True, timeout=120)
|
347 |
+
|
348 |
+
self.log_setup(f"✅ {repo_name} instalado")
|
349 |
+
|
350 |
+
except Exception as e:
|
351 |
+
self.log_setup(f"⚠️ Erro ao instalar {repo_name}: {e}", "warning")
|
352 |
+
|
353 |
+
self.log_setup("✅ Custom nodes configurados")
|
354 |
+
return True
|
355 |
+
|
356 |
+
def start_comfyui(self) -> bool:
|
357 |
+
"""Inicia o servidor ComfyUI"""
|
358 |
+
if self.process and self.process.poll() is None:
|
359 |
+
self.log_setup("✅ ComfyUI já está rodando")
|
360 |
+
return True
|
361 |
+
|
362 |
+
self.log_setup("🚀 Iniciando servidor ComfyUI...")
|
363 |
+
|
364 |
+
try:
|
365 |
+
os.chdir(self.app_dir)
|
366 |
+
|
367 |
+
# Inicia ComfyUI em background
|
368 |
+
self.process = subprocess.Popen([
|
369 |
+
"python", "main.py",
|
370 |
+
"--listen", "0.0.0.0",
|
371 |
+
"--port", str(self.comfy_port),
|
372 |
+
"--preview-method", "auto",
|
373 |
+
"--dont-print-server"
|
374 |
+
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
375 |
+
|
376 |
+
# Aguarda servidor ficar pronto
|
377 |
+
for attempt in range(60): # 60 tentativas = 2 minutos
|
378 |
+
if self._test_comfyui_connection():
|
379 |
+
self.log_setup("✅ ComfyUI está rodando!")
|
380 |
+
return True
|
381 |
+
|
382 |
+
if self.process.poll() is not None:
|
383 |
+
stdout, stderr = self.process.communicate()
|
384 |
+
self.log_setup(f"❌ ComfyUI parou: {stderr}", "error")
|
385 |
+
return False
|
386 |
+
|
387 |
+
time.sleep(2)
|
388 |
+
|
389 |
+
self.log_setup("❌ Timeout aguardando ComfyUI", "error")
|
390 |
+
return False
|
391 |
+
|
392 |
+
except Exception as e:
|
393 |
+
self.log_setup(f"❌ Erro ao iniciar ComfyUI: {e}", "error")
|
394 |
+
return False
|
395 |
+
|
396 |
+
def _test_comfyui_connection(self) -> bool:
|
397 |
+
"""Testa conexão com ComfyUI"""
|
398 |
+
try:
|
399 |
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
400 |
+
sock.settimeout(1)
|
401 |
+
return sock.connect_ex(('127.0.0.1', self.comfy_port)) == 0
|
402 |
+
except:
|
403 |
+
return False
|
404 |
+
|
405 |
+
def get_status(self) -> Dict[str, Any]:
|
406 |
+
"""Retorna status do sistema"""
|
407 |
+
return {
|
408 |
+
"setup_complete": self.setup_complete,
|
409 |
+
"comfyui_running": self.process and self.process.poll() is None,
|
410 |
+
"comfyui_url": f"http://127.0.0.1:{self.comfy_port}" if self.setup_complete else None,
|
411 |
+
"logs": self.setup_logs[-20:], # Últimas 20 linhas
|
412 |
+
"memory_usage": psutil.virtual_memory().percent,
|
413 |
+
"disk_usage": psutil.disk_usage('/').percent if os.path.exists('/') else 0,
|
414 |
+
"models_dir_size": self._get_dir_size(self.models_dir)
|
415 |
+
}
|
416 |
+
|
417 |
+
def _get_dir_size(self, path: Path) -> str:
|
418 |
+
"""Retorna tamanho do diretório formatado"""
|
419 |
+
try:
|
420 |
+
total_size = sum(f.stat().st_size for f in path.glob('**/*') if f.is_file())
|
421 |
+
size_gb = total_size / (1024**3)
|
422 |
+
return f"{size_gb:.2f} GB"
|
423 |
+
except:
|
424 |
+
return "N/A"
|
425 |
+
|
426 |
+
def stop_comfyui(self):
|
427 |
+
"""Para o ComfyUI"""
|
428 |
+
if self.process:
|
429 |
+
self.log_setup("🛑 Parando ComfyUI...")
|
430 |
+
self.process.terminate()
|
431 |
+
try:
|
432 |
+
self.process.wait(timeout=10)
|
433 |
+
except subprocess.TimeoutExpired:
|
434 |
+
self.process.kill()
|
435 |
+
self.process = None
|
436 |
+
|
437 |
+
# Instância global
|
438 |
+
comfy_manager = ComfyUISpaceManager()
|
439 |
+
|
440 |
+
def setup_comfyui():
|
441 |
+
"""Interface para setup do ComfyUI"""
|
442 |
+
if comfy_manager.setup_complete:
|
443 |
+
return "✅ Setup já completo!", comfy_manager.get_status()
|
444 |
+
|
445 |
+
# Executa setup em thread separada
|
446 |
+
def run_setup():
|
447 |
+
comfy_manager.setup_comfyui()
|
448 |
+
comfy_manager.start_comfyui()
|
449 |
+
|
450 |
+
setup_thread = threading.Thread(target=run_setup, daemon=True)
|
451 |
+
setup_thread.start()
|
452 |
+
|
453 |
+
return "🚀 Setup iniciado! Acompanhe o progresso abaixo...", comfy_manager.get_status()
|
454 |
+
|
455 |
+
def get_status_update():
|
456 |
+
"""Atualização de status para interface"""
|
457 |
+
status = comfy_manager.get_status()
|
458 |
+
|
459 |
+
# Formata logs
|
460 |
+
logs_text = "\n".join(status["logs"]) if status["logs"] else "Aguardando logs..."
|
461 |
+
|
462 |
+
# Status geral
|
463 |
+
if status["setup_complete"] and status["comfyui_running"]:
|
464 |
+
main_status = "🟢 ComfyUI Rodando - Pronto para usar!"
|
465 |
+
comfyui_link = f'<a href="http://127.0.0.1:{comfy_manager.comfy_port}" target="_blank">🎯 Abrir ComfyUI</a>'
|
466 |
+
elif status["setup_complete"]:
|
467 |
+
main_status = "🟡 Setup completo - Iniciando ComfyUI..."
|
468 |
+
comfyui_link = "⏳ Aguardando..."
|
469 |
+
else:
|
470 |
+
main_status = "🟡 Setup em progresso..."
|
471 |
+
comfyui_link = "⏳ Aguardando setup..."
|
472 |
+
|
473 |
+
# Informações do sistema
|
474 |
+
system_info = f"""
|
475 |
+
💾 Memória: {status['memory_usage']:.1f}%
|
476 |
+
💿 Disco: {status['disk_usage']:.1f}%
|
477 |
+
📁 Modelos: {status['models_dir_size']}
|
478 |
+
"""
|
479 |
+
|
480 |
+
return main_status, logs_text, system_info, comfyui_link
|
481 |
+
|
482 |
+
def restart_comfyui():
|
483 |
+
"""Reinicia ComfyUI"""
|
484 |
+
comfy_manager.stop_comfyui()
|
485 |
+
time.sleep(2)
|
486 |
+
success = comfy_manager.start_comfyui()
|
487 |
+
|
488 |
+
if success:
|
489 |
+
return "✅ ComfyUI reiniciado com sucesso!"
|
490 |
+
else:
|
491 |
+
return "❌ Erro ao reiniciar ComfyUI"
|
492 |
+
|
493 |
+
# Interface Gradio
|
494 |
+
def create_interface():
|
495 |
+
"""Cria interface Gradio"""
|
496 |
+
|
497 |
+
with gr.Blocks(
|
498 |
+
title="ComfyUI Hugging Face Space",
|
499 |
+
theme=gr.themes.Soft(),
|
500 |
+
css="""
|
501 |
+
.status-box { padding: 20px; border-radius: 10px; margin: 10px 0; }
|
502 |
+
.running { background-color: #d4edda; border: 1px solid #c3e6cb; }
|
503 |
+
.setup { background-color: #fff3cd; border: 1px solid #ffeaa7; }
|
504 |
+
.error { background-color: #f8d7da; border: 1px solid #f5c6cb; }
|
505 |
+
"""
|
506 |
+
) as demo:
|
507 |
+
|
508 |
+
gr.Markdown("""
|
509 |
+
# 🚀 ComfyUI Hugging Face Space
|
510 |
+
|
511 |
+
**ComfyUI executando gratuitamente no Hugging Face Spaces!**
|
512 |
+
|
513 |
+
✅ GPU Tesla T4 gratuita
|
514 |
+
✅ Modelos persistentes
|
515 |
+
✅ Interface web completa
|
516 |
+
✅ Custom nodes incluídos
|
517 |
+
""")
|
518 |
+
|
519 |
+
with gr.Row():
|
520 |
+
with gr.Column(scale=2):
|
521 |
+
# Status principal
|
522 |
+
status_display = gr.Markdown("🟡 Aguardando inicialização...", elem_classes=["status-box", "setup"])
|
523 |
+
|
524 |
+
# Botões de controle
|
525 |
+
with gr.Row():
|
526 |
+
setup_btn = gr.Button("🚀 Iniciar Setup", variant="primary", scale=2)
|
527 |
+
restart_btn = gr.Button("🔄 Reiniciar", scale=1)
|
528 |
+
|
529 |
+
# Link para ComfyUI
|
530 |
+
comfyui_link = gr.HTML("⏳ Aguardando setup...")
|
531 |
+
|
532 |
+
# Informações do sistema
|
533 |
+
system_info = gr.Textbox(
|
534 |
+
label="📊 Informações do Sistema",
|
535 |
+
interactive=False,
|
536 |
+
lines=4
|
537 |
+
)
|
538 |
+
|
539 |
+
with gr.Column(scale=3):
|
540 |
+
# Logs em tempo real
|
541 |
+
logs_display = gr.Textbox(
|
542 |
+
label="📋 Logs do Setup",
|
543 |
+
lines=20,
|
544 |
+
max_lines=30,
|
545 |
+
interactive=False,
|
546 |
+
autoscroll=True
|
547 |
+
)
|
548 |
+
|
549 |
+
# Informações de ajuda
|
550 |
+
with gr.Accordion("📚 Como Usar", open=False):
|
551 |
+
gr.Markdown("""
|
552 |
+
### 🎯 Passos para usar:
|
553 |
+
|
554 |
+
1. **Clique em "Iniciar Setup"** - O sistema irá baixar e configurar tudo automaticamente
|
555 |
+
2. **Aguarde ~10-15 minutos** na primeira execução (downloads dos modelos)
|
556 |
+
3. **Clique em "Abrir ComfyUI"** quando aparecer o link
|
557 |
+
4. **Use normalmente** - Todos os dados ficam salvos automaticamente!
|
558 |
+
|
559 |
+
### 🔧 Recursos incluídos:
|
560 |
+
- **FLUX.1-schnell** - Modelo de geração rápida
|
561 |
+
- **VAE e CLIP** - Encoders necessários
|
562 |
+
- **LoRAs populares** - Para estilos específicos
|
563 |
+
- **ComfyUI Manager** - Para instalar novos nodes
|
564 |
+
- **WAS Node Suite** - Nodes úteis extras
|
565 |
+
|
566 |
+
### 💡 Dicas:
|
567 |
+
- ✅ Seus modelos ficam salvos entre sessões
|
568 |
+
- ✅ Pode fechar e abrir o Space sem perder dados
|
569 |
+
- ✅ Use "Reiniciar" se algo der errado
|
570 |
+
- ✅ Logs mostram o progresso detalhado
|
571 |
+
""")
|
572 |
+
|
573 |
+
# Event handlers
|
574 |
+
setup_btn.click(
|
575 |
+
fn=setup_comfyui,
|
576 |
+
outputs=[status_display, logs_display]
|
577 |
+
)
|
578 |
+
|
579 |
+
restart_btn.click(
|
580 |
+
fn=restart_comfyui,
|
581 |
+
outputs=[status_display]
|
582 |
+
)
|
583 |
+
|
584 |
+
# Auto-refresh de status a cada 3 segundos
|
585 |
+
demo.load(
|
586 |
+
fn=get_status_update,
|
587 |
+
outputs=[status_display, logs_display, system_info, comfyui_link],
|
588 |
+
every=3
|
589 |
+
)
|
590 |
+
|
591 |
+
return demo
|
592 |
+
|
593 |
+
# Tratamento de sinais para shutdown gracioso
|
594 |
+
def signal_handler(signum, frame):
|
595 |
+
"""Handler para shutdown gracioso"""
|
596 |
+
logger.info(f"Recebido sinal {signum}, parando ComfyUI...")
|
597 |
+
comfy_manager.stop_comfyui()
|
598 |
+
sys.exit(0)
|
599 |
+
|
600 |
+
signal.signal(signal.SIGINT, signal_handler)
|
601 |
+
signal.signal(signal.SIGTERM, signal_handler)
|
602 |
+
|
603 |
+
# Inicia interface
|
604 |
+
if __name__ == "__main__":
|
605 |
+
# Setup inicial em background se não foi feito
|
606 |
+
if not comfy_manager.setup_complete:
|
607 |
+
setup_thread = threading.Thread(
|
608 |
+
target=lambda: comfy_manager.setup_comfyui() and comfy_manager.start_comfyui(),
|
609 |
+
daemon=True
|
610 |
+
)
|
611 |
+
setup_thread.start()
|
612 |
+
|
613 |
+
# Cria e lança interface
|
614 |
+
demo = create_interface()
|
615 |
+
demo.launch(
|
616 |
+
server_name="0.0.0.0",
|
617 |
+
server_port=7860,
|
618 |
+
share=False, # HF Spaces já é público
|
619 |
+
inbrowser=False
|
620 |
+
)
|