Files
silveroxides-ComfyUI_Gemini…/gemini_nodes.py
2025-05-07 18:42:33 +02:00

463 lines
20 KiB
Python

import os
import json
import uuid
import torch
import numpy as np
from PIL import Image
import io
import folder_paths
from google import genai
from google.genai import types
import time
import traceback
import threading
import queue
import sys
import importlib
import subprocess
import random
def check_and_install_dependencies():
required_packages = {
'requests': 'requests',
'pysocks': 'PySocks',
}
for module_name, package_name in required_packages.items():
try:
importlib.import_module(module_name)
except ImportError:
print(f"[WARNING] 未找到 {package_name},尝试安装...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", package_name])
print(f"[INFO] 成功安装 {package_name}")
except Exception as e:
print(f"[ERROR] 安装 {package_name} 失败: {str(e)}")
try:
check_and_install_dependencies()
except Exception as e:
print(f"[WARNING] 检查依赖时出错: {str(e)}")
class SSL_GeminiAPIKeyConfig:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"api_key": ("STRING", {"multiline": False}),
}
}
RETURN_TYPES = ("GEMINI_CONFIG",)
RETURN_NAMES = ("config",)
FUNCTION = "configure"
CATEGORY = "💠SSL/API/Gemini"
def configure(self, api_key):
config = {"api_key": api_key}
return (config,)
class SSL_GeminiTextPrompt:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"config": ("GEMINI_CONFIG",),
"prompt": ("STRING", {"multiline": True}),
"system_instruction": ("STRING", {"default": "You are a helpful AI assistant.", "multiline": True}),
"model": (["gemini-1.0-pro", "gemini-exp-1206", "gemini-1.5-flash", "gemini-1.5-pro", "gemini-2.0-flash", "gemini-2.0-flash-lite-001", "gemini-2.0-flash-exp", "gemini-2.0-pro", "gemini-2.0-pro-exp-02-05"], {"default": "gemini-2.0-flash"}),
"temperature": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
"top_p": ("FLOAT", {"default": 0.95, "min": 0.0, "max": 1.0, "step": 0.01}),
"top_k": ("INT", {"default": 40, "min": 1, "max": 100, "step": 1}),
"max_output_tokens": ("INT", {"default": 8192, "min": 1, "max": 8192, "step": 1}),
"include_images": (["True", "False"], {"default": "True"}),
},
"optional": {
"input_image": ("IMAGE",),
"input_image_2": ("IMAGE",), # 添加第二个图像输入
"use_proxy": (["False", "True"], {"default": "False"}),
"proxy_host": ("STRING", {"default": "127.0.0.1"}),
"proxy_port": ("INT", {"default": 7890, "min": 1, "max": 65535}),
"use_seed": (["True", "False"], {"default": "True"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
}
}
RETURN_TYPES = ("STRING", "IMAGE", "INT")
RETURN_NAMES = ("text", "image", "seed")
FUNCTION = "generate"
CATEGORY = "💠SSL/API/Gemini"
def save_binary_file(self, data, mime_type):
ext = ".bin"
if mime_type == "image/png":
ext = ".png"
elif mime_type == "image/jpeg":
ext = ".jpg"
output_dir = folder_paths.get_output_directory()
gemini_dir = os.path.join(output_dir, "gemini_outputs")
os.makedirs(gemini_dir, exist_ok=True)
file_name = os.path.join(gemini_dir, f"gemini_output_{uuid.uuid4()}{ext}")
with open(file_name, "wb") as f:
f.write(data)
return file_name
def generate_empty_image(self, width=64, height=64):
empty_image = np.ones((height, width, 3), dtype=np.float32) * 0.2
tensor = torch.from_numpy(empty_image).unsqueeze(0)
return tensor
def generate(self, config, prompt, system_instruction, model, temperature, top_p, top_k, max_output_tokens, include_images,
input_image=None, input_image_2=None, use_proxy="False", proxy_host="127.0.0.1", proxy_port=7890,
use_seed="True", seed=0):
original_http_proxy = os.environ.get('HTTP_PROXY')
original_https_proxy = os.environ.get('HTTPS_PROXY')
original_http_proxy_lower = os.environ.get('http_proxy')
original_https_proxy_lower = os.environ.get('https_proxy')
print(f"[INFO] 开始生成,模型: {model}, 温度: {temperature}")
# 改进的随机种子处理逻辑
if use_seed == "True":
# 如果种子为0,生成随机种子
if seed == 0:
# 使用当前时间戳和随机数组合生成种子,确保每次运行都不同
current_time = int(time.time() * 1000)
random_component = random.randint(0, 1000000)
actual_seed = (current_time + random_component) % 2147483647
print(f"[INFO] 生成随机种子: {actual_seed}")
else:
# 使用指定的种子值
actual_seed = seed
print(f"[INFO] 使用指定种子: {actual_seed}")
# 设置随机种子
random.seed(actual_seed)
np.random.seed(actual_seed)
torch.manual_seed(actual_seed)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(actual_seed)
else:
actual_seed = None
print("[INFO] 未使用种子")
try:
if use_proxy == "True":
if not proxy_host.startswith(('http://', 'https://')):
proxy_url = f"http://{proxy_host}:{proxy_port}"
else:
proxy_url = f"{proxy_host}:{proxy_port}"
os.environ['HTTP_PROXY'] = proxy_url
os.environ['HTTPS_PROXY'] = proxy_url
os.environ['http_proxy'] = proxy_url
os.environ['https_proxy'] = proxy_url
os.environ['REQUESTS_CA_BUNDLE'] = ''
print(f"[INFO] 已启用代理: {proxy_url}")
print(f"[INFO] 初始化Gemini客户端")
try:
client_options = {}
if use_proxy == "True":
try:
import google.api_core.http_client
import google.auth.transport.requests
try:
import requests
from requests.adapters import HTTPAdapter
class ProxyAdapter(HTTPAdapter):
def __init__(self, proxy_url, **kwargs):
self.proxy_url = proxy_url
super().__init__(**kwargs)
def add_headers(self, request, **kwargs):
super().add_headers(request, **kwargs)
session = requests.Session()
proxies = {
"http": proxy_url,
"https": proxy_url
}
session.proxies.update(proxies)
adapter = ProxyAdapter(proxy_url, max_retries=3)
session.mount('http://', adapter)
session.mount('https://', adapter)
session.verify = False
http_client = google.api_core.http_client.RequestsHttpClient(session=session)
client_options["http_client"] = http_client
print(f"[INFO] 已使用requests库设置代理到HTTP客户端")
except Exception as proxy_error:
print(f"[WARNING] 设置HTTP客户端代理失败: {str(proxy_error)}")
except ImportError as e:
print(f"[WARNING] 导入Google API HTTP客户端库失败: {str(e)}")
client = genai.Client(api_key=config["api_key"], **client_options)
print(f"[INFO] Gemini客户端初始化成功")
except Exception as e:
print(f"[ERROR] Gemini客户端初始化失败: {str(e)}")
return (f"Gemini客户端初始化失败: {str(e)}", self.generate_empty_image(), actual_seed if actual_seed is not None else 0)
try:
import socket
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
test_socket.settimeout(5)
test_host = "generativelanguage.googleapis.com"
if use_proxy == "True":
try:
import socks
test_socket = socks.socksocket()
test_socket.set_proxy(socks.HTTP, proxy_host, proxy_port)
test_socket.settimeout(5)
except ImportError:
print(f"[WARNING] 未安装PySocks库,无法通过代理测试连接")
test_socket.connect((test_host, 443))
test_socket.close()
network_ok = True
except Exception as e:
network_ok = False
print(f"[WARNING] 网络连接测试失败: {str(e)}")
if use_proxy == "True":
try:
import subprocess
cmd = f"curl -x {proxy_url} -s -o /dev/null -w '%{{http_code}}' https://{test_host}"
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10)
if result.returncode == 0 and result.stdout.strip() in ['200', '301', '302']:
network_ok = True
else:
print(f"[WARNING] curl代理测试失败,状态码: {result.stdout.strip() if result.stdout else 'N/A'}")
except Exception as curl_error:
print(f"[WARNING] curl测试失败: {str(curl_error)}")
except ImportError:
pass
contents = []
# 处理输入图像
images_to_process = []
if input_image is not None:
images_to_process.append(input_image)
if input_image_2 is not None:
images_to_process.append(input_image_2)
# 如果有图像需要处理
if images_to_process:
try:
img_parts = []
for img in images_to_process:
img_array = img[0].cpu().numpy()
img_array = (img_array * 255).astype(np.uint8)
pil_img = Image.fromarray(img_array)
img_byte_arr = io.BytesIO()
pil_img.save(img_byte_arr, format='PNG')
img_bytes = img_byte_arr.getvalue()
img_part = {"inline_data": {"mime_type": "image/png", "data": img_bytes}}
img_parts.append(img_part)
# 添加所有图像部分和文本部分
contents = img_parts + [{"text": prompt}]
except Exception as e:
print(f"[ERROR] 处理输入图像时出错: {str(e)}")
return (f"处理输入图像时出错: {str(e)}", self.generate_empty_image(), actual_seed if actual_seed is not None else 0)
else:
contents = prompt
response_modalities = ["text"]
if include_images == "True":
response_modalities.append("image")
generate_content_config = types.GenerateContentConfig(
temperature=temperature,
top_p=top_p,
top_k=top_k,
max_output_tokens=max_output_tokens,
safety_settings=[
types.SafetySetting(
category="HARM_CATEGORY_HARASSMENT",
threshold="BLOCK_NONE", # Block none
),
types.SafetySetting(
category="HARM_CATEGORY_HATE_SPEECH",
threshold="BLOCK_NONE", # Block none
),
types.SafetySetting(
category="HARM_CATEGORY_SEXUALLY_EXPLICIT",
threshold="BLOCK_NONE", # Block none
),
types.SafetySetting(
category="HARM_CATEGORY_DANGEROUS_CONTENT",
threshold="BLOCK_NONE", # Block none
),
],
response_modalities=response_modalities,
system_instruction=[
types.Part.from_text(text=system_instruction),
],
)
if use_seed == "True" and actual_seed is not None:
try:
generate_content_config.seed = actual_seed
except Exception as seed_error:
print(f"[WARNING] 无法设置种子到API请求: {str(seed_error)}")
text_output = ""
image_tensor = None
try:
print(f"[INFO] 发送API请求到Gemini")
start_time = time.time()
timeout = 30
def api_call():
try:
api_response = client.models.generate_content(
model=model,
contents=contents,
config=generate_content_config,
)
result_queue.put(("success", api_response))
except Exception as e:
print(f"[ERROR] API线程中出错: {str(e)}")
result_queue.put(("error", e))
result_queue = queue.Queue()
api_thread = threading.Thread(target=api_call)
api_thread.daemon = True
api_thread.start()
try:
status, result = result_queue.get(timeout=timeout)
elapsed_time = time.time() - start_time
if status == "success":
response = result
print(f"[INFO] API请求成功完成,耗时: {elapsed_time:.2f}秒")
else:
print(f"[ERROR] API请求线程中出错,耗时: {elapsed_time:.2f}秒,错误: {str(result)}")
error_str = str(result).lower()
if any(term in error_str for term in ["timeout", "connection", "network", "socket", "连接", "网络"]):
if not network_ok and use_proxy != "True":
return (f"API请求失败: {str(result)}。网络连接测试失败,建议启用代理。", self.generate_empty_image(), actual_seed if actual_seed is not None else 0)
raise result
except queue.Empty:
elapsed_time = time.time() - start_time
print(f"[ERROR] API请求超时,已等待: {elapsed_time:.2f}秒")
timeout_msg = f"Gemini API请求超时,已等待{timeout}秒。"
if not network_ok:
if use_proxy == "True":
timeout_msg += f"网络连接测试失败,当前使用的代理({proxy_host}:{proxy_port})可能无效,请检查代理设置。"
else:
timeout_msg += "网络连接测试失败,建议启用代理。"
else:
timeout_msg += "网络连接测试成功,但API请求仍然超时,可能是服务器繁忙或请求内容过大。"
return (timeout_msg, self.generate_empty_image(), actual_seed if actual_seed is not None else 0)
print(f"[INFO] 收到API响应")
if hasattr(response, 'candidates') and response.candidates:
for part in response.candidates[0].content.parts:
if hasattr(part, 'text') and part.text is not None:
text_output += part.text
elif hasattr(part, 'inline_data') and part.inline_data is not None:
try:
inline_data = part.inline_data
mime_type = inline_data.mime_type
data = inline_data.data
image_path = self.save_binary_file(data, mime_type)
img = Image.open(image_path)
if img.mode != 'RGB':
img = img.convert('RGB')
img_array = np.array(img).astype(np.float32) / 255.0
image_tensor = torch.from_numpy(img_array).unsqueeze(0)
except Exception as e:
print(f"[ERROR] 图像处理错误: {str(e)}")
text_output += f"\n图像处理错误: {str(e)}"
except Exception as e:
print(f"[ERROR] API调用错误: {str(e)}")
text_output = f"API调用错误: {str(e)}"
if image_tensor is None:
image_tensor = self.generate_empty_image()
# if use_seed == "True" and actual_seed is not None:
# seed_info = f"\n\n[种子信息: {actual_seed}]"
# text_output += seed_info
return (text_output, image_tensor, actual_seed if actual_seed is not None else 0)
finally:
if original_http_proxy:
os.environ['HTTP_PROXY'] = original_http_proxy
else:
if 'HTTP_PROXY' in os.environ:
os.environ.pop('HTTP_PROXY')
if original_https_proxy:
os.environ['HTTPS_PROXY'] = original_https_proxy
else:
if 'HTTPS_PROXY' in os.environ:
os.environ.pop('HTTPS_PROXY')
if original_http_proxy_lower:
os.environ['http_proxy'] = original_http_proxy_lower
else:
if 'http_proxy' in os.environ:
os.environ.pop('http_proxy')
if original_https_proxy_lower:
os.environ['https_proxy'] = original_https_proxy_lower
else:
if 'https_proxy' in os.environ:
os.environ.pop('https_proxy')
if 'REQUESTS_CA_BUNDLE' in os.environ:
os.environ.pop('REQUESTS_CA_BUNDLE')
try:
import requests
if hasattr(requests, 'Session'):
clean_session = requests.Session()
if hasattr(requests, 'session'):
requests.session = lambda: clean_session
except:
pass
NODE_CLASS_MAPPINGS = {
"SSL_GeminiAPIKeyConfig": SSL_GeminiAPIKeyConfig,
"SSL_GeminiTextPrompt": SSL_GeminiTextPrompt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"SSL_GeminiAPIKeyConfig": "💠SSL Gemini API Key",
"SSL_GeminiTextPrompt": "💠SSL Gemini Text Prompt",
}