add grok image api

This commit is contained in:
qnsh
2026-09-02 11:07:03 +08:00
parent 6d5391ecc0
commit 980242acb4
6 changed files with 941 additions and 109 deletions
+3 -1
View File
@@ -2,6 +2,7 @@ from comfy_api.latest import ComfyExtension, io
from typing_extensions import override
from .gemini.gemini_image_node import *
from .grok.grok_image_node import *
from .gemini.gemini_image_preset_node import *
from .gemini.gemini_tts_node import *
from .gemini.gemini_tts_multi_node import *
@@ -27,7 +28,8 @@ class APIExtension(ComfyExtension):
@override
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
GeminiImage,
GeminiImage,
GrokImage,
GeminiTTS,
GeminiTTSMulti,
GeminiSTT,
+25 -7
View File
@@ -6,7 +6,10 @@
"api_key": "put your key here",
"timeout": 120,
"api_protocol": "openai-completions",
"models": ["gpt-4o", "gpt-5"]
"models": [
"gpt-4o",
"gpt-5"
]
},
{
"api-name": "OpenAI Responses",
@@ -14,14 +17,29 @@
"api_key": "put your key here",
"timeout": 120,
"api_protocol": "openai-responses",
"models": ["gpt-5"]
"models": [
"gpt-5"
]
}
],
"skills": {
"paths": ["skills"],
"paths": [
"skills"
],
"allow_call": false
},
"gemini-image":{
"grok-image": {
"base_url": "https://api.x.ai/v1",
"api_key": "put your key here",
"timeout": 120,
"models": [
"grok-imagine-image-2.0",
"grok-imagine-image-quality",
"grok-imagine-image-pro",
"grok-imagine-image"
]
},
"gemini-image": {
"base_url": "https://generativelanguage.googleapis.com/v1beta/models",
"api_key": "put your key here",
"timeout": 120,
@@ -31,7 +49,7 @@
"gemini-3.1-flash-image-preview"
]
},
"gemini-tts": {
"gemini-tts": {
"base_url": "https://generativelanguage.googleapis.com/v1beta/models",
"api_key": "put your key here",
"timeout": 120,
@@ -96,9 +114,9 @@
"FireRedTeam/FireRed-Image-Edit-1.1"
]
},
"proxy":{
"proxy": {
"enable": false,
"http": "",
"https": "http://127.0.0.1:7890"
}
}
}
+525
View File
@@ -0,0 +1,525 @@
import os
import json
import base64
import requests
import torch
import numpy as np
from PIL import Image
from io import BytesIO
from typing import Optional, List, Dict, Any, Tuple
from comfy_api.latest import ComfyExtension, io
from ..utils.image_utils import tensor_to_base64_string
from ..utils.config_utils import get_config_section
DEFAULT_MODELS = [
"grok-imagine-image-2.0",
"grok-imagine-image-quality",
"grok-imagine-image-pro",
"grok-imagine-image"
]
class GrokImage(io.ComfyNode):
"""
这个节点使用 xAI Grok Image API 生成或者修改图片
"""
@classmethod
def _load_models_from_config(cls) -> List[str]:
"""
从 config.json 中加载模型列表
如果获取不到,返回默认模型列表
"""
try:
config_path = os.path.join(os.path.dirname(__file__), '..', "config.json")
if not os.path.exists(config_path):
return DEFAULT_MODELS
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
if 'grok-image' in config and 'models' in config['grok-image']:
models = config['grok-image']['models']
if isinstance(models, list) and len(models) > 0:
return models
return DEFAULT_MODELS
except Exception:
return DEFAULT_MODELS
@classmethod
def _load_config_credentials(cls, config_options: Optional[dict] = None) -> Tuple[str, str, int]:
"""
从 config.json 中加载并验证 API 凭据,如果提供了 config_options 则优先使用
返回 (base_url, api_key, timeout) 元组
"""
# 如果提供了配置覆盖,则使用覆盖配置
if config_options is not None:
base_url = config_options.get('base_url', '').strip()
api_key = config_options.get('api_key', '').strip()
timeout = config_options.get('timeout', 120)
# 如果覆盖配置中有有效的 base_url 和 api_key,则直接返回
if base_url and api_key:
return base_url, api_key, timeout
# 否则从配置文件加载
config_path = os.path.join(os.path.dirname(__file__), '..', "config.json")
# 检查配置文件是否存在
if not os.path.exists(config_path):
raise FileNotFoundError(f"Config file not found: {config_path}")
try:
with open(config_path, 'r', encoding='utf-8') as f:
config = json.load(f)
# 检查是否存在 grok-image 配置段
if 'grok-image' not in config:
raise ValueError("Missing 'grok-image' section in config file")
grok_config = config['grok-image']
# 获取并验证 base_url (默认 https://api.x.ai/v1)
base_url = grok_config.get('base_url', 'https://api.x.ai/v1')
base_url = base_url.strip() if isinstance(base_url, str) else str(base_url).strip()
if not base_url:
base_url = "https://api.x.ai/v1"
# 获取并验证 api_key
if 'api_key' not in grok_config:
raise ValueError("Missing 'api_key' in grok-image section")
api_key = grok_config['api_key'].strip() if isinstance(grok_config['api_key'], str) else str(grok_config['api_key']).strip()
if not api_key:
raise ValueError("api_key cannot be empty")
# 获取 timeout 参数,默认值为 120 秒
timeout = grok_config.get('timeout', 120)
if isinstance(timeout, str):
try:
timeout = int(timeout)
except ValueError:
timeout = 120
# 如果有配置覆盖,则使用覆盖的值(如果提供了)
if config_options is not None:
if config_options.get('base_url', '').strip():
base_url = config_options['base_url'].strip()
if config_options.get('api_key', '').strip():
api_key = config_options['api_key'].strip()
if config_options.get('timeout'):
timeout = config_options['timeout']
return base_url, api_key, timeout
except Exception as e:
raise ValueError(f"Config loading error: {str(e)}")
@classmethod
def _get_proxy_config(cls, proxy_options: Optional[dict] = None) -> Optional[Dict[str, str]]:
"""
从 config.json 中获取代理配置,如果提供了 proxy_options 则优先使用
返回 proxies 字典或 None
"""
# 如果提供了代理覆盖配置
if proxy_options is not None:
if not proxy_options.get('enable', False):
return None
proxies = {}
if proxy_options.get('http', '').strip():
proxies['http'] = proxy_options['http'].strip()
if proxy_options.get('https', '').strip():
proxies['https'] = proxy_options['https'].strip()
return proxies if proxies else None
# 否则从配置文件加载
try:
proxy_config = get_config_section('proxy')
if not proxy_config or not proxy_config.get('enable', False):
return None
proxies = {}
if proxy_config.get('http'):
proxies['http'] = proxy_config['http']
if proxy_config.get('https'):
proxies['https'] = proxy_config['https']
return proxies if proxies else None
except Exception:
return None
@classmethod
def define_schema(cls) -> io.Schema:
"""
返回 GrokImage 节点 schema
"""
model_options = cls._load_models_from_config()
default_model = model_options[0]
return io.Schema(
node_id="YCYY_Grok_Image_API",
display_name="Grok Image API",
category="YCYY/API/image",
inputs=[
io.Image.Input(
id="images",
optional=True,
tooltip="Optional image(s) for image-to-image editing. Grok supports up to 3 reference images (1 for pro model)."
),
io.AnyType.Input(
id="config_options",
optional=True,
tooltip="Optional configuration override from YCYY API Config Options"
),
io.AnyType.Input(
id="proxy_options",
optional=True,
tooltip="Optional proxy configuration override from YCYY API Proxy Options"
),
io.String.Input(
id="prompt",
multiline=True,
tooltip="The text prompt used to generate or edit the image"
),
io.Combo.Input(
id="model",
options=model_options,
default=default_model,
tooltip="Grok image model"
),
io.Combo.Input(
id="aspect_ratio",
options=[
"auto",
"1:1",
"2:3",
"3:2",
"3:4",
"4:3",
"9:16",
"16:9",
"9:19.5",
"19.5:9",
"9:20",
"20:9",
"1:2",
"2:1"
],
default="auto",
tooltip="Aspect ratio of the output image. 'auto' matches input image in edit mode or generates 1:1."
),
io.Combo.Input(
id="resolution",
options=[
"1K",
"2K"
],
default="1K",
tooltip="Resolution of the output image (1K or 2K)."
),
io.Combo.Input(
id="quality",
options=[
"default",
"medium",
"low"
],
default="medium",
tooltip="Quality level, supported only by the grok-imagine-image-2.0 model."
),
io.Int.Input(
id="number_of_images",
default=1,
min=1,
max=10,
step=1,
tooltip="Number of images to generate (1 to 10)."
),
io.Int.Input(
id="seed",
min=0,
max=0xFFFFFFFFFFFFFFFF,
default=0,
control_after_generate=True,
tooltip="Random seed for generation."
)
],
outputs=[
io.Image.Output(),
io.String.Output()
],
description="This node uses the xAI Grok Image API to generate or edit images."
)
@classmethod
def execute(
cls,
prompt: str,
model: str,
aspect_ratio: str,
resolution: str,
quality: str,
number_of_images: int,
seed: int,
images: Optional[torch.Tensor] = None,
config_options: Optional[dict] = None,
proxy_options: Optional[dict] = None
) -> io.NodeOutput:
# 加载配置和凭据,如果提供了 config_options 则使用覆盖配置
base_url, api_key, timeout = cls._load_config_credentials(config_options)
# 获取代理配置,如果提供了 proxy_options 则使用覆盖配置
proxies = cls._get_proxy_config(proxy_options)
if not prompt or not prompt.strip():
raise ValueError("prompt cannot be empty")
clean_base_url = base_url.rstrip("/")
if clean_base_url.endswith("/images/generations") or clean_base_url.endswith("/images/edits"):
clean_base_url = clean_base_url.rsplit("/images", 1)[0]
elif clean_base_url.endswith("/images"):
clean_base_url = clean_base_url.rsplit("/images", 1)[0]
gen_url = f"{clean_base_url}/images/generations"
edit_url = f"{clean_base_url}/images/edits"
if images is not None:
return cls._edit_images(
api_url=edit_url,
api_key=api_key,
prompt=prompt,
model=model,
aspect_ratio=aspect_ratio,
resolution=resolution,
quality=quality,
number_of_images=number_of_images,
seed=seed,
images=images,
timeout=timeout,
proxies=proxies
)
else:
return cls._generate_images(
api_url=gen_url,
api_key=api_key,
prompt=prompt,
model=model,
aspect_ratio=aspect_ratio,
resolution=resolution,
quality=quality,
number_of_images=number_of_images,
seed=seed,
timeout=timeout,
proxies=proxies
)
@classmethod
def _generate_images(
cls,
api_url: str,
api_key: str,
prompt: str,
model: str,
aspect_ratio: str,
resolution: str,
quality: str,
number_of_images: int,
seed: int,
timeout: int,
proxies: Optional[dict] = None
) -> io.NodeOutput:
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
payload = {
"model": model,
"prompt": prompt,
"n": number_of_images,
"seed": seed,
"response_format": "b64_json",
"resolution": resolution.lower() if resolution else "1k"
}
if aspect_ratio != "auto":
payload["aspect_ratio"] = aspect_ratio
if quality and quality != "default":
payload["quality"] = quality
try:
resp = requests.post(api_url, headers=headers, json=payload, timeout=timeout, proxies=proxies)
return cls._parse_response(resp, model=model, timeout=timeout, proxies=proxies)
except Exception as e:
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": f"The API request failed. Please check if the interface address and key are correct: {str(e)}"
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
@classmethod
def _edit_images(
cls,
api_url: str,
api_key: str,
prompt: str,
model: str,
aspect_ratio: str,
resolution: str,
quality: str,
number_of_images: int,
seed: int,
images: torch.Tensor,
timeout: int,
proxies: Optional[dict] = None
) -> io.NodeOutput:
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
input_images = []
total_imgs = images.shape[0] if len(images.shape) >= 4 else 1
max_imgs = 1 if "pro" in model else 3
num_to_take = min(total_imgs, max_imgs)
for idx in range(num_to_take):
img_tensor = images[idx].unsqueeze(0) if len(images.shape) >= 4 else images.unsqueeze(0)
b64_str = tensor_to_base64_string(img_tensor, mime_type="image/png")
input_images.append({
"url": f"data:image/png;base64,{b64_str}"
})
payload = {
"model": model,
"prompt": prompt,
"images": input_images,
"n": number_of_images,
"seed": seed,
"response_format": "b64_json",
"resolution": resolution.lower() if resolution else "1k"
}
if aspect_ratio != "auto":
payload["aspect_ratio"] = aspect_ratio
try:
resp = requests.post(api_url, headers=headers, json=payload, timeout=timeout, proxies=proxies)
return cls._parse_response(resp, model=model, timeout=timeout, proxies=proxies)
except Exception as e:
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": f"The API request failed. Please check if the interface address and key are correct: {str(e)}"
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
@classmethod
def _parse_response(
cls,
resp: requests.Response,
model: str = "",
timeout: int = 120,
proxies: Optional[dict] = None
) -> io.NodeOutput:
# 检查 HTTP 状态码
if resp.status_code != 200:
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": f"API request returns an error. status_code: {resp.status_code}, error_reason: {resp.text}"
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
# 检查返回内容是否为空
if not resp.text.strip():
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": "The API returns an empty content"
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
try:
data = resp.json()
except Exception as json_exception:
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": f"The API returned a JSON parsing failure: {str(json_exception)}"
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
# 提取图像数据列表
items = data.get("data", [])
if not items:
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": "Image data not found in response",
"raw_response": data
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
image_tensors = []
revised_prompts = []
for item in items:
if not isinstance(item, dict):
continue
if item.get("revised_prompt"):
revised_prompts.append(item["revised_prompt"])
b64_json = item.get("b64_json")
image_url = item.get("url")
if b64_json:
try:
image_bytes = base64.b64decode(b64_json)
pil_image = Image.open(BytesIO(image_bytes)).convert("RGB")
img_np = np.array(pil_image).astype(np.float32) / 255.0
img_tensor = torch.from_numpy(img_np).unsqueeze(0)
image_tensors.append(img_tensor)
except Exception:
pass
elif image_url:
try:
img_resp = requests.get(image_url, timeout=timeout, proxies=proxies)
if img_resp.status_code == 200:
pil_image = Image.open(BytesIO(img_resp.content)).convert("RGB")
img_np = np.array(pil_image).astype(np.float32) / 255.0
img_tensor = torch.from_numpy(img_np).unsqueeze(0)
image_tensors.append(img_tensor)
except Exception:
pass
if not image_tensors:
empty_image = cls._create_empty_image()
err_info = {
"success": False,
"message": "Failed to decode or download any image from response",
"raw_response": data
}
return io.NodeOutput(empty_image, json.dumps(err_info, ensure_ascii=False))
if len(image_tensors) == 1:
final_tensor = image_tensors[0]
else:
final_tensor = torch.cat(image_tensors, dim=0)
usage = data.get("usage", {})
info = {
"success": True,
"model": model,
"created": data.get("created"),
"usage": usage,
"revised_prompts": revised_prompts
}
return io.NodeOutput(final_tensor, json.dumps(info, ensure_ascii=False, indent=2))
@classmethod
def _create_empty_image(cls) -> torch.Tensor:
"""创建空图像"""
try:
return torch.zeros(1, 512, 512, 3, dtype=torch.float32)
except Exception:
return None
+179 -35
View File
@@ -3,44 +3,101 @@
"display_name": "OpenAI Text API",
"description": "OpenAI and OpenAI-compatible text, image and video API.",
"inputs": {
"api_name": {"name": "api_name"},
"model": {"name": "model"},
"system_prompt": {"name": "system_prompt"},
"user_prompt": {"name": "user_prompt"},
"persist_context": {"name": "persist_context", "tooltip": "Persist chat context between calls"},
"clear_history": {"name": "clear_history", "tooltip": "Clear the current conversation history"},
"stream": {"name": "stream", "tooltip": "If true, the model response is streamed to the client as it is generated using server-sent events (SSE)."},
"images": {"name": "images", "tooltip": "Optional image input"},
"videos": {"name": "videos", "tooltip": "Optional video input; unsupported APIs return an error"},
"config_options": {"name": "config_options", "tooltip": "Configuration override"},
"proxy_options": {"name": "proxy_options", "tooltip": "Proxy override"},
"advanced_options": {"name": "advanced_options", "tooltip": "Advanced model options"},
"skill_options": {"name": "skill_options", "tooltip": "Optional progressive Skill configuration from OpenAI Text Skill Options"}
"api_name": {
"name": "api_name"
},
"model": {
"name": "model"
},
"system_prompt": {
"name": "system_prompt"
},
"user_prompt": {
"name": "user_prompt"
},
"persist_context": {
"name": "persist_context",
"tooltip": "Persist chat context between calls"
},
"clear_history": {
"name": "clear_history",
"tooltip": "Clear the current conversation history"
},
"stream": {
"name": "stream",
"tooltip": "If true, the model response is streamed to the client as it is generated using server-sent events (SSE)."
},
"images": {
"name": "images",
"tooltip": "Optional image input"
},
"videos": {
"name": "videos",
"tooltip": "Optional video input; unsupported APIs return an error"
},
"config_options": {
"name": "config_options",
"tooltip": "Configuration override"
},
"proxy_options": {
"name": "proxy_options",
"tooltip": "Proxy override"
},
"advanced_options": {
"name": "advanced_options",
"tooltip": "Advanced model options"
},
"skill_options": {
"name": "skill_options",
"tooltip": "Optional progressive Skill configuration from OpenAI Text Skill Options"
}
},
"outputs": {
"0": {"name": "Result"},
"1": {"name": "Conversation", "tooltip": "Full redacted Pi Skill Agent ledger for Skill calls"},
"2": {"name": "Skill Trace"}
"0": {
"name": "Result"
},
"1": {
"name": "Conversation",
"tooltip": "Full redacted Pi Skill Agent ledger for Skill calls"
},
"2": {
"name": "Skill Trace"
}
}
},
"YCYY_Preview_API_Result": {
"display_name": "Preview API Result",
"description": "Render text as a Markdown preview with copy support.",
"inputs": {
"source": {"name": "source", "tooltip": "Markdown or plain text to preview"}
"source": {
"name": "source",
"tooltip": "Markdown or plain text to preview"
}
},
"outputs": {
"0": {"name": "text"}
"0": {
"name": "text"
}
}
},
"YCYY_OpenAI_Text_Skill_Options": {
"display_name": "OpenAI Text Skill Options",
"description": "Load a local SKILL.md package for progressive use by OpenAI Text API.",
"inputs": {
"skill_name": {"name": "skill_name", "tooltip": "Select a configured local Skill"},
"description": {"name": "Skill description", "tooltip": "Read-only description of the selected Skill"}
"skill_name": {
"name": "skill_name",
"tooltip": "Select a configured local Skill"
},
"description": {
"name": "Skill description",
"tooltip": "Read-only description of the selected Skill"
}
},
"outputs": {"0": {"name": "skill_options"}}
"outputs": {
"0": {
"name": "skill_options"
}
}
},
"YCYY_OpenAI_Text_Advanced_Options": {
"display_name": "OpenAI Text Advanced Options",
@@ -51,7 +108,11 @@
"tooltip": "A JSON object containing protocol/API-specific request parameters. Example:\n{\n \"temperature\": 0.7,\n \"reasoning\": {\n \"effort\": \"medium\"\n }\n}"
}
},
"outputs": {"0": {"name": "advanced_options"}}
"outputs": {
"0": {
"name": "advanced_options"
}
}
},
"YCYY_Gemini_Image_API": {
"display_name": "Gemini Image API",
@@ -100,6 +161,60 @@
}
}
},
"YCYY_Grok_Image_API": {
"display_name": "Grok Image API",
"description": "This node uses the xAI Grok Image API to generate or edit images.",
"inputs": {
"images": {
"name": "images",
"tooltip": "Optional reference image(s) for editing. Up to 3 images (1 for pro model)."
},
"config_options": {
"name": "config_options",
"tooltip": "Optional configuration override from YCYY API Config Options"
},
"proxy_options": {
"name": "proxy_options",
"tooltip": "Optional proxy configuration override from YCYY API Proxy Options"
},
"prompt": {
"name": "prompt",
"tooltip": "The prompt used to generate or edit the image"
},
"model": {
"name": "model",
"tooltip": "Grok image model"
},
"aspect_ratio": {
"name": "aspect_ratio",
"tooltip": "Aspect ratio of the generated image. 'auto' matches input image in edit mode or generates 1:1."
},
"resolution": {
"name": "resolution",
"tooltip": "Resolution of the output image (1K or 2K)."
},
"quality": {
"name": "quality",
"tooltip": "Quality level, supported only by the grok-imagine-image-2.0 model."
},
"number_of_images": {
"name": "number_of_images",
"tooltip": "Number of images to generate (1 to 10)."
},
"seed": {
"name": "seed",
"tooltip": "Seed to use for generation."
}
},
"outputs": {
"0": {
"name": "Image"
},
"1": {
"name": "Info"
}
}
},
"YCYY_Ollama_VLM_API": {
"display_name": "Ollama VLM API",
"description": "This node uses the Ollama VLM model for image reasoning and analysis.",
@@ -475,20 +590,49 @@
"display_name": "ModelScope Image Edit API",
"description": "This node uses the ModelScope API to edit an input image.",
"inputs": {
"image": {"name": "image", "tooltip": "Input image to edit"},
"config_options": {"name": "config_options", "tooltip": "Optional configuration override from YCYY API Config Options"},
"proxy_options": {"name": "proxy_options", "tooltip": "Optional proxy configuration override from YCYY API Proxy Options"},
"prompt": {"name": "prompt", "tooltip": "Image editing instruction"},
"negative_prompt": {"name": "negative_prompt", "tooltip": "Negative prompt"},
"model": {"name": "model"},
"width": {"name": "width"},
"height": {"name": "height"},
"steps": {"name": "steps"},
"guidance": {"name": "guidance"}
"image": {
"name": "image",
"tooltip": "Input image to edit"
},
"config_options": {
"name": "config_options",
"tooltip": "Optional configuration override from YCYY API Config Options"
},
"proxy_options": {
"name": "proxy_options",
"tooltip": "Optional proxy configuration override from YCYY API Proxy Options"
},
"prompt": {
"name": "prompt",
"tooltip": "Image editing instruction"
},
"negative_prompt": {
"name": "negative_prompt",
"tooltip": "Negative prompt"
},
"model": {
"name": "model"
},
"width": {
"name": "width"
},
"height": {
"name": "height"
},
"steps": {
"name": "steps"
},
"guidance": {
"name": "guidance"
}
},
"outputs": {
"0": {"name": "Image"},
"1": {"name": "String"}
"0": {
"name": "Image"
},
"1": {
"name": "String"
}
}
},
"YCYY_Image_Compare": {
@@ -505,4 +649,4 @@
}
}
}
}
}
+209 -65
View File
@@ -3,44 +3,101 @@
"display_name": "OpenAI 文本 API",
"description": "支持 OpenAI 及兼容接口的文本、图像和视频请求。",
"inputs": {
"api_name": {"name": "API 名称"},
"model": {"name": "模型"},
"system_prompt": {"name": "系统提示词"},
"user_prompt": {"name": "用户提示词"},
"persist_context": {"name": "保持上下文", "tooltip": "在多次调用之间保持对话上下文"},
"clear_history": {"name": "清除历史", "tooltip": "清除当前会话历史"},
"stream": {"name": "流式输出", "tooltip": "设为 true 时,模型响应会在生成过程中通过服务器发送事件(SSE)流式传输到客户端。"},
"images": {"name": "图像", "tooltip": "可选图像输入"},
"videos": {"name": "视频", "tooltip": "可选视频输入;不支持视频的接口会返回错误"},
"config_options": {"name": "配置选项", "tooltip": "配置覆盖"},
"proxy_options": {"name": "代理选项", "tooltip": "代理覆盖"},
"advanced_options": {"name": "高级选项", "tooltip": "模型高级参数"},
"skill_options": {"name": "Skill 选项", "tooltip": "来自 OpenAI 文本 Skill 选项节点的可选渐进式 Skill 配置"}
"api_name": {
"name": "API 名称"
},
"model": {
"name": "模型"
},
"system_prompt": {
"name": "系统提示词"
},
"user_prompt": {
"name": "用户提示词"
},
"persist_context": {
"name": "保持上下文",
"tooltip": "在多次调用之间保持对话上下文"
},
"clear_history": {
"name": "清除历史",
"tooltip": "清除当前会话历史"
},
"stream": {
"name": "流式输出",
"tooltip": "设为 true 时,模型响应会在生成过程中通过服务器发送事件(SSE)流式传输到客户端。"
},
"images": {
"name": "图像",
"tooltip": "可选图像输入"
},
"videos": {
"name": "视频",
"tooltip": "可选视频输入;不支持视频的接口会返回错误"
},
"config_options": {
"name": "配置选项",
"tooltip": "配置覆盖"
},
"proxy_options": {
"name": "代理选项",
"tooltip": "代理覆盖"
},
"advanced_options": {
"name": "高级选项",
"tooltip": "模型高级参数"
},
"skill_options": {
"name": "Skill 选项",
"tooltip": "来自 OpenAI 文本 Skill 选项节点的可选渐进式 Skill 配置"
}
},
"outputs": {
"0": {"name": "结果"},
"1": {"name": "对话", "tooltip": "Skill 调用时输出完整脱敏的 Pi Skill Agent ledger"},
"2": {"name": "Skill 调用记录"}
"0": {
"name": "结果"
},
"1": {
"name": "对话",
"tooltip": "Skill 调用时输出完整脱敏的 Pi Skill Agent ledger"
},
"2": {
"name": "Skill 调用记录"
}
}
},
"YCYY_Preview_API_Result": {
"display_name": "Preview API Result",
"description": "将文本渲染为 Markdown 网页预览,并支持复制。",
"inputs": {
"source": {"name": "文本", "tooltip": "要预览的 Markdown 或纯文本"}
"source": {
"name": "文本",
"tooltip": "要预览的 Markdown 或纯文本"
}
},
"outputs": {
"0": {"name": "文本"}
"0": {
"name": "文本"
}
}
},
"YCYY_OpenAI_Text_Skill_Options": {
"display_name": "OpenAI 文本 Skill 选项",
"description": "加载本地 SKILL.md,并供 OpenAI 文本 API 渐进式读取引用资料。",
"inputs": {
"skill_name": {"name": "Skill 名称", "tooltip": "选择配置中发现的本地 Skill"},
"description": {"name": "Skill 描述", "tooltip": "当前所选 Skill 的只读描述"}
"skill_name": {
"name": "Skill 名称",
"tooltip": "选择配置中发现的本地 Skill"
},
"description": {
"name": "Skill 描述",
"tooltip": "当前所选 Skill 的只读描述"
}
},
"outputs": {"0": {"name": "Skill 选项"}}
"outputs": {
"0": {
"name": "Skill 选项"
}
}
},
"YCYY_OpenAI_Text_Advanced_Options": {
"display_name": "OpenAI 文本高级选项(JSON)",
@@ -51,7 +108,11 @@
"tooltip": "填写协议或 API 特有请求参数的 JSON 对象。示例:\n{\n \"temperature\": 0.7,\n \"reasoning\": {\n \"effort\": \"medium\"\n }\n}"
}
},
"outputs": {"0": {"name": "高级选项"}}
"outputs": {
"0": {
"name": "高级选项"
}
}
},
"YCYY_Gemini_Image_API": {
"display_name": "Gemini 图像 API",
@@ -61,11 +122,11 @@
"name": "图像",
"tooltip": "可选的图像,用作模型的上下文"
},
"config_options":{
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options":{
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
@@ -101,19 +162,73 @@
}
}
},
"YCYY_Ollama_VLM_API":{
"display_name": "Ollama 视觉 API",
"description": "这个节点使用Ollama VLM 模型进行图片推理分析",
"YCYY_Grok_Image_API": {
"display_name": "Grok 图像 API",
"description": "该节点使用 xAI Grok 图像 API 生成或编辑图像",
"inputs": {
"images":{
"images": {
"name": "图像",
"tooltip": "用于分析的图像(支持多个)"
"tooltip": "可选的参考图像,用于图生图编辑。Grok 支持最多 3 张参考图片(pro 模型支持 1 张)。"
},
"config_options":{
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options":{
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
"prompt": {
"name": "提示词",
"tooltip": "用于生成或编辑图像的提示词"
},
"model": {
"name": "模型",
"tooltip": "Grok 图像模型"
},
"aspect_ratio": {
"name": "图像宽高比",
"tooltip": "输出图像的宽高比。'auto' 在编辑模式下匹配输入图像,或生成 1:1 正方形。"
},
"resolution": {
"name": "图像分辨率",
"tooltip": "控制输出图像的分辨率(1K 或 2K)。"
},
"quality": {
"name": "质量",
"tooltip": "质量等级,仅 grok-imagine-image-2.0 模型支持。"
},
"number_of_images": {
"name": "生成数量",
"tooltip": "要生成的图像数量(1 到 10)。"
},
"seed": {
"name": "种子",
"tooltip": "用于生成噪声的随机种子。"
}
},
"outputs": {
"0": {
"name": "图像"
},
"1": {
"name": "信息"
}
}
},
"YCYY_Ollama_VLM_API": {
"display_name": "Ollama 视觉 API",
"description": "这个节点使用Ollama VLM 模型进行图片推理分析",
"inputs": {
"images": {
"name": "图像",
"tooltip": "用于分析的图像(支持多个)"
},
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
@@ -127,26 +242,26 @@
}
}
},
"YCYY_Ollama_LLM_API":{
"YCYY_Ollama_LLM_API": {
"display_name": "Ollama 文本 API",
"description": "该节点使用 Ollama 大语言模型进行对话",
"inputs": {
"config_options":{
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options":{
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
"advanced_options":{
"advanced_options": {
"name": "高级选项",
"tooltip": "模型的高级选项(可选),接受来自 Ollama 文本高级选项节点的输入。"
},
"system_prompt":{
"system_prompt": {
"name": "system_prompt"
},
"user_prompt":{
"user_prompt": {
"name": "user_prompt"
},
"model": {
@@ -172,17 +287,17 @@
}
}
},
"YCYY_Ollama_LLM_Advanced_Options":{
"YCYY_Ollama_LLM_Advanced_Options": {
"display_name": "Ollama 文本高级选项",
"description": "该节点为 ollama 大语言模型提供了高级选项。",
"inputs": {
"max_tokens":{
"inputs": {
"max_tokens": {
"name": "max_tokens"
},
"temperature":{
"temperature": {
"name": "temperature"
},
"top_p":{
"top_p": {
"name": "top_p"
},
"enable_max_tokens": {
@@ -261,19 +376,19 @@
}
}
},
"YCYY_Gemini_Image_Preset":{
"YCYY_Gemini_Image_Preset": {
"display_name": "Gemini 图像预设",
"description": "该节点为 Gemini 图像 API 提供预设值",
"inputs": {
"preset":{
"preset": {
"name": "预设名称",
"tooltip": "预设名称"
},
"description":{
"description": {
"name": "预设描述",
"tooltip": "预设描述"
},
"prompt":{
"prompt": {
"name": "预设提示词",
"tooltip": "预设提示词"
}
@@ -332,11 +447,11 @@
"name": "text",
"tooltip": "需要转换为语音的文本"
},
"config_options":{
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options":{
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
@@ -406,11 +521,11 @@
"name": "prompt",
"tooltip": "用于指导转录的提示词。您可以要求特定的格式或指令。"
},
"config_options":{
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options":{
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
@@ -431,11 +546,11 @@
"display_name": "魔搭图像API",
"description": "该节点使用 ModelScope API 生成图像",
"inputs": {
"config_options":{
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options":{
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
@@ -476,20 +591,49 @@
"display_name": "魔搭图像编辑API",
"description": "该节点使用 ModelScope API 编辑输入图像",
"inputs": {
"image": {"name": "输入图像", "tooltip": "要编辑的输入图像"},
"config_options": {"name": "配置选项", "tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"},
"proxy_options": {"name": "代理选项", "tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"},
"prompt": {"name": "prompt", "tooltip": "图像编辑指令"},
"negative_prompt": {"name": "negative_prompt", "tooltip": "负向提示"},
"model": {"name": "模型"},
"width": {"name": "宽度"},
"height": {"name": "高度"},
"steps": {"name": "采样步数"},
"guidance": {"name": "提示词引导系数"}
"image": {
"name": "输入图像",
"tooltip": "要编辑的输入图像"
},
"config_options": {
"name": "配置选项",
"tooltip": "可选配置覆盖选项,来自 YCYY API 配置选项"
},
"proxy_options": {
"name": "代理选项",
"tooltip": "可选代理覆盖选项,来自 YCYY API 代理选项"
},
"prompt": {
"name": "prompt",
"tooltip": "图像编辑指令"
},
"negative_prompt": {
"name": "negative_prompt",
"tooltip": "负向提示"
},
"model": {
"name": "模型"
},
"width": {
"name": "宽度"
},
"height": {
"name": "高度"
},
"steps": {
"name": "采样步数"
},
"guidance": {
"name": "提示词引导系数"
}
},
"outputs": {
"0": {"name": "图像"},
"1": {"name": "字符串"}
"0": {
"name": "图像"
},
"1": {
"name": "字符串"
}
}
},
"YCYY_Image_Compare": {
@@ -506,4 +650,4 @@
}
}
}
}
}
-1
View File
@@ -1 +0,0 @@