feat:优化NanoImage

This commit is contained in:
2025-12-30 13:46:56 +08:00
parent b44d1589d1
commit 58f17e4497
2 changed files with 99 additions and 39 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

View File

@@ -10,6 +10,7 @@ import tomllib
import httpx import httpx
import uuid import uuid
import base64 import base64
import re
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime
from typing import List, Optional from typing import List, Optional
@@ -93,58 +94,117 @@ class NanoImage(PluginBase):
async with client.stream("POST", url, json=payload, headers=headers) as response: async with client.stream("POST", url, json=payload, headers=headers) as response:
logger.debug(f"收到响应状态码: {response.status_code}") logger.debug(f"收到响应状态码: {response.status_code}")
if response.status_code == 200: if response.status_code == 200:
# 处理流式响应 content_type = (response.headers.get("content-type") or "").lower()
is_sse = "text/event-stream" in content_type or "event-stream" in content_type
# 处理流式响应SSE
image_url = None image_url = None
image_base64 = None image_base64 = None
full_content = "" full_content = ""
async for line in response.aiter_lines(): if is_sse:
if line.startswith("data: "): async for line in response.aiter_lines():
data_str = line[6:] if not line:
if data_str == "[DONE]":
break
try:
import json
data = json.loads(data_str)
if "choices" in data and data["choices"]:
delta = data["choices"][0].get("delta", {})
# 方式1: 从 delta.images 中提取(新格式)
images = delta.get("images", [])
if images and len(images) > 0:
img_data = images[0].get("image_url", {}).get("url", "")
if img_data:
if img_data.startswith("data:image"):
# base64 格式
image_base64 = img_data
logger.info(f"从 delta.images 提取到 base64 图片")
elif img_data.startswith("http"):
image_url = img_data
logger.info(f"从 delta.images 提取到图片URL: {image_url}")
# 方式2: 从 content 中提取(旧格式)
content = delta.get("content", "")
if content:
full_content += content
if "http" in content:
import re
urls = re.findall(r'https?://[^\s\)\]"\']+', content)
if urls:
image_url = urls[0].rstrip("'\"")
logger.info(f"从 content 提取到图片URL: {image_url}")
except Exception as e:
logger.warning(f"解析响应数据失败: {e}")
continue continue
if line.startswith("data:"):
data_str = line[5:].lstrip()
if data_str == "[DONE]":
break
try:
import json
data = json.loads(data_str)
if "choices" in data and data["choices"]:
delta = data["choices"][0].get("delta", {})
# 方式1: 从 delta.images 中提取(新格式)
images = delta.get("images", [])
if images and len(images) > 0:
img_data = images[0].get("image_url", {}).get("url", "")
if img_data:
if img_data.startswith("data:image"):
# base64 格式
image_base64 = img_data
logger.info("从 delta.images 提取到 base64 图片")
elif img_data.startswith("http"):
image_url = img_data
logger.info(f"从 delta.images 提取到图片URL: {image_url}")
# 方式2: 从 content 中提取(旧格式)
content = delta.get("content", "")
if content:
full_content += content
if "http" in content:
urls = re.findall(r'https?://[^\s\)\]"\']+', content)
if urls:
image_url = urls[0].rstrip("'\"")
logger.info(f"从 content 提取到图片URL: {image_url}")
except Exception as e:
logger.warning(f"解析响应数据失败: {e}")
continue
else:
# 非流式application/json某些网关即使传了 stream=true 也会返回完整 JSON
raw = await response.aread()
try:
import json
data = json.loads(raw.decode("utf-8", errors="ignore"))
except Exception as e:
logger.error(f"解析 JSON 响应失败: {type(e).__name__}: {e}")
data = None
if isinstance(data, dict):
# 1) 标准 images endpoint 兼容:{"data":[{"url":...}|{"b64_json":...}]}
items = data.get("data")
if isinstance(items, list) and items:
first = items[0] if isinstance(items[0], dict) else {}
if isinstance(first, dict):
b64_json = first.get("b64_json")
if b64_json:
image_base64 = b64_json
logger.info("从 data[0].b64_json 提取到 base64 图片")
else:
u = first.get("url") or ""
if isinstance(u, str) and u:
image_url = u
logger.info(f"从 data[0].url 提取到图片URL: {image_url}")
# 2) chat.completion 兼容choices[0].message.images[0].image_url.url
if not image_url and not image_base64:
try:
choices = data.get("choices") or []
if choices:
msg = (choices[0].get("message") or {}) if isinstance(choices[0], dict) else {}
images = msg.get("images") or []
if isinstance(images, list) and images:
img0 = images[0] if isinstance(images[0], dict) else {}
if isinstance(img0, dict):
img_data = (
(img0.get("image_url") or {}).get("url")
if isinstance(img0.get("image_url"), dict)
else img0.get("url")
)
if isinstance(img_data, str) and img_data:
if img_data.startswith("data:image"):
image_base64 = img_data
logger.info("从 message.images 提取到 base64 图片")
elif img_data.startswith("http"):
image_url = img_data
logger.info(f"从 message.images 提取到图片URL: {image_url}")
except Exception:
pass
# 如果没有从流中提取到URL尝试从完整内容中提取 # 如果没有从流中提取到URL尝试从完整内容中提取
if not image_url and not image_base64 and full_content: if not image_url and not image_base64 and full_content:
import re
urls = re.findall(r'https?://[^\s\)\]"\']+', full_content) urls = re.findall(r'https?://[^\s\)\]"\']+', full_content)
if urls: if urls:
image_url = urls[0].rstrip("'\"") image_url = urls[0].rstrip("'\"")
logger.info(f"从完整内容提取到图片URL: {image_url}") logger.info(f"从完整内容提取到图片URL: {image_url}")
if not image_url and not image_base64: if not image_url and not image_base64:
logger.error(f"未能提取到图片,完整响应: {full_content[:500]}") # 避免把 base64 打到日志里:只输出裁剪后的概要
if full_content:
logger.error(f"未能提取到图片,完整响应(截断): {full_content[:500]}")
else:
# 非SSE时 full_content 可能为空,补充输出 content-type 便于定位
logger.error(f"未能提取到图片content-type={content_type or 'unknown'}")
# 处理 base64 图片 # 处理 base64 图片
if image_base64: if image_base64: