v2.2.1 — Fix ComfyUI integration: httpx, correct checkpoint, output path
- Use httpx instead of urllib for ComfyUI API calls - Fix checkpoint name: NoobAI-XL-v1.1.safetensors - Fix output directory: ~/comfy/ComfyUI/output/ - Mara portrait generated successfully (507KB PNG)
This commit is contained in:
+17
-34
@@ -2,24 +2,24 @@
|
|||||||
NeonChat ComfyUI Integration — Profilbild-Generierung für Characters
|
NeonChat ComfyUI Integration — Profilbild-Generierung für Characters
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json, os, time, urllib.request, urllib.error
|
import json, os, time, shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import httpx
|
||||||
|
|
||||||
COMFYUI_URL = os.environ.get("COMFYUI_URL", "http://localhost:8188")
|
COMFYUI_URL = os.environ.get("COMFYUI_URL", "http://localhost:8188")
|
||||||
COMFYUI_OUTPUT_DIR = Path(os.environ.get("COMFYUI_OUTPUT", str(Path.home() / "comfy" / "output")))
|
COMFYUI_OUTPUT_DIR = Path(os.environ.get("COMFYUI_OUTPUT", str(Path.home() / "comfy" / "ComfyUI" / "output")))
|
||||||
CHARACTERS_DIR = Path(__file__).parent / "characters"
|
CHARACTERS_DIR = Path(__file__).parent / "characters"
|
||||||
AVATARS_DIR = Path(__file__).parent / "static" / "avatars"
|
AVATARS_DIR = Path(__file__).parent / "static" / "avatars"
|
||||||
|
|
||||||
def is_comfyui_running() -> bool:
|
def is_comfyui_running() -> bool:
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(f"{COMFYUI_URL}/system_stats", method="GET")
|
with httpx.Client(timeout=3) as client:
|
||||||
with urllib.request.urlopen(req, timeout=3) as resp:
|
resp = client.get(f"{COMFYUI_URL}/system_stats")
|
||||||
return resp.status == 200
|
return resp.status_code == 200
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def build_portrait_workflow(prompt: str, negative: str = "", width: int = 512, height: int = 512) -> dict:
|
def build_portrait_workflow(prompt: str, negative: str = "", width: int = 512, height: int = 512) -> dict:
|
||||||
"""Baut einen ComfyUI Workflow für ein Porträtfoto."""
|
|
||||||
return {
|
return {
|
||||||
"3": {
|
"3": {
|
||||||
"class_type": "KSampler",
|
"class_type": "KSampler",
|
||||||
@@ -38,7 +38,7 @@ def build_portrait_workflow(prompt: str, negative: str = "", width: int = 512, h
|
|||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
"class_type": "CheckpointLoaderSimple",
|
"class_type": "CheckpointLoaderSimple",
|
||||||
"inputs": {"ckpt_name": "noobaiXLNAIFP_vPred10Version.safetensors"}
|
"inputs": {"ckpt_name": "NoobAI-XL-v1.1.safetensors"}
|
||||||
},
|
},
|
||||||
"5": {
|
"5": {
|
||||||
"class_type": "EmptyLatentImage",
|
"class_type": "EmptyLatentImage",
|
||||||
@@ -69,29 +69,20 @@ def build_portrait_workflow(prompt: str, negative: str = "", width: int = 512, h
|
|||||||
}
|
}
|
||||||
|
|
||||||
def queue_prompt(workflow: dict) -> str:
|
def queue_prompt(workflow: dict) -> str:
|
||||||
"""Schickt den Workflow an ComfyUI und gibt die Prompt-ID zurück."""
|
with httpx.Client(timeout=10) as client:
|
||||||
data = json.dumps({"prompt": workflow}).encode("utf-8")
|
resp = client.post(f"{COMFYUI_URL}/prompt", json={"prompt": workflow})
|
||||||
req = urllib.request.Request(
|
result = resp.json()
|
||||||
f"{COMFYUI_URL}/prompt",
|
|
||||||
data=data,
|
|
||||||
headers={"Content-Type": "application/json"},
|
|
||||||
method="POST"
|
|
||||||
)
|
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
||||||
result = json.loads(resp.read())
|
|
||||||
return result.get("prompt_id", "")
|
return result.get("prompt_id", "")
|
||||||
|
|
||||||
def get_history(prompt_id: str) -> dict:
|
def get_history(prompt_id: str) -> dict:
|
||||||
"""Holt den Verlauf eines Prompts um das fertige Bild zu finden."""
|
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request(f"{COMFYUI_URL}/history/{prompt_id}", method="GET")
|
with httpx.Client(timeout=5) as client:
|
||||||
with urllib.request.urlopen(req, timeout=5) as resp:
|
resp = client.get(f"{COMFYUI_URL}/history/{prompt_id}")
|
||||||
return json.loads(resp.read())
|
return resp.json()
|
||||||
except:
|
except:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def wait_for_image(prompt_id: str, timeout: int = 120) -> str | None:
|
def wait_for_image(prompt_id: str, timeout: int = 180) -> str | None:
|
||||||
"""Wartet bis das Bild fertig ist und gibt den Dateinamen zurück."""
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
while time.time() - start < timeout:
|
while time.time() - start < timeout:
|
||||||
history = get_history(prompt_id)
|
history = get_history(prompt_id)
|
||||||
@@ -101,20 +92,15 @@ def wait_for_image(prompt_id: str, timeout: int = 120) -> str | None:
|
|||||||
images = outputs["9"].get("images", [])
|
images = outputs["9"].get("images", [])
|
||||||
if images:
|
if images:
|
||||||
return images[0].get("filename", "")
|
return images[0].get("filename", "")
|
||||||
time.sleep(2)
|
time.sleep(3)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def generate_portrait(character_name: str, description: str) -> str | None:
|
def generate_portrait(character_name: str, description: str) -> str | None:
|
||||||
"""
|
|
||||||
Generiert ein Profilbild für einen Character via ComfyUI.
|
|
||||||
Gibt den Pfad zur gespeicherten Datei zurück, oder None bei Fehler.
|
|
||||||
"""
|
|
||||||
if not is_comfyui_running():
|
if not is_comfyui_running():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
AVATARS_DIR.mkdir(parents=True, exist_ok=True)
|
AVATARS_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Build prompt from character description
|
|
||||||
prompt = f"anime style, {description}, simple background, headshot"
|
prompt = f"anime style, {description}, simple background, headshot"
|
||||||
negative = "blurry, deformed, ugly, realistic"
|
negative = "blurry, deformed, ugly, realistic"
|
||||||
|
|
||||||
@@ -125,22 +111,19 @@ def generate_portrait(character_name: str, description: str) -> str | None:
|
|||||||
if not prompt_id:
|
if not prompt_id:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
filename = wait_for_image(prompt_id, timeout=120)
|
filename = wait_for_image(prompt_id, timeout=180)
|
||||||
if not filename:
|
if not filename:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Find the image in ComfyUI output
|
|
||||||
comfy_output = COMFYUI_OUTPUT_DIR / filename
|
comfy_output = COMFYUI_OUTPUT_DIR / filename
|
||||||
if not comfy_output.exists():
|
if not comfy_output.exists():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Copy to avatars dir
|
|
||||||
safe_name = character_name.lower().replace(" ", "_")
|
safe_name = character_name.lower().replace(" ", "_")
|
||||||
dest = AVATARS_DIR / f"{safe_name}.png"
|
dest = AVATARS_DIR / f"{safe_name}.png"
|
||||||
import shutil
|
|
||||||
shutil.copy2(str(comfy_output), str(dest))
|
shutil.copy2(str(comfy_output), str(dest))
|
||||||
|
|
||||||
return f"/static/avatars/{safe_name}.png"
|
return f"/static/avatars/{safe_name}.png"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ComfyUI error: {e}")
|
print(f"ComfyUI error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 496 KiB |
Reference in New Issue
Block a user