Files
natiris/bridges/ComfyBridge.py

223 lines
8.1 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
ComfyBridge Working Funktionierende IPAdapter Integration
"""
import json
import os
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
import requests
PATHS = {
"state": os.path.expanduser("~/natiris/core/natiris_full_state.json"),
"output_dir": os.path.expanduser("~/natiris/generated"),
"base_images": os.path.expanduser("~/natiris/assets/base_images"),
}
COMFY_API = "http://localhost:8188"
TRUST_MAP = [
{"range": [0, 3], "style": "neutral_portrait", "lighting": "professional cold", "ipadapter_weight": 0.4, "distance": "medium"},
{"range": [4, 7], "style": "personal_context", "lighting": "warm golden", "ipadapter_weight": 0.6, "distance": "medium-close"},
{"range": [8, 10], "style": "intimate", "lighting": "intimate soft", "ipadapter_weight": 0.8, "distance": "close"},
]
class ComfyBridgeWorking:
def __init__(self):
self.output_dir = Path(PATHS["output_dir"])
self.output_dir.mkdir(exist_ok=True)
self.client_id = f"natiris_{uuid.uuid4().hex[:6]}"
def check_health(self):
try:
resp = requests.get(f"{COMFY_API}/system_stats", timeout=5)
return {"reachable": True, "version": resp.json()["system"]["comfyui_version"]}
except Exception as e:
return {"reachable": False, "error": str(e)}
def get_style_config(self, trust):
for entry in TRUST_MAP:
if entry["range"][0] <= trust <= entry["range"][1]:
return entry
return TRUST_MAP[1]
def build_prompt(self, trust=7.0, mood=5):
style = self.get_style_config(trust)
positive = (
f"portrait of young woman, {style['lighting']}, "
f"{style['distance']} shot, mood {mood}/10, "
"beautiful, consistent face, realistic, 8k"
)
negative = "ugly, deformed, blurry, low quality, extra limbs"
return {
"positive": positive,
"negative": negative,
"style": style["style"],
"trust": trust,
"ipadapter_weight": style["ipadapter_weight"],
"width": 512,
"height": 768 if trust > 7 else 512,
}
def build_basic_workflow(self, prompt_data):
"""Einfacher Workflow ohne IPAdapter"""
seed = int(time.time() * 1000) % 2147483647
return {
"1": {"inputs": {"text": prompt_data["positive"], "clip": ["4", 1]}, "class_type": "CLIPTextEncode"},
"2": {"inputs": {"text": prompt_data["negative"], "clip": ["4", 1]}, "class_type": "CLIPTextEncode"},
"3": {
"inputs": {
"seed": seed,
"steps": 25,
"cfg": 7.0,
"sampler_name": "euler_ancestral",
"scheduler": "karras",
"denoise": 1.0,
"model": ["4", 0],
"positive": ["1", 0],
"negative": ["2", 0],
"latent_image": ["5", 0]
},
"class_type": "KSampler"
},
"4": {"inputs": {"ckpt_name": "realisticVisionV60B1_v51HyperVAE.safetensors"}, "class_type": "CheckpointLoaderSimple"},
"5": {"inputs": {"width": prompt_data["width"], "height": prompt_data["height"], "batch_size": 1}, "class_type": "EmptyLatentImage"},
"6": {"inputs": {"samples": ["3", 0], "vae": ["4", 2]}, "class_type": "VAEDecode"},
"7": {"inputs": {"filename_prefix": f"natiris_{prompt_data['style']}", "images": ["6", 0]}, "class_type": "SaveImage"},
}
def submit_and_wait(self, workflow):
"""Sendet Workflow und wartet auf Ergebnis"""
# Submit
data = {"prompt": workflow, "client_id": self.client_id}
resp = requests.post(f"{COMFY_API}/prompt", json=data, timeout=10)
result = resp.json()
if "prompt_id" not in result:
return {"success": False, "error": result.get("error", "Submit failed")}
prompt_id = result["prompt_id"]
print(f"⏳ Generating... (ID: {prompt_id[:8]})")
# Warten (simpler Polling)
for _ in range(300): # max 5 Min
time.sleep(1)
try:
history = requests.get(f"{COMFY_API}/history", timeout=5).json()
if prompt_id in history:
return {"success": True, "data": history[prompt_id], "prompt_id": prompt_id}
except:
continue
return {"success": False, "error": "Timeout"}
def save_image(self, history_data, prompt_data):
"""Speichert generiertes Bild"""
outputs = history_data.get("outputs", {})
for node_id, node_out in outputs.items():
if "images" in node_out:
for img in node_out["images"]:
try:
params = {
"filename": img["filename"],
"subfolder": img.get("subfolder", ""),
"type": "output"
}
resp = requests.get(f"{COMFY_API}/view", params=params, timeout=30)
if resp.status_code == 200:
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"natiris_{prompt_data['style']}_{ts}.png"
filepath = self.output_dir / filename
with open(filepath, "wb") as f:
f.write(resp.content)
return {
"success": True,
"path": str(filepath),
"filename": filename
}
except Exception as e:
print(f"Save error: {e}")
return {"success": False, "error": "Could not save image"}
def generate(self, state_path=None):
"""Hauptmethode"""
# State laden für Trust-Level
trust = 7.0
mood = 5
if os.path.exists(PATHS["state"]):
try:
with open(PATHS["state"]) as f:
state = json.load(f)
core = state.get("core_state", {})
trust = core.get("trust", 7.0)
mood = core.get("mood", 5)
except:
pass
# Health Check
health = self.check_health()
if not health["reachable"]:
return {"success": False, "error": "ComfyUI unreachable"}
# Prompt & Workflow
prompt_data = self.build_prompt(trust, mood)
workflow = self.build_basic_workflow(prompt_data)
# Generieren
print(f"🎨 Generating with trust={trust}, mood={mood}...")
result = self.submit_and_wait(workflow)
if not result["success"]:
return result
# Speichern
return self.save_image(result["data"], prompt_data)
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--check", action="store_true")
parser.add_argument("--test", action="store_true")
args = parser.parse_args()
bridge = ComfyBridgeWorking()
if args.check:
h = bridge.check_health()
print(f"ComfyUI: {'' if h['reachable'] else ''} {h.get('version', 'n/a')}")
return
if args.test:
print("Testing generation...")
result = bridge.generate()
if result["success"]:
print(f"✅ SUCCESS: {result['path']}")
else:
print(f"❌ FAILED: {result.get('error', 'Unknown')}")
with open("/tmp/comfy_result.json", "w") as f:
json.dump(result, f, indent=2)
else:
result = bridge.generate()
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()