Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 40 additions & 21 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,16 @@ def load_env_file():
COMFYUI_INSTANCES = [s.strip() for s in os.getenv("COMFYUI_INSTANCES", "127.0.0.1:8188").split(",") if s.strip()]
COMFYUI_ADDRESS = COMFYUI_INSTANCES[0]

def comfy_base_url(addr):
"""返回带协议的 ComfyUI 基础地址。支持 https://domain:port 等远程加密后端;
无协议前缀时默认 http://(兼容旧的 host:port 写法)。"""
addr = str(addr or "").strip().rstrip("/")
if not addr:
return "http://"
if addr.startswith("http://") or addr.startswith("https://"):
return addr
return f"http://{addr}"

AI_BASE_URL = os.getenv("COMFLY_BASE_URL", "https://ai.comfly.chat").rstrip("/")
AI_API_KEY = os.getenv("COMFLY_API_KEY", "")
PUBLIC_BASE_URL = os.getenv("PUBLIC_BASE_URL", "").strip().rstrip("/")
Expand Down Expand Up @@ -2738,7 +2748,7 @@ def check_images_exist(backend_addr, images):
if not images: return True
for img in images:
try:
url = f"http://{backend_addr}/view?filename={urllib.parse.quote(img)}&type=input"
url = f"{comfy_base_url(backend_addr)}/view?filename={urllib.parse.quote(img)}&type=input"
r = requests.get(url, stream=True, timeout=0.5)
r.close()
if r.status_code != 200: return False
Expand Down Expand Up @@ -2773,7 +2783,7 @@ def get_best_backend(required_images: List[str] = None):

