Files
ketle-manandClaude Sonnet 5.5 5d3b4d0a21 docs/ui: call the judge a decision model everywhere user-facing
Only display strings change: the "VLA" row label becomes "Decision", the tooltip,
node DESCRIPTION, locales descriptions and README wording follow. Identifiers,
the saved-settings key and the /models response keys are unchanged, so saved
selections and behavior are unaffected. pyproject.toml is left as is on purpose
(pushing it triggers the Registry publish workflow).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-03 15:59:33 +09:00

120 lines
5.3 KiB
Python

import io
import os
import folder_paths
import numpy as np
import torch
from PIL import Image, ImageOps
try:
from comfy_execution.graph_utils import ExecutionBlocker
except ImportError: # 古いComfyUI: ブロックできないので黒画像で代替する
ExecutionBlocker = None
# 入力画像(socket)の最新値をノードIDごとに保持し、フロントのVLMチャットへ渡す(PNGバイト列)。
# サーバー再起動で消える。件数を抑えて無制限に増やさない。
_INPUT_CACHE: dict = {}
_INPUT_CACHE_MAX = 16
_INPUT_CACHE_SIDE = 1024
def get_cached_input_png(node_id):
return _INPUT_CACHE.get(str(node_id))
def _cache_input(node_id, tensor):
if node_id is None:
return
arr = (tensor[0].clamp(0, 1) * 255).byte().cpu().numpy()
img = Image.fromarray(arr)
img.thumbnail((_INPUT_CACHE_SIDE, _INPUT_CACHE_SIDE))
buf = io.BytesIO()
img.save(buf, format="PNG")
_INPUT_CACHE.pop(str(node_id), None)
_INPUT_CACHE[str(node_id)] = buf.getvalue()
while len(_INPUT_CACHE) > _INPUT_CACHE_MAX:
_INPUT_CACHE.pop(next(iter(_INPUT_CACHE)))
def _load_input_image(name):
"""inputフォルダの画像(フロントがアップロードしたI2Iソース)をIMAGEテンソルへ。"""
if not name or ".." in name.replace("\\", "/").split("/") or os.path.isabs(name):
return None
path = folder_paths.get_annotated_filepath(name)
if not os.path.isfile(path):
return None
img = ImageOps.exif_transpose(Image.open(path)).convert("RGB")
return torch.from_numpy(np.array(img).astype(np.float32) / 255.0)[None,]
class LiveChatStream:
"""Live Chat Stream: チャット(ストリーミング) → 画像生成プロンプト出力ノード。
チャットUI・ストリーミング・画像生成のキュー投入はフロントエンド(web/live_chat_stream.js)が行い、
チャット応答から抽出した <prompt>/<negative> をこのノードのウィジェットへ書き込む。
系統A(positive/negative)=通常のチャット画像生成、
系統B(chat_p/chat_n)=LLM/VLMの思考・返信そのものの画像化。
画像入力(image)があればそれをVLMへ渡し、出力(image)へ通す。I2Iに使える。
Python側は、その値を下流(CLIPTextEncode等)へ渡すだけ。
"""
@classmethod
def INPUT_TYPES(cls):
# ウィジェット値はワークフローJSONに位置インデックスで保存される。
# 追加は末尾のみ、並べ替え・削除は保存済みワークフローを壊す。
return {
"required": {},
"optional": {
"prompt_text": ("STRING", {"default": "", "multiline": True}),
"negative_text": ("STRING", {"default": "", "multiline": True}),
"response_text": ("STRING", {"default": "", "multiline": True}),
# 系統B(LLM/VLMの思考・表現の画像化)。末尾に追加(位置インデックス互換のため)
"chat_p_text": ("STRING", {"default": "", "multiline": True}),
"chat_n_text": ("STRING", {"default": "", "multiline": True}),
"thinking_text": ("STRING", {"default": "", "multiline": True}),
# 画像入力(socket)。ウィジェットではないので位置インデックスに影響しない。
"image": ("IMAGE",),
# フロントがドロップ/選択した画像をinputへアップロードしたファイル名(内部用)
"input_image_name": ("STRING", {"default": ""}),
},
"hidden": {"unique_id": "UNIQUE_ID"},
}
# 出力スロットも末尾追加のみ(保存済みワークフローのリンクはスロット番号で保持される)
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "IMAGE")
RETURN_NAMES = ("positive", "negative", "response", "chat_p", "chat_n", "thinking", "image")
FUNCTION = "run"
CATEGORY = "LiveChatStream"
DESCRIPTION = "Chat with an LLM / VLM and turn the streaming reply into images, optionally judged by a decision model. Image input / output enables I2I."
def run(self, prompt_text="", negative_text="", response_text="",
chat_p_text="", chat_n_text="", thinking_text="",
image=None, input_image_name="", unique_id=None):
# 優先順位: socket入力 > ドロップ/選択(アップロード済み) > なし
if image is not None:
out_image = image
_cache_input(unique_id, image)
else:
out_image = _load_input_image(input_image_name)
if out_image is None:
# 画像が無い時は下流(I2Iの分岐)を実行させない。古いComfyUIは黒画像で代替。
out_image = ExecutionBlocker(None) if ExecutionBlocker else torch.zeros((1, 64, 64, 3))
return (
(prompt_text or "").strip(),
(negative_text or "").strip(),
response_text or "",
(chat_p_text or "").strip(),
(chat_n_text or "").strip(),
thinking_text or "",
out_image,
)
NODE_CLASS_MAPPINGS = {
"LiveChatStream": LiveChatStream,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LiveChatStream": "Live Chat Stream",
}