From bb6c629aef145770d257fa048d8fbe422a0e7707 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:19:21 +0800 Subject: [PATCH 1/3] Implement base64 fallback for image handling when no uploader is configured (#1) * Initial plan * Implement base64 fallback for image handling when no uploader configured Co-authored-by: bbbugg <80089841+bbbugg@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bbbugg <80089841+bbbugg@users.noreply.github.com> --- app/handler/response_handler.py | 24 +++++++++++++++++++++++ app/service/image/image_create_service.py | 6 +++++- app/utils/helpers.py | 17 ++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/app/handler/response_handler.py b/app/handler/response_handler.py index 1300e71..bbe047d 100644 --- a/app/handler/response_handler.py +++ b/app/handler/response_handler.py @@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional from app.config.config import settings from app.utils.uploader import ImageUploaderFactory from app.log.logger import get_openai_logger +from app.utils.helpers import is_image_upload_configured logger = get_openai_logger() @@ -251,7 +252,22 @@ def _extract_result( return text, reasoning_content, tool_calls, thought +def _has_inline_image_part(response: Dict[str, Any]) -> bool: + try: + for c in response.get("candidates", []): + for p in c.get("content", {}).get("parts", []): + if isinstance(p, dict) and ("inlineData" in p): + return True + except Exception: + return False + return False + + def _extract_image_data(part: dict) -> str: + # Return empty string if no uploader is configured + if not is_image_upload_configured(): + return "" + image_uploader = None if settings.UPLOAD_PROVIDER == "smms": image_uploader = ImageUploaderFactory.create( @@ -322,6 +338,10 @@ def _extract_tool_calls( def _handle_gemini_stream_response( response: Dict[str, Any], model: str, stream: bool ) -> Dict[str, Any]: + # Early return raw Gemini response if no uploader configured and contains inline images + if not is_image_upload_configured() and _has_inline_image_part(response): + return response + text, reasoning_content, tool_calls, thought = _extract_result( response, model, stream=stream, gemini_format=True ) @@ -339,6 +359,10 @@ def _handle_gemini_stream_response( def _handle_gemini_normal_response( response: Dict[str, Any], model: str, stream: bool ) -> Dict[str, Any]: + # Early return raw Gemini response if no uploader configured and contains inline images + if not is_image_upload_configured() and _has_inline_image_part(response): + return response + text, reasoning_content, tool_calls, thought = _extract_result( response, model, stream=stream, gemini_format=True ) diff --git a/app/service/image/image_create_service.py b/app/service/image/image_create_service.py index 9a3b867..b90ef99 100644 --- a/app/service/image/image_create_service.py +++ b/app/service/image/image_create_service.py @@ -10,6 +10,7 @@ from app.core.constants import VALID_IMAGE_RATIOS from app.domain.openai_models import ImageGenerationRequest from app.log.logger import get_image_create_logger from app.utils.uploader import ImageUploaderFactory +from app.utils.helpers import is_image_upload_configured logger = get_image_create_logger() @@ -97,12 +98,15 @@ class ImageCreateService: image_data = generated_image.image.image_bytes image_uploader = None - if request.response_format == "b64_json": + # Return base64 if explicitly requested or if no uploader is configured + if request.response_format == "b64_json" or not is_image_upload_configured(): base64_image = base64.b64encode(image_data).decode("utf-8") images_data.append( {"b64_json": base64_image, "revised_prompt": request.prompt} ) + continue else: + # Upload to configured provider current_date = time.strftime("%Y/%m/%d") filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png" diff --git a/app/utils/helpers.py b/app/utils/helpers.py index c283654..f0ce1e8 100644 --- a/app/utils/helpers.py +++ b/app/utils/helpers.py @@ -10,6 +10,7 @@ from pathlib import Path import logging from app.core.constants import DATA_URL_PATTERN, IMAGE_URL_PATTERN, VALID_IMAGE_RATIOS +from app.config.config import settings helper_logger = logging.getLogger("app.utils") @@ -189,3 +190,19 @@ def get_current_version(default_version: str = "0.0.0") -> str: except IOError as e: helper_logger.error(f"Error reading VERSION file ('{version_file}'): {e}. Using default version '{default_version}'.") return default_version + + +def is_image_upload_configured() -> bool: + """Return True only if a valid upload provider is selected and all required settings for that provider are present.""" + provider = getattr(settings, "UPLOAD_PROVIDER", "").strip().lower() + if provider == "smms": + return bool(getattr(settings, "SMMS_SECRET_TOKEN", None)) + if provider == "picgo": + return bool(getattr(settings, "PICGO_API_KEY", None)) + if provider == "cloudflare_imgbed": + return all([ + getattr(settings, "CLOUDFLARE_IMGBED_URL", None), + getattr(settings, "CLOUDFLARE_IMGBED_AUTH_CODE", None), + getattr(settings, "CLOUDFLARE_IMGBED_UPLOAD_FOLDER", None), + ]) + return False From 8711088ebcf7f0da593b0e28e01c0df779e20ad7 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 Aug 2025 16:23:39 +0800 Subject: [PATCH 2/3] Fix circular import issue between config, logger, and helpers modules (#2) * Initial plan * Fix circular import by removing top-level settings import from helpers.py Co-authored-by: bbbugg <80089841+bbbugg@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: bbbugg <80089841+bbbugg@users.noreply.github.com> --- app/utils/helpers.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/utils/helpers.py b/app/utils/helpers.py index f0ce1e8..a913c73 100644 --- a/app/utils/helpers.py +++ b/app/utils/helpers.py @@ -10,7 +10,6 @@ from pathlib import Path import logging from app.core.constants import DATA_URL_PATTERN, IMAGE_URL_PATTERN, VALID_IMAGE_RATIOS -from app.config.config import settings helper_logger = logging.getLogger("app.utils") @@ -193,16 +192,21 @@ def get_current_version(default_version: str = "0.0.0") -> str: def is_image_upload_configured() -> bool: - """Return True only if a valid upload provider is selected and all required settings for that provider are present.""" - provider = getattr(settings, "UPLOAD_PROVIDER", "").strip().lower() + """Return True only if a valid upload provider is selected and all required settings for that provider are present. Uses lazy import to avoid circular imports.""" + try: + from app.config.config import settings # local import to avoid circular dependency at module import time + except Exception: + return False + + provider = (getattr(settings, "UPLOAD_PROVIDER", "") or "").strip().lower() if provider == "smms": - return bool(getattr(settings, "SMMS_SECRET_TOKEN", None)) + return bool(getattr(settings, "SMMS_SECRET_TOKEN", "")) if provider == "picgo": - return bool(getattr(settings, "PICGO_API_KEY", None)) + return bool(getattr(settings, "PICGO_API_KEY", "")) if provider == "cloudflare_imgbed": return all([ - getattr(settings, "CLOUDFLARE_IMGBED_URL", None), - getattr(settings, "CLOUDFLARE_IMGBED_AUTH_CODE", None), - getattr(settings, "CLOUDFLARE_IMGBED_UPLOAD_FOLDER", None), + getattr(settings, "CLOUDFLARE_IMGBED_URL", ""), + getattr(settings, "CLOUDFLARE_IMGBED_AUTH_CODE", ""), + getattr(settings, "CLOUDFLARE_IMGBED_UPLOAD_FOLDER", ""), ]) return False From 1d15a21ce51d82966d7b3bd5e548e6ed08fbb4d0 Mon Sep 17 00:00:00 2001 From: bbbugg <80089841+bbbugg@users.noreply.github.com> Date: Sun, 31 Aug 2025 00:33:33 +0800 Subject: [PATCH 3/3] Remove upload folder check for Cloudflare imgbed --- app/utils/helpers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/utils/helpers.py b/app/utils/helpers.py index a913c73..29778ed 100644 --- a/app/utils/helpers.py +++ b/app/utils/helpers.py @@ -207,6 +207,5 @@ def is_image_upload_configured() -> bool: return all([ getattr(settings, "CLOUDFLARE_IMGBED_URL", ""), getattr(settings, "CLOUDFLARE_IMGBED_AUTH_CODE", ""), - getattr(settings, "CLOUDFLARE_IMGBED_UPLOAD_FOLDER", ""), ]) return False