This commit is contained in:
rui40000
2026-03-27 11:58:27 +08:00
parent 5585ecbff2
commit 9cd61aa63f
13 changed files with 203 additions and 1 deletions
+4
View File
@@ -30,6 +30,8 @@ from .color_matcher_node import NODE_DISPLAY_NAME_MAPPINGS as COLORMATCHER_NODE_
# 新增:素材拆分节点 # 新增:素材拆分节点
from .image_splitter_node import NODE_CLASS_MAPPINGS as IMAGESPLITTER_NODE_CLASS_MAPPINGS from .image_splitter_node import NODE_CLASS_MAPPINGS as IMAGESPLITTER_NODE_CLASS_MAPPINGS
from .image_splitter_node import NODE_DISPLAY_NAME_MAPPINGS as IMAGESPLITTER_NODE_DISPLAY_NAME_MAPPINGS from .image_splitter_node import NODE_DISPLAY_NAME_MAPPINGS as IMAGESPLITTER_NODE_DISPLAY_NAME_MAPPINGS
from .xmiles_nanobanana_node import NODE_CLASS_MAPPINGS as XMILES_NODE_CLASS_MAPPINGS
from .xmiles_nanobanana_node import NODE_DISPLAY_NAME_MAPPINGS as XMILES_NODE_DISPLAY_NAME_MAPPINGS
# 合并节点映射字典 # 合并节点映射字典
NODE_CLASS_MAPPINGS = {} NODE_CLASS_MAPPINGS = {}
@@ -48,6 +50,7 @@ NODE_CLASS_MAPPINGS.update(UTF8_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(OPENAI_NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(OPENAI_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(COLORMATCHER_NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(COLORMATCHER_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(IMAGESPLITTER_NODE_CLASS_MAPPINGS) NODE_CLASS_MAPPINGS.update(IMAGESPLITTER_NODE_CLASS_MAPPINGS)
NODE_CLASS_MAPPINGS.update(XMILES_NODE_CLASS_MAPPINGS)
# 合并节点显示名称映射 # 合并节点显示名称映射
NODE_DISPLAY_NAME_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {}
@@ -66,5 +69,6 @@ NODE_DISPLAY_NAME_MAPPINGS.update(UTF8_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(OPENAI_NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(OPENAI_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(COLORMATCHER_NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(COLORMATCHER_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(IMAGESPLITTER_NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(IMAGESPLITTER_NODE_DISPLAY_NAME_MAPPINGS)
NODE_DISPLAY_NAME_MAPPINGS.update(XMILES_NODE_DISPLAY_NAME_MAPPINGS)
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+198
View File
@@ -0,0 +1,198 @@
import torch
import numpy as np
import requests
import json
import uuid
import io
import base64
from PIL import Image
class XmilesNanobananaNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"text": ("STRING", {"multiline": True, "default": ""}),
"resolution": (["1K", "2K", "4K"], {"default": "4K"}),
"aspect_ratio": (["1:1","2:3","3:2","3:4","4:3","4:5","5:4","9:16","16:9","21:9"], {"default": "9:16"}),
},
"optional": {
"images": ("IMAGE",),
"proxy_url": ("STRING", {"default": "", "multiline": False}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("images", "log")
OUTPUT_IS_LIST = (True, False)
FUNCTION = "generate"
CATEGORY = "Rui-Node🐶/AI模型🤖"
def _make_client_id(self):
return f"{uuid.uuid4()}-test"
def _tensor_to_png_base64(self, tensor):
arr = tensor.cpu().numpy()
arr = np.clip(arr, 0, 1)
img = Image.fromarray((arr * 255).astype(np.uint8), 'RGB')
buf = io.BytesIO()
img.save(buf, format="PNG")
return base64.b64encode(buf.getvalue()).decode("utf-8")
def _pil_to_tensor(self, pil_img):
if pil_img.mode != "RGB":
pil_img = pil_img.convert("RGB")
np_img = np.array(pil_img).astype(np.float32) / 255.0
t = torch.from_numpy(np_img).unsqueeze(0)
return t
def _download_image_tensor(self, url, proxies=None):
r = requests.get(url, proxies=proxies, timeout=60)
r.raise_for_status()
img = Image.open(io.BytesIO(r.content))
return self._pil_to_tensor(img)
def generate(self, text, resolution, aspect_ratio, images=None, proxy_url=""):
client_id = self._make_client_id()
proxies = None
if proxy_url and proxy_url.strip():
proxies = {"http": proxy_url, "https": proxy_url}
parts = []
if text and text.strip():
parts.append({"text": text})
image_parts = []
if images is not None:
if isinstance(images, list):
tensors = [img[0] for img in images]
else:
tensors = [images[0]]
for t in tensors:
b64 = self._tensor_to_png_base64(t)
image_parts.append({"inlineData": {"data": "data:image/png;base64," + b64, "mimeType": "image/png"}})
for p in image_parts:
parts.append(p)
body_obj = {
"contents": [
{
"parts": parts,
"role": "user"
}
],
"filePath": {
"inputs": {
"filePath": ""
}
},
"generationConfig": {
"candidateCount": 1,
"imageConfig": {
"aspectRatio": aspect_ratio,
"imageSize": resolution
},
"responseModalities": ["TEXT", "IMAGE"],
"temperature": 1.0,
"topP": 0.95
},
"model": "gemini-3.1-flash-image-preview"
}
payload = {
"taskType": "ZENMUX",
"clientId": client_id,
"clientType": "image",
"callBackService": "remoteApi",
"extraData": {
"faceDetailer": 0,
"filePath": "",
"loraNum": 0,
"memberType": "PLUS",
"moduleName": "全能编辑 V2",
"resolution": "",
"taskType": "ZENMUX",
"uniqueId": str(uuid.uuid4().int)[:19],
"workflowName": ""
},
"imgIdList": [],
"memberType": "PLUS",
"body": json.dumps(body_obj, ensure_ascii=False)
}
url = "https://test.holopix.cn/ai-holopix-queue/api/prompt"
try:
resp = requests.post(url, headers={"Content-Type": "application/json"}, json=payload, proxies=proxies, timeout=60)
resp.raise_for_status()
data = resp.json()
status = data.get("status")
gen_status = data.get("generateStatus")
logs = {"status": status, "generateStatus": gen_status, "clientId": data.get("clientId"), "timestamp": data.get("timestamp")}
tensors = []
if status == 0 and gen_status == 1:
items = data.get("data") or []
for item in items:
url_item = item.get("url")
if url_item:
t = self._download_image_tensor(url_item, proxies=proxies)
tensors.append(t)
return (tensors if tensors else [], json.dumps(logs, ensure_ascii=False))
else:
return ([], json.dumps(data, ensure_ascii=False))
except Exception as e:
return ([], str(e))
class XmilesNanobananaResultParser:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"json_text": ("STRING", {"multiline": True, "default": ""}),
"proxy_url": ("STRING", {"default": "", "multiline": False}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
RETURN_NAMES = ("images", "log")
OUTPUT_IS_LIST = (True, False)
FUNCTION = "parse"
CATEGORY = "Rui-Node🐶/AI模型🤖"
def _download_image_tensor(self, url, proxies=None):
r = requests.get(url, proxies=proxies, timeout=60)
r.raise_for_status()
img = Image.open(io.BytesIO(r.content))
if img.mode != "RGB":
img = img.convert("RGB")
np_img = np.array(img).astype(np.float32) / 255.0
return torch.from_numpy(np_img).unsqueeze(0)
def parse(self, json_text, proxy_url=""):
proxies = None
if proxy_url and proxy_url.strip():
proxies = {"http": proxy_url, "https": proxy_url}
try:
obj = json.loads(json_text)
status = obj.get("status")
gen_status = obj.get("generateStatus")
tensors = []
if status == 0 and gen_status == 1:
items = obj.get("data") or []
for item in items:
url_item = item.get("url")
if url_item:
tensors.append(self._download_image_tensor(url_item, proxies=proxies))
return (tensors, json.dumps({"status": status, "generateStatus": gen_status}, ensure_ascii=False))
except Exception as e:
return ([], str(e))
NODE_CLASS_MAPPINGS = {
"XmilesNanobanana": XmilesNanobananaNode,
"XmilesNanobananaResultParser": XmilesNanobananaResultParser,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"XmilesNanobanana": "Xmiles-nanobanana",
"XmilesNanobananaResultParser": "Xmiles-nanobanana 结果解析",
}