diff --git a/.env.example b/.env.example index 863a137..f65e95d 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,8 @@ CREATE_IMAGE_MODEL=imagen-3.0-generate-002 UPLOAD_PROVIDER=smms SMMS_SECRET_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX PICGO_API_KEY=xxxx +CLOUDFLARE_IMGBED_URL=https://xxxxxxx.pages.dev/upload +CLOUDFLARE_IMGBED_AUTH_CODE=xxxxxxxxx ########################################################################## #########################stream_optimizer 相关配置######################## STREAM_MIN_DELAY=0.016 diff --git a/README.md b/README.md index 9f51d54..94e64bc 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,11 @@ CREATE_IMAGE_MODEL="imagen-3.0-generate-002" # 图片生成模型,默认使用imagen-3.0 # 图片上传配置 - UPLOAD_PROVIDER="smms" # 图片上传提供商,目前支持smms + UPLOAD_PROVIDER="smms" # 图片上传提供商,目前支持smms、picgo、cloudflare_imgbed SMMS_SECRET_TOKEN="your-smms-token" # SM.MS图床的API Token + PICGO_API_KEY="your-picogo-apikey" # PicoGo图床的API Key 可在 `https://www.picgo.net/settings/api` 获取 + CLOUDFLARE_IMGBED_URL="https://xxxxxxx.pages.dev/upload" # CloudFlare 图床上传地址,可自行搭建:`https://github.com/MarSeventh/CloudFlare-ImgBed` + CLOUDFLARE_IMGBED_AUTH_CODE="your-cloudflare-imgber-auth-code" # CloudFlare图床的鉴权key,可在项目后台设置,若无鉴权则可直接置空。 # stream_optimizer 相关配置 STREAM_MIN_DELAY=0.016 @@ -138,10 +141,26 @@ - `UPLOAD_PROVIDER`: 图片上传服务提供商 - 默认值: `smms` - - 说明: 目前支持 SM.MS 图床 + - 可选值: `smms`, `picgo`, `cloudflare_imgbed` + - 说明: 用于选择图片上传的服务提供商。目前支持 SM.MS 图床, PicGo 图床, 以及 Cloudflare ImgBed。 + - `SMMS_SECRET_TOKEN`: SM.MS API Token - - 用途: 用于图片上传到 SM.MS 图床 - - 获取方式: 需要在 SM.MS 官网注册并获取 + - 用途: 用于图片上传到 SM.MS 图床的身份验证。 + - 获取方式: 需要在 [SM.MS 官网](https://sm.ms/) 注册并获取。 + + - `PICGO_API_KEY`: PicGo API Key + - 用途: 用于图片上传到 PicGo 图床的身份验证。 + - 获取方式: 可在 [PicGo 官网](https://www.picgo.net/settings/api) 的设置页面 API 选项中获取。 + + - `CLOUDFLARE_IMGBED_URL`: Cloudflare ImgBed 上传地址 + - 用途: 指定 Cloudflare ImgBed 图床的上传 API 地址。 + - 获取方式: 如果您自行搭建了 Cloudflare ImgBed 服务,请填写您的服务部署地址。参考 [Cloudflare-ImgBed 项目](https://github.com/MarSeventh/CloudFlare-ImgBed) 自行搭建。 + - 注意: URL 必须以 `https://` 开头,并指向 `/upload` 路径 ,例如 `https://cloudflare-imgbed-7b0.pages.dev/upload`。 + + - `CLOUDFLARE_IMGBED_AUTH_CODE`: Cloudflare ImgBed 鉴权 Key + - 用途: 用于 Cloudflare ImgBed 图床的身份验证。 + - 说明: 如果您的 Cloudflare ImgBed 服务启用了鉴权,请填写鉴权 Key。若未启用鉴权,则留空即可。 + - 获取方式: 在 Cloudflare ImgBed 项目的后台设置中获取,或在搭建时自行设置。 #### 流式输出优化配置 diff --git a/app/core/config.py b/app/core/config.py index f629bf7..816cb14 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -18,6 +18,8 @@ class Settings(BaseSettings): UPLOAD_PROVIDER: str = "smms" SMMS_SECRET_TOKEN: str = "" PICGO_API_KEY: str = "" + CLOUDFLARE_IMGBED_URL: str = "" + CLOUDFLARE_IMGBED_AUTH_CODE: str = "" TEST_MODEL: str = "gemini-1.5-flash" # 流式输出优化器配置 diff --git a/app/core/uploader.py b/app/core/uploader.py index e30211d..b52b227 100644 --- a/app/core/uploader.py +++ b/app/core/uploader.py @@ -258,6 +258,119 @@ class PicGoUploader(ImageUploader): original_error=e ) + +class CloudFlareImgBedUploader(ImageUploader): + """CloudFlare图床上传器""" + + def __init__(self, auth_code: str, api_url: str): + """ + 初始化CloudFlare图床上传器 + + Args: + auth_code: 认证码 + api_url: 上传API地址 + """ + self.auth_code = auth_code + self.api_url = api_url + + def upload(self, file: bytes, filename: str) -> UploadResponse: + """ + 上传图片到CloudFlare图床 + + Args: + file: 图片文件二进制数据 + filename: 文件名 + + Returns: + UploadResponse: 上传响应对象 + + Raises: + UploadError: 上传失败时抛出异常 + """ + try: + # 准备请求URL(添加认证码参数,如果存在) + if self.auth_code: + request_url = f"{self.api_url}?authCode={self.auth_code}" + else: + request_url = self.api_url + + # 准备文件数据 + files = { + "file": (filename, file) + } + + # 发送请求 + response = requests.post( + request_url, + files=files + ) + + # 检查响应状态 + response.raise_for_status() + + # 解析响应 + result = response.json() + + # 验证响应格式 + if not result or not isinstance(result, list) or len(result) == 0: + raise UploadError( + message="Invalid response format", + error_type=UploadErrorType.PARSE_ERROR + ) + + # 获取文件URL + file_path = result[0].get("src") + if not file_path: + raise UploadError( + message="Missing file URL in response", + error_type=UploadErrorType.PARSE_ERROR + ) + + # 构建完整URL(如果返回的是相对路径) + base_url = self.api_url.split("/upload")[0] + full_url = file_path if file_path.startswith(("http://", "https://")) else f"{base_url}{file_path}" + + # 构建图片元数据(注意:CloudFlare-ImgBed不返回所有元数据,所以部分字段为默认值) + image_metadata = ImageMetadata( + width=0, # CloudFlare-ImgBed不返回宽度 + height=0, # CloudFlare-ImgBed不返回高度 + filename=filename, + size=0, # CloudFlare-ImgBed不返回大小 + url=full_url, + delete_url=None # CloudFlare-ImgBed不返回删除URL + ) + + return UploadResponse( + success=True, + code="success", + message="Upload success", + data=image_metadata + ) + + except requests.RequestException as e: + # 处理网络请求相关错误 + raise UploadError( + message=f"Upload request failed: {str(e)}", + error_type=UploadErrorType.NETWORK_ERROR, + original_error=e + ) + except (KeyError, ValueError, TypeError, IndexError) as e: + # 处理响应解析错误 + raise UploadError( + message=f"Invalid response format: {str(e)}", + error_type=UploadErrorType.PARSE_ERROR, + original_error=e + ) + except UploadError: + # 重新抛出已经是 UploadError 类型的异常 + raise + except Exception as e: + # 处理其他未预期的错误 + raise UploadError( + message=f"Upload failed: {str(e)}", + error_type=UploadErrorType.UNKNOWN, + original_error=e + ) class ImageUploaderFactory: @staticmethod @@ -272,4 +385,9 @@ class ImageUploaderFactory: elif provider == "picgo": api_url = credentials.get("api_url", "https://www.picgo.net/api/1/upload") return PicGoUploader(credentials["api_key"], api_url) + elif provider == "cloudflare_imgbed": + return CloudFlareImgBedUploader( + credentials["auth_code"], + credentials["base_url"] + ) raise ValueError(f"Unknown provider: {provider}") diff --git a/app/services/chat/response_handler.py b/app/services/chat/response_handler.py index 99682b2..5b2f5d2 100644 --- a/app/services/chat/response_handler.py +++ b/app/services/chat/response_handler.py @@ -200,6 +200,8 @@ def _extract_image_data(part: dict) -> str: image_uploader = ImageUploaderFactory.create(provider=settings.UPLOAD_PROVIDER,api_key=settings.SMMS_SECRET_TOKEN) elif settings.UPLOAD_PROVIDER == "picgo": image_uploader = ImageUploaderFactory.create(provider=settings.UPLOAD_PROVIDER,api_key=settings.PICGO_API_KEY) + elif settings.UPLOAD_PROVIDER == "cloudflare_imgbed": + image_uploader = ImageUploaderFactory.create(provider=settings.UPLOAD_PROVIDER,base_url=settings.CLOUDFLARE_IMGBED_URL,auth_code=settings.CLOUDFLARE_IMGBED_AUTH_CODE) current_date = time.strftime("%Y/%m/%d") filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png" base64_data = part["inlineData"]["data"] diff --git a/app/services/image_create_service.py b/app/services/image_create_service.py index 353c51d..d31f87f 100644 --- a/app/services/image_create_service.py +++ b/app/services/image_create_service.py @@ -96,11 +96,6 @@ class ImageCreateService: for index, generated_image in enumerate(response.generated_images): image_data = generated_image.image.image_bytes image_uploader = None - if settings.UPLOAD_PROVIDER == "smms": - image_uploader = ImageUploaderFactory.create(provider=settings.UPLOAD_PROVIDER,api_key=settings.SMMS_SECRET_TOKEN) - current_date = time.strftime("%Y/%m/%d") - filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png" - upload_response = image_uploader.upload(image_data,filename) if request.response_format == "b64_json": base64_image = base64.b64encode(image_data).decode('utf-8') @@ -109,6 +104,30 @@ class ImageCreateService: "revised_prompt": request.prompt }) else: + current_date = time.strftime("%Y/%m/%d") + filename = f"{current_date}/{uuid.uuid4().hex[:8]}.png" + + if settings.UPLOAD_PROVIDER == "smms": + image_uploader = ImageUploaderFactory.create( + provider=settings.UPLOAD_PROVIDER, + api_key=settings.SMMS_SECRET_TOKEN + ) + elif settings.UPLOAD_PROVIDER == "picgo": + image_uploader = ImageUploaderFactory.create( + provider=settings.UPLOAD_PROVIDER, + api_key=settings.PICGO_API_KEY + ) + elif settings.UPLOAD_PROVIDER == "cloudflare_imgbed": + image_uploader = ImageUploaderFactory.create( + provider=settings.UPLOAD_PROVIDER, + base_url=settings.CLOUDFLARE_IMGBED_URL, + auth_code=settings.CLOUDFLARE_IMGBED_AUTH_CODE + ) + else: + raise ValueError(f"Unsupported upload provider: {settings.UPLOAD_PROVIDER}") + + upload_response = image_uploader.upload(image_data, filename) + images_data.append({ "url": f"{upload_response.data.url}", "revised_prompt": request.prompt