for addr in COMFYUI_INSTANCES:
try:
with urllib.request.urlopen(f"http://{addr}/queue", timeout=1) as response:
with urllib.request.urlopen(f"{comfy_base_url(addr)}/queue", timeout=1) as response:
data = json.loads(response.read())
remote_load = len(data.get('queue_running', [])) + len(data.get('queue_pending', []))
with LOAD_LOCK:
Expand All @@ -2800,7 +2810,7 @@ def reserve_best_backend(required_images: List[str] = None):
backend_stats = {}
for addr in COMFYUI_INSTANCES:
try:
with urllib.request.urlopen(f"http://{addr}/queue", timeout=1) as response:
with urllib.request.urlopen(f"{comfy_base_url(addr)}/queue", timeout=1) as response:
data = json.loads(response.read())
remote_load = len(data.get('queue_running', [])) + len(data.get('queue_pending', []))
has_images = check_images_exist(addr, required_images)
Expand All @@ -2825,7 +2835,7 @@ def reserve_best_backend(required_images: List[str] = None):
def download_image(comfy_address, comfy_url_path, prefix="studio_"):
filename = f"{prefix}{uuid.uuid4().hex[:10]}.png"
local_path = output_path_for(filename, "output")
full_url = f"http://{comfy_address}{comfy_url_path}"
full_url = f"{comfy_base_url(comfy_address)}{comfy_url_path}"
try:
with urllib.request.urlopen(full_url, timeout=COMFYUI_DOWNLOAD_TIMEOUT) as response, open(local_path, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
Expand Down Expand Up @@ -2892,7 +2902,7 @@ def download_comfy_output(comfy_address, item, prefix="studio_"):
subfolder = urllib.parse.quote(str(item.get("subfolder") or ""))
file_type = urllib.parse.quote(str(item.get("type") or "output"))
comfy_url_path = f"/view?filename={urllib.parse.quote(str(item['filename']))}&subfolder={subfolder}&type={file_type}"
full_url = f"http://{comfy_address}{comfy_url_path}"
full_url = f"{comfy_base_url(comfy_address)}{comfy_url_path}"
try:
with urllib.request.urlopen(full_url, timeout=COMFYUI_DOWNLOAD_TIMEOUT) as response, open(local_path, 'wb') as out_file:
shutil.copyfileobj(response, out_file)
Expand Down Expand Up @@ -2984,7 +2994,7 @@ def save_to_history(record):

def get_comfy_history(comfy_address, prompt_id):
try:
with urllib.request.urlopen(f"http://{comfy_address}/history/{prompt_id}") as response:
with urllib.request.urlopen(f"{comfy_base_url(comfy_address)}/history/{prompt_id}") as response:
return json.loads(response.read())
except Exception as e:
return {}
Expand Down Expand Up @@ -8960,7 +8970,7 @@ def view_image(filename: str, type: str = "input", subfolder: str = ""):
# 先按原逻辑去各 ComfyUI 后端找
for addr in COMFYUI_INSTANCES:
try:
url = f"http://{addr}/view"
url = f"{comfy_base_url(addr)}/view"
params = {"filename": filename, "type": type, "subfolder": subfolder}
r = requests.get(url, params=params, timeout=1)
if r.status_code == 200:
Expand Down Expand Up @@ -9039,7 +9049,7 @@ async def upload_image(files: List[UploadFile] = File(...)):
for addr in COMFYUI_INSTANCES:
try:
files_data = {'image': (file.filename, content, file.content_type)}
response = requests.post(f"http://{addr}/upload/image", files=files_data, timeout=5)
response = requests.post(f"{comfy_base_url(addr)}/upload/image", files=files_data, timeout=5)
if response.status_code == 200:
last_result = response.json()
success_count += 1
Expand Down Expand Up @@ -9149,7 +9159,7 @@ async def upload_comfyui_base64(payload: Base64UploadRequest):
comfy_name = None
for addr in COMFYUI_INSTANCES:
try:
resp = requests.post(f"http://{addr}/upload/image",
resp = requests.post(f"{comfy_base_url(addr)}/upload/image",
files={'image': (filename, content, ct or 'image/png')}, timeout=10)
if resp.status_code == 200:
comfy_name = resp.json().get("name", filename)
Expand Down Expand Up @@ -14206,7 +14216,7 @@ def generate(req: GenerateRequest):
for image_name in required_images:
need_sync = False
try:
check_url = f"http://{target_backend}/view?filename={urllib.parse.quote(image_name)}&type=input"
check_url = f"{comfy_base_url(target_backend)}/view?filename={urllib.parse.quote(image_name)}&type=input"
resp = requests.get(check_url, stream=True, timeout=0.5)
resp.close()
if resp.status_code != 200:
Expand All @@ -14220,7 +14230,7 @@ def generate(req: GenerateRequest):
for addr in COMFYUI_INSTANCES:
if addr == target_backend: continue
try:
src_url = f"http://{addr}/view?filename={urllib.parse.quote(image_name)}&type=input"
src_url = f"{comfy_base_url(addr)}/view?filename={urllib.parse.quote(image_name)}&type=input"
r = requests.get(src_url, timeout=5)
if r.status_code == 200:
image_content = r.content
Expand All @@ -14231,7 +14241,7 @@ def generate(req: GenerateRequest):
if image_content:
try:
files = {'image': (image_name, image_content, image_type)}
requests.post(f"http://{target_backend}/upload/image", files=files, timeout=10)
requests.post(f"{comfy_base_url(target_backend)}/upload/image", files=files, timeout=10)
except Exception as e:
print(f"Sync upload failed: {e}")

Expand Down Expand Up @@ -14275,7 +14285,7 @@ def generate(req: GenerateRequest):
p = {"prompt": workflow, "client_id": CLIENT_ID}
data = json.dumps(p).encode('utf-8')
try:
post_req = urllib.request.Request(f"http://{target_backend}/prompt", data=data)
post_req = urllib.request.Request(f"{comfy_base_url(target_backend)}/prompt", data=data)
prompt_id = json.loads(urllib.request.urlopen(post_req, timeout=10).read())['prompt_id']
except urllib.error.HTTPError as e:
error_body = e.read().decode('utf-8')
Expand Down Expand Up @@ -14828,22 +14838,31 @@ def get_comfyui_instances():

@app.put("/api/comfyui/instances")
def save_comfyui_instances(payload: ComfyInstancesPayload):
# 宽容校验:去前后空白、去 http(s):// 前缀、去尾部斜杠;要求形如 host:port
# 宽容校验:去前后空白、去尾部斜杠;保留 http(s):// 前缀以支持远程加密后端
#(如 https://example.com:8188);要求形如 [scheme://]host:port
cleaned = []
for item in payload.instances:
s = str(item or "").strip()
if not s:
continue
s = re.sub(r"^https?://", "", s)
s = s.rstrip("/")
if ":" not in s:
raise HTTPException(status_code=400, detail=f"地址缺少端口号:{item}(应为 host:port,例如 127.0.0.1:8188)")
host, _, port = s.rpartition(":")
scheme = ""
m = re.match(r"^(https?://)", s, re.I)
if m:
scheme = m.group(1).lower()
rest = s[len(m.group(1)):]
else:
rest = s
rest = re.sub(r"^//", "", rest)
if ":" not in rest:
raise HTTPException(status_code=400, detail=f"地址缺少端口号:{item}(应为 host:port,例如 127.0.0.1:8188;远程后端可用 https://example.com:8188)")
host, _, port = rest.rpartition(":")
if not host or not port.isdigit():
raise HTTPException(status_code=400, detail=f"地址不合法:{item}(应为 host:port,例如 127.0.0.1:8188)")
if s in cleaned:
raise HTTPException(status_code=400, detail=f"地址不合法:{item}(应为 host:port,例如 127.0.0.1:8188;远程后端可用 https://example.com:8188)")
normalized = f"{scheme}{rest}" if scheme else rest
if normalized in cleaned:
continue
cleaned.append(s)
cleaned.append(normalized)
if not cleaned:
raise HTTPException(status_code=400, detail="至少保留一个 ComfyUI 后端地址")
# 写入 env 文件
Expand Down
2 changes: 1 addition & 1 deletion static/comfyui-settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<i data-lucide="save" class="w-3.5 h-3.5"></i><span>保存</span>
</button>
</div>
<div style="font-size:10.5px;color:var(--faint);line-height:1.55;font-weight:600">格式:<span style="font-family:ui-monospace,Menlo,monospace">host:port</span>(如 <span style="font-family:ui-monospace,Menlo,monospace">127.0.0.1:8188</span>)</div>
<div style="font-size:10.5px;color:var(--faint);line-height:1.55;font-weight:600">格式:<span style="font-family:ui-monospace,Menlo,monospace">host:port</span>(如 <span style="font-family:ui-monospace,Menlo,monospace">127.0.0.1:8188</span>);远程加密后端可填 <span style="font-family:ui-monospace,Menlo,monospace">https://domain:port</span></div>
</div>

<div class="side-card workflow-panel workflow-panel-comfy">
Expand Down
2 changes: 1 addition & 1 deletion static/js/comfyui-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ function renderComfyInstances(){
el.innerHTML = comfyInstances.map((addr, i) => `
<div style="display:flex;align-items:center;gap:6px;padding:4px;border:1px solid var(--line);border-radius:9px;background:var(--soft)">
<span style="width:18px;text-align:center;font-size:10.5px;color:var(--faint);font-weight:800">${i + 1}</span>
<input class="small-input" type="text" value="${escapeAttr(addr)}" placeholder="host:port" oninput="updateComfyInstance(${i}, this.value)" style="flex:1;height:28px;padding:0 8px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--text);font-size:12px;font-family:ui-monospace,Menlo,monospace">
<input class="small-input" type="text" value="${escapeAttr(addr)}" placeholder="host:port 或 https://domain:port" oninput="updateComfyInstance(${i}, this.value)" style="flex:1;height:28px;padding:0 8px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--text);font-size:12px;font-family:ui-monospace,Menlo,monospace">
<button class="opt-del" type="button" onclick="removeComfyInstance(${i})" title="删除"><i data-lucide="x" class="w-3 h-3"></i></button>
</div>
`).join('');
Expand Down