Add simplified Gemini 3.1 image node UI
This commit is contained in:
@@ -74,6 +74,57 @@ api -> Chat Image Bridge -> Chat Image Bridge
|
||||
|
||||
可以直接接到 `Save Image` 或其他 ComfyUI 图像节点。
|
||||
|
||||
## Gemini 3.1 简洁图片节点
|
||||
|
||||
添加节点:
|
||||
|
||||
```text
|
||||
api -> Chat Image Bridge -> Gemini 3.1 图片生成 / 编辑
|
||||
```
|
||||
|
||||
该节点针对 `catapi.catcard.uk` 的 Gemini 原生 `generateContent` 接口。默认地址已经填写,日常使用时前面板只需要操作提示词、输出比例和分辨率。
|
||||
|
||||
### 简洁界面
|
||||
|
||||
- 左侧图片输入全部平等命名为“图片1、图片2……”;不预设主图和参考图。
|
||||
- 初始只显示图片1。连接最后一个图片口后自动增加下一个,最多14张;断开末尾多余连接后自动收起。
|
||||
- 用户可以在提示词中写“以图片1的人物、图片2的服装为参考”,插件会自动给图片加顺序标签。
|
||||
- 没有连接图片时自动进行文生图,连接图片后自动进行编辑或参考图生成。
|
||||
- 右侧只有一个 IMAGE 输出。
|
||||
- Base URL、API Key 和超时设置默认折叠在“连接设置”中。
|
||||
|
||||
前面板核心参数:
|
||||
|
||||
```text
|
||||
提示词
|
||||
输出比例
|
||||
分辨率
|
||||
```
|
||||
|
||||
分辨率会同时切换模型名和发送 `imageConfig.imageSize`:
|
||||
|
||||
| 分辨率 | 模型 |
|
||||
| --- | --- |
|
||||
| `1K` | `gemini-3.1-flash-image-1K` |
|
||||
| `2K` | `gemini-3.1-flash-image-2K` |
|
||||
| `4K` | `gemini-3.1-flash-image-4k` |
|
||||
|
||||
输出比例使用一个下拉框:
|
||||
|
||||
- `自动 / 保持原图`:不发送 `aspectRatio`。单图编辑保持输入比例,多图输入由模型结合提示词决定。
|
||||
- `方形 · 1:1`:方形输出。
|
||||
- `横图 · 16:9`、`横图 · 21:9` 等:横向输出。
|
||||
- `竖图 · 9:16`、`竖图 · 3:4` 等:竖向输出。
|
||||
- `自定义比例`:此时才显示自定义输入框,例如填写 `20:9`,节点会自动映射到最接近的服务端合法比例。
|
||||
|
||||
比例下方带有一个只读画幅预览,不增加额外参数。
|
||||
|
||||
服务端当前支持:`1:1`、`1:4`、`1:8`、`2:3`、`3:2`、`3:4`、`4:1`、`4:3`、`4:5`、`5:4`、`8:1`、`9:16`、`16:9`、`21:9`。服务端不直接接受 `20:9`,节点会将其映射为 `21:9`。
|
||||
|
||||
最多支持14个动态 ComfyUI `IMAGE` 输入。中转站实测可以接受20张,但模型的可靠公开能力为14张,因此节点将上限设置为14。
|
||||
|
||||
实测任意比例的 `1000×437` 输入图选择“自动 / 保持原图 + 4K”后输出为 `6152×2688`,基本精确保留原图比例;同一输入选择“横图 · 16:9 + 4K”时输出为 `5504×3072`。因此节点既支持保持原画幅的高清重绘,也支持改变画幅与提升分辨率同时进行。
|
||||
|
||||
## GPT Image 生图节点
|
||||
|
||||
添加节点:
|
||||
|
||||
+203
-1
@@ -1,5 +1,6 @@
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import time
|
||||
from io import BytesIO
|
||||
@@ -23,6 +24,46 @@ GRSAI_PROXY_RE = re.compile(
|
||||
RESOLUTION_OPTIONS = ["auto", "1K", "2K", "4K"]
|
||||
ASPECT_RATIO_OPTIONS = ["auto", "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3", "4:5", "5:4", "21:9"]
|
||||
DEFAULT_MODEL = "gemini-3-pro-image-preview"
|
||||
CATAPI_GEMINI_BASE_URL = "https://catapi.catcard.uk"
|
||||
CATAPI_GEMINI_MODELS = {
|
||||
"1K": "gemini-3.1-flash-image-1K",
|
||||
"2K": "gemini-3.1-flash-image-2K",
|
||||
"4K": "gemini-3.1-flash-image-4k",
|
||||
}
|
||||
CATAPI_GEMINI_ASPECT_RATIOS = [
|
||||
"1:1",
|
||||
"1:4",
|
||||
"1:8",
|
||||
"2:3",
|
||||
"3:2",
|
||||
"3:4",
|
||||
"4:1",
|
||||
"4:3",
|
||||
"4:5",
|
||||
"5:4",
|
||||
"8:1",
|
||||
"9:16",
|
||||
"16:9",
|
||||
"21:9",
|
||||
]
|
||||
CATAPI_OUTPUT_RATIO_OPTIONS = [
|
||||
"自动 / 保持原图",
|
||||
"方形 · 1:1",
|
||||
"横图 · 16:9",
|
||||
"横图 · 21:9",
|
||||
"横图 · 4:3",
|
||||
"横图 · 3:2",
|
||||
"横图 · 5:4",
|
||||
"横图 · 4:1",
|
||||
"横图 · 8:1",
|
||||
"竖图 · 9:16",
|
||||
"竖图 · 3:4",
|
||||
"竖图 · 2:3",
|
||||
"竖图 · 4:5",
|
||||
"竖图 · 1:4",
|
||||
"竖图 · 1:8",
|
||||
"自定义比例",
|
||||
]
|
||||
GPT_IMAGE_DEFAULT_BASE_URL = ""
|
||||
GPT_IMAGE_MODELS = ["gpt-image-2-vip", "gpt-image-2", "nano-banana", "nano-banana-2", "nano-banana-pro"]
|
||||
GPT_IMAGE_RESOLUTION_OPTIONS = ["auto", "1K", "2K", "4K"]
|
||||
@@ -109,6 +150,10 @@ def normalize_gemini_base(base_url):
|
||||
value = (base_url or "").strip().rstrip("/")
|
||||
if not value:
|
||||
raise ValueError("base_url is required")
|
||||
for marker in ("/v1beta/models/", "/v1/models/"):
|
||||
if marker in value:
|
||||
value = value.split(marker, 1)[0]
|
||||
break
|
||||
if value.endswith("/chat/completions"):
|
||||
value = value[: -len("/chat/completions")]
|
||||
if value.endswith("/v1"):
|
||||
@@ -172,6 +217,40 @@ def normalize_generation_option(value):
|
||||
return value
|
||||
|
||||
|
||||
def nearest_catapi_aspect_ratio(value):
|
||||
value = (value or "").strip()
|
||||
match = re.fullmatch(r"\s*(\d+(?:\.\d+)?)\s*[:/]\s*(\d+(?:\.\d+)?)\s*", value)
|
||||
if not match:
|
||||
raise ValueError("custom_aspect_ratio must look like 20:9")
|
||||
|
||||
width = float(match.group(1))
|
||||
height = float(match.group(2))
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError("custom_aspect_ratio values must be greater than zero")
|
||||
|
||||
target = width / height
|
||||
|
||||
def distance(option):
|
||||
option_width, option_height = (float(part) for part in option.split(":"))
|
||||
return abs(math.log(target / (option_width / option_height)))
|
||||
|
||||
return min(CATAPI_GEMINI_ASPECT_RATIOS, key=distance)
|
||||
|
||||
|
||||
def resolve_catapi_output_ratio(output_ratio, custom_ratio=""):
|
||||
output_ratio = (output_ratio or "").strip()
|
||||
if not output_ratio or output_ratio == "自动 / 保持原图":
|
||||
return "", "auto"
|
||||
if output_ratio == "自定义比例":
|
||||
requested = (custom_ratio or "").strip()
|
||||
return nearest_catapi_aspect_ratio(requested), requested
|
||||
|
||||
match = re.search(r"(\d+:\d+)\s*$", output_ratio)
|
||||
if not match or match.group(1) not in CATAPI_GEMINI_ASPECT_RATIOS:
|
||||
raise ValueError(f"Unsupported output ratio option: {output_ratio}")
|
||||
return match.group(1), match.group(1)
|
||||
|
||||
|
||||
def gpt_image_size_for(model, resolution, aspect_ratio):
|
||||
model = (model or "").strip()
|
||||
resolution = (resolution or "auto").strip()
|
||||
@@ -365,7 +444,13 @@ def redact_object(value):
|
||||
if isinstance(value, list):
|
||||
return [redact_object(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {key: redact_object(item) for key, item in value.items()}
|
||||
result = {}
|
||||
for key, item in value.items():
|
||||
if key in ("data", "b64_json") and isinstance(item, str) and len(item) > 200:
|
||||
result[key] = f"<redacted {len(item)} chars>"
|
||||
else:
|
||||
result[key] = redact_object(item)
|
||||
return result
|
||||
return value
|
||||
|
||||
|
||||
@@ -758,6 +843,121 @@ class ChatImageBridgeAdvanced(ChatImageBridgeBase):
|
||||
)
|
||||
|
||||
|
||||
class CatAPIGeminiImage(ChatImageBridgeBase):
|
||||
"""Simple Gemini 3.1 image generation and editing node for catapi.catcard.uk."""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {"default": "", "multiline": True}),
|
||||
"output_ratio": (CATAPI_OUTPUT_RATIO_OPTIONS, {"default": "自动 / 保持原图"}),
|
||||
"custom_ratio": ("STRING", {"default": "20:9", "multiline": False}),
|
||||
"resolution": (list(CATAPI_GEMINI_MODELS), {"default": "1K"}),
|
||||
"api_key": ("STRING", {"default": "", "multiline": False, "password": True}),
|
||||
"base_url": ("STRING", {"default": CATAPI_GEMINI_BASE_URL, "multiline": False}),
|
||||
"timeout_seconds": ("INT", {"default": 600, "min": 60, "max": 3600}),
|
||||
},
|
||||
"optional": {
|
||||
"image_1": ("IMAGE",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "generate"
|
||||
CATEGORY = "api/Chat Image Bridge"
|
||||
|
||||
def generate(
|
||||
self,
|
||||
prompt,
|
||||
output_ratio,
|
||||
custom_ratio,
|
||||
resolution,
|
||||
api_key,
|
||||
base_url,
|
||||
timeout_seconds,
|
||||
**kwargs,
|
||||
):
|
||||
if not (api_key or "").strip():
|
||||
raise ValueError("api_key is required")
|
||||
if not (base_url or "").strip():
|
||||
raise ValueError("base_url is required")
|
||||
if not (prompt or "").strip():
|
||||
raise ValueError("prompt is required")
|
||||
if resolution not in CATAPI_GEMINI_MODELS:
|
||||
raise ValueError(f"Unsupported resolution: {resolution}")
|
||||
|
||||
sent_ratio, requested_ratio = resolve_catapi_output_ratio(output_ratio, custom_ratio)
|
||||
|
||||
model = CATAPI_GEMINI_MODELS[resolution]
|
||||
endpoint = normalize_gemini_endpoint(base_url, model)
|
||||
image_inputs = []
|
||||
for index in range(1, 15):
|
||||
image = kwargs.get(f"image_{index}")
|
||||
if image is not None:
|
||||
image_inputs.append((index, image))
|
||||
|
||||
if len(image_inputs) > 14:
|
||||
raise ValueError("A maximum of 14 images is supported")
|
||||
|
||||
if image_inputs:
|
||||
parts = [
|
||||
{
|
||||
"text": (
|
||||
"以下图片按图片1、图片2的顺序编号。它们地位平等,"
|
||||
"请严格根据用户提示理解每张图片的用途。"
|
||||
)
|
||||
}
|
||||
]
|
||||
for image_index, image in image_inputs:
|
||||
parts.append({"text": f"图片{image_index}:"})
|
||||
parts.append({"inlineData": tensor_to_png_inline_data(image)})
|
||||
parts.append({"text": f"用户要求:\n{prompt.strip()}"})
|
||||
else:
|
||||
parts = [{"text": prompt.strip()}]
|
||||
|
||||
image_config = {"imageSize": resolution}
|
||||
if sent_ratio:
|
||||
image_config["aspectRatio"] = sent_ratio
|
||||
payload = {
|
||||
"contents": [{"role": "user", "parts": parts}],
|
||||
"generationConfig": {
|
||||
"responseModalities": ["IMAGE"],
|
||||
"imageConfig": image_config,
|
||||
},
|
||||
}
|
||||
|
||||
data = self._post_with_retries(
|
||||
endpoint=endpoint,
|
||||
api_key=api_key,
|
||||
payload=payload,
|
||||
timeout_seconds=int(timeout_seconds),
|
||||
retry_times=1,
|
||||
)
|
||||
refs = collect_image_references(data)
|
||||
if not refs:
|
||||
preview = json.dumps(redact_object(data), ensure_ascii=False)[:2000]
|
||||
raise RuntimeError(f"Gemini response did not contain image data: {preview}")
|
||||
|
||||
output_image, _ = self._decode_refs_to_image(refs, int(timeout_seconds))
|
||||
height = int(output_image.shape[1])
|
||||
width = int(output_image.shape[2])
|
||||
info = {
|
||||
"model": model,
|
||||
"resolution": resolution,
|
||||
"output_ratio": output_ratio,
|
||||
"requested_aspect_ratio": requested_ratio,
|
||||
"sent_aspect_ratio": sent_ratio or "auto",
|
||||
"input_images": len(image_inputs),
|
||||
"output_width": width,
|
||||
"output_height": height,
|
||||
"output_aspect_ratio": round(width / height, 6),
|
||||
}
|
||||
print(f"[CatAPI Gemini Image] {json.dumps(info, ensure_ascii=False)}")
|
||||
return (output_image,)
|
||||
|
||||
|
||||
class GPTImageBase(ChatImageBridgeBase):
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
@@ -977,6 +1177,7 @@ RightCodesGPTImage = GPTImage
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ChatImageBridge": ChatImageBridge,
|
||||
"ChatImageBridgeAdvanced": ChatImageBridgeAdvanced,
|
||||
"CatAPIGeminiImage": CatAPIGeminiImage,
|
||||
"GPTImage": GPTImage,
|
||||
"GPTImageEdit": GPTImageEdit,
|
||||
"RightCodesGPTImage": GPTImage,
|
||||
@@ -985,6 +1186,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ChatImageBridge": "Chat Image Bridge",
|
||||
"ChatImageBridgeAdvanced": "Chat Image Bridge Advanced",
|
||||
"CatAPIGeminiImage": "Gemini 3.1 图片生成 / 编辑",
|
||||
"GPTImage": "GPT Image Generate",
|
||||
"GPTImageEdit": "GPT Image Edit",
|
||||
"RightCodesGPTImage": "GPT Image",
|
||||
|
||||
@@ -3,6 +3,10 @@ import { api } from "/scripts/api.js";
|
||||
|
||||
const NODE_CLASS = "ChatImageBridge";
|
||||
const FETCH_BUTTON = "Fetch Models";
|
||||
const CATAPI_NODE_CLASS = "CatAPIGeminiImage";
|
||||
const MAX_CATAPI_IMAGES = 14;
|
||||
const CATAPI_AUTO_RATIO = "自动 / 保持原图";
|
||||
const CATAPI_CUSTOM_RATIO = "自定义比例";
|
||||
|
||||
function findWidget(node, name) {
|
||||
return node.widgets?.find((widget) => widget.name === name);
|
||||
@@ -119,3 +123,239 @@ app.registerExtension({
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function setWidgetVisible(node, widget, visible) {
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (visible && widget.__catapiHidden) {
|
||||
widget.type = widget.__catapiHidden.type;
|
||||
widget.computeSize = widget.__catapiHidden.computeSize;
|
||||
widget.serializeValue = widget.__catapiHidden.serializeValue;
|
||||
delete widget.__catapiHidden;
|
||||
} else if (!visible && !widget.__catapiHidden) {
|
||||
widget.__catapiHidden = {
|
||||
type: widget.type,
|
||||
computeSize: widget.computeSize,
|
||||
serializeValue: widget.serializeValue,
|
||||
};
|
||||
widget.type = "converted-widget";
|
||||
widget.computeSize = () => [0, -4];
|
||||
widget.serializeValue = () => widget.value;
|
||||
}
|
||||
|
||||
const computed = node.computeSize?.() || node.size;
|
||||
node.setSize?.([Math.max(node.size?.[0] || 360, 360), computed?.[1] || node.size?.[1] || 200]);
|
||||
markCanvasDirty();
|
||||
}
|
||||
|
||||
function parseRatioValue(value) {
|
||||
const match = `${value || ""}`.match(/(\d+(?:\.\d+)?)\s*[:/]\s*(\d+(?:\.\d+)?)\s*$/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const width = Number(match[1]);
|
||||
const height = Number(match[2]);
|
||||
return width > 0 && height > 0 ? width / height : null;
|
||||
}
|
||||
|
||||
function currentPreviewRatio(node) {
|
||||
const outputRatio = `${findWidget(node, "output_ratio")?.value || ""}`;
|
||||
if (outputRatio === CATAPI_AUTO_RATIO) {
|
||||
return null;
|
||||
}
|
||||
if (outputRatio === CATAPI_CUSTOM_RATIO) {
|
||||
return parseRatioValue(findWidget(node, "custom_ratio")?.value);
|
||||
}
|
||||
return parseRatioValue(outputRatio);
|
||||
}
|
||||
|
||||
function makeRatioPreviewWidget(node) {
|
||||
return {
|
||||
name: "ratio_preview",
|
||||
type: "custom",
|
||||
value: null,
|
||||
serialize: false,
|
||||
computeSize() {
|
||||
return [360, 62];
|
||||
},
|
||||
draw(ctx, _node, width, y, height) {
|
||||
const ratio = currentPreviewRatio(node);
|
||||
const centerX = width / 2;
|
||||
const centerY = y + height / 2 + 2;
|
||||
ctx.save();
|
||||
ctx.lineWidth = 1.5;
|
||||
ctx.strokeStyle = "#8a8a8a";
|
||||
ctx.fillStyle = "#b8b8b8";
|
||||
ctx.font = "12px sans-serif";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(ratio ? (ratio > 1 ? "横图" : ratio < 1 ? "竖图" : "方形") : "自动 / 保持原图", 14, centerY + 4);
|
||||
|
||||
if (ratio) {
|
||||
const maxWidth = 105;
|
||||
const maxHeight = 42;
|
||||
let boxWidth = maxWidth;
|
||||
let boxHeight = boxWidth / ratio;
|
||||
if (boxHeight > maxHeight) {
|
||||
boxHeight = maxHeight;
|
||||
boxWidth = boxHeight * ratio;
|
||||
}
|
||||
ctx.strokeRect(centerX + 24 - boxWidth / 2, centerY - boxHeight / 2, boxWidth, boxHeight);
|
||||
} else {
|
||||
ctx.setLineDash([5, 4]);
|
||||
ctx.strokeRect(centerX - 28, centerY - 18, 80, 36);
|
||||
}
|
||||
ctx.restore();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function catapiImageInputs(node) {
|
||||
return (node.inputs || []).filter((input) => /^image_\d+$/.test(input.name));
|
||||
}
|
||||
|
||||
function labelCatapiImageInputs(node) {
|
||||
for (const input of catapiImageInputs(node)) {
|
||||
const index = Number(input.name.split("_")[1]);
|
||||
input.label = `图片${index}`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCatapiImageInputs(node) {
|
||||
let inputs = catapiImageInputs(node);
|
||||
labelCatapiImageInputs(node);
|
||||
|
||||
const last = inputs[inputs.length - 1];
|
||||
if (last?.link != null && inputs.length < MAX_CATAPI_IMAGES) {
|
||||
const nextIndex = inputs.length + 1;
|
||||
node.addInput(`image_${nextIndex}`, "IMAGE");
|
||||
inputs = catapiImageInputs(node);
|
||||
labelCatapiImageInputs(node);
|
||||
}
|
||||
|
||||
while (inputs.length > 1) {
|
||||
const lastInput = inputs[inputs.length - 1];
|
||||
const previousInput = inputs[inputs.length - 2];
|
||||
if (lastInput.link != null || previousInput.link != null) {
|
||||
break;
|
||||
}
|
||||
node.removeInput(node.inputs.indexOf(lastInput));
|
||||
inputs = catapiImageInputs(node);
|
||||
}
|
||||
|
||||
const computed = node.computeSize?.() || node.size;
|
||||
node.setSize?.([Math.max(node.size?.[0] || 360, 360), computed?.[1] || node.size?.[1] || 200]);
|
||||
markCanvasDirty();
|
||||
}
|
||||
|
||||
function prepareCatapiNode(node) {
|
||||
node.title = "Gemini 3.1 图片生成 / 编辑";
|
||||
node.setSize?.([Math.max(node.size?.[0] || 380, 380), node.size?.[1] || 240]);
|
||||
|
||||
const labels = {
|
||||
prompt: "提示词",
|
||||
output_ratio: "输出比例",
|
||||
custom_ratio: "自定义比例",
|
||||
resolution: "分辨率",
|
||||
api_key: "API Key",
|
||||
base_url: "Base URL",
|
||||
timeout_seconds: "超时时间",
|
||||
};
|
||||
for (const [name, label] of Object.entries(labels)) {
|
||||
const widget = findWidget(node, name);
|
||||
if (widget) {
|
||||
widget.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
const ratioWidget = findWidget(node, "output_ratio");
|
||||
const customRatioWidget = findWidget(node, "custom_ratio");
|
||||
const updateRatioWidgets = () => {
|
||||
setWidgetVisible(node, customRatioWidget, ratioWidget?.value === CATAPI_CUSTOM_RATIO);
|
||||
markCanvasDirty();
|
||||
};
|
||||
if (ratioWidget && !ratioWidget.__catapiCallbackWrapped) {
|
||||
const originalCallback = ratioWidget.callback;
|
||||
ratioWidget.callback = function (value) {
|
||||
const result = originalCallback?.apply(this, arguments);
|
||||
updateRatioWidgets();
|
||||
return result;
|
||||
};
|
||||
ratioWidget.__catapiCallbackWrapped = true;
|
||||
}
|
||||
if (customRatioWidget && !customRatioWidget.__catapiCallbackWrapped) {
|
||||
const originalCallback = customRatioWidget.callback;
|
||||
customRatioWidget.callback = function () {
|
||||
const result = originalCallback?.apply(this, arguments);
|
||||
markCanvasDirty();
|
||||
return result;
|
||||
};
|
||||
customRatioWidget.__catapiCallbackWrapped = true;
|
||||
}
|
||||
|
||||
if (node.addCustomWidget && !node.widgets?.some((widget) => widget.name === "ratio_preview")) {
|
||||
node.addCustomWidget(makeRatioPreviewWidget(node));
|
||||
}
|
||||
|
||||
const advancedWidgets = ["api_key", "base_url", "timeout_seconds"].map((name) => findWidget(node, name));
|
||||
node.properties = node.properties || {};
|
||||
if (node.properties.catapiConnectionExpanded == null) {
|
||||
node.properties.catapiConnectionExpanded = false;
|
||||
}
|
||||
let connectionButton = findWidget(node, "connection_settings");
|
||||
const updateConnectionWidgets = () => {
|
||||
const expanded = Boolean(node.properties.catapiConnectionExpanded);
|
||||
for (const widget of advancedWidgets) {
|
||||
setWidgetVisible(node, widget, expanded);
|
||||
}
|
||||
if (connectionButton) {
|
||||
connectionButton.label = expanded ? "收起连接设置" : "⚙ 连接设置";
|
||||
connectionButton.name = "connection_settings";
|
||||
}
|
||||
};
|
||||
if (!connectionButton) {
|
||||
connectionButton = node.addWidget("button", "connection_settings", null, () => {
|
||||
node.properties.catapiConnectionExpanded = !node.properties.catapiConnectionExpanded;
|
||||
updateConnectionWidgets();
|
||||
});
|
||||
connectionButton.serialize = false;
|
||||
}
|
||||
|
||||
updateRatioWidgets();
|
||||
updateConnectionWidgets();
|
||||
updateCatapiImageInputs(node);
|
||||
if (node.outputs?.[0]) {
|
||||
node.outputs[0].label = "图像";
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.ChatImageBridge.CatAPISimpleUI",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||||
if (nodeData.name !== CATAPI_NODE_CLASS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const result = onNodeCreated?.apply(this, arguments);
|
||||
prepareCatapiNode(this);
|
||||
return result;
|
||||
};
|
||||
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function () {
|
||||
const result = onConnectionsChange?.apply(this, arguments);
|
||||
updateCatapiImageInputs(this);
|
||||
return result;
|
||||
};
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
const result = onConfigure?.apply(this, arguments);
|
||||
setTimeout(() => prepareCatapiNode(this), 0);
|
||||
return result;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user