From b68ff96af68d9c6c8a2d41d370a7d05fd5446a84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 4 Jan 2026 18:31:17 +0000 Subject: [PATCH] Address all review comments from PR review thread Co-authored-by: neverbiasu <90372299+neverbiasu@users.noreply.github.com> --- __pycache__/nodes.cpython-312.pyc | Bin 0 -> 23762 bytes nodes.py | 66 +++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 20 deletions(-) create mode 100644 __pycache__/nodes.cpython-312.pyc diff --git a/__pycache__/nodes.cpython-312.pyc b/__pycache__/nodes.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e016fbb78ed2c7938bc56ebfbf189ad70ecc28b GIT binary patch literal 23762 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 literal 0 HcmV?d00001 diff --git a/nodes.py b/nodes.py index c046bd0..b55edd8 100644 --- a/nodes.py +++ b/nodes.py @@ -578,6 +578,7 @@ class ModelScopeImageGenerator(ModelScopeBase): "loras": ( "STRING", { + "default": "", "placeholder": '{"lora_id": 0.6} or just lora_id', "tooltip": "LoRA model configuration.", } @@ -665,31 +666,36 @@ class ModelScopeImageGenerator(ModelScopeBase): loras = loras.strip() if loras: # Try to parse as JSON, otherwise use as string - if loras.startswith('{'): - try: - payload["loras"] = json.loads(loras) - except json.JSONDecodeError: - # Fallback to string if parsing fails - payload["loras"] = loras - else: + try: + parsed_loras = json.loads(loras) + except json.JSONDecodeError: + # Fallback to string if parsing fails payload["loras"] = loras + else: + # Only accept JSON objects (dict); otherwise, use the original string + if isinstance(parsed_loras, dict): + payload["loras"] = parsed_loras + else: + payload["loras"] = loras try: # Use ensure_ascii=False for Chinese characters support data_bytes = json.dumps(payload, ensure_ascii=False).encode('utf-8') - resp = requests.post(url, headers=headers, data=data_bytes, timeout=60) + headers_with_charset = dict(headers) + headers_with_charset["Content-Type"] = "application/json; charset=utf-8" + resp = requests.post(url, headers=headers_with_charset, data=data_bytes, timeout=60) resp.raise_for_status() except requests.RequestException as e: raise RuntimeError(f"Network error calling ModelScope Image API: {e}") from e try: data = resp.json() - task_id = data.get("task_id") except Exception as e: - raise RuntimeError(f"Failed to parse API response or missing task_id: {e}") from e + raise RuntimeError(f"Failed to parse API response as JSON: {e}") from e + task_id = data.get("task_id") if not task_id: - raise RuntimeError(f"No task_id returned from async request: {data}") + raise RuntimeError(f"No task_id returned from async request: {data}") # Poll for status # BASE_URL is .../v1 @@ -702,8 +708,10 @@ class ModelScopeImageGenerator(ModelScopeBase): } # Poll for up to 10 minutes (600s) - max_retries = 120 - for _ in range(max_retries): + max_wait_time = 600 + start_poll_time = time.time() + + while time.time() - start_poll_time < max_wait_time: try: result = requests.get(task_url, headers=poll_headers, timeout=30) result.raise_for_status() @@ -714,24 +722,42 @@ class ModelScopeImageGenerator(ModelScopeBase): output_images = task_data.get("output_images") if output_images and len(output_images) > 0: image_url = output_images[0] - return (self._download_image_from_url(image_url, headers),) + download_headers = {"Authorization": f"Bearer {key}"} if key else None + return (self._download_image_from_url(image_url, download_headers),) else: - raise RuntimeError(f"Task succeeded but no output images found: {task_data}") + raise RuntimeError( + f"Task succeeded but no output images found (status={status}, " + f"output_count={len(output_images) if output_images is not None else 0})" + ) elif status == "FAILED": - raise RuntimeError(f"Image Generation Failed: {task_data}") + error_message = task_data.get("error_message") or task_data.get("message") or "Unknown error" + error_code = task_data.get("error_code") or task_data.get("code") + details = f", code={error_code}" if error_code is not None else "" + raise RuntimeError(f"Image generation failed (status={status}{details}): {error_message}") elif status in ["PENDING", "RUNNING"]: time.sleep(5) continue else: - # Unknown status, wait and retry - time.sleep(5) + # Unknown status: fail fast with a clear error + raise RuntimeError(f"Unknown task status '{status}' received from API: {task_data}") except requests.RequestException as e: - # Transient network error during polling? - print(f"Warning: Polling failed: {e}. Retrying...") + # Distinguish permanent HTTP errors (4xx) from transient issues. + status_code = getattr(getattr(e, "response", None), "status_code", None) + if status_code is not None and 400 <= status_code < 500: + # Client errors are typically permanent for this task; fail fast. + raise RuntimeError( + f"Image generation polling failed with client error {status_code}: " + f"{getattr(e.response, 'text', '')}" + ) from e + + # Transient network or server error during polling: log and retry. + import logging + logger = logging.getLogger(__name__) + logger.warning(f"Polling failed: {e}. Retrying...") time.sleep(5) raise RuntimeError("Image generation timed out after polling.")