From 2e3634fe6cf3b8abc4bec306c9585e627556a58a Mon Sep 17 00:00:00 2001 From: hujuying <105225728+hujuying@users.noreply.github.com> Date: Wed, 22 Oct 2025 03:01:58 +0800 Subject: [PATCH] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新 ComfyUI-ModelScope-API 支持Qwen3-VL系列模型反推 --- ModelScope-API-图片解析.json | 182 +++++++++++++++++++++++++++++++ __init__.py | 21 +++- modelscope_image_caption_node.py | 177 ++++++++++++++++++++++++++++++ 3 files changed, 375 insertions(+), 5 deletions(-) create mode 100644 ModelScope-API-图片解析.json create mode 100644 modelscope_image_caption_node.py diff --git a/ModelScope-API-图片解析.json b/ModelScope-API-图片解析.json new file mode 100644 index 0000000..420160e --- /dev/null +++ b/ModelScope-API-图片解析.json @@ -0,0 +1,182 @@ +{ + "id": "6d37ea84-d0ab-4123-8c8c-965b62a18230", + "revision": 0, + "last_node_id": 19, + "last_link_id": 31, + "nodes": [ + { + "id": 17, + "type": "easy showAnything", + "pos": [ + 2732.3588101299074, + 397.60778722933657 + ], + "size": [ + 482.4299999999994, + 553.9499999999997 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "label": "输入任何", + "name": "anything", + "shape": 7, + "type": "*", + "link": 31 + } + ], + "outputs": [ + { + "name": "output", + "type": "*", + "links": null + } + ], + "properties": { + "cnr_id": "comfyui-easy-use", + "ver": "1.3.3", + "Node name for S&R": "easy showAnything" + }, + "widgets_values": [ + "一位年轻美丽的亚洲女性,身着精致的淡紫色丝绸旗袍,旗袍上绣有金色祥云与花卉纹样,领口为高立领配盘扣设计,前襟开衩至大腿中部,展现优雅曲线。她佩戴半透明薄纱长袖,袖口拼接红色织锦并饰以金色龙纹刺绣,搭配同色系红色过膝袜。发型为古典盘发,插有金质流苏发簪与珠饰,刘海轻柔垂落,妆容精致,红唇微启,眼神温柔含情,直视镜头。背景为中式室内场景,墙面挂有水墨风格的山水或花鸟画作,色调柔和偏暖,营造静谧典雅氛围。整体构图采用中景人像视角,人物居中站立,略微侧身,突出服装细节与身材比例。光影柔和均匀,强调丝绸面料的光泽感与刺绣纹理,皮肤质感细腻光滑,呈现数字绘画的超写实美感。风格参考新中式美学与现代商业人像摄影,融合传统元素与时尚表现力,色彩主调为淡紫、金、红三色,辅以米白与浅棕背景,形成高级和谐的视觉层次。画面干净无杂物,焦点集中于人物神态与服饰工艺,适合用于AI绘图生成高质量东方美人肖像。" + ] + }, + { + "id": 5, + "type": "LoadImage", + "pos": [ + 1869.8370214843746, + 350.8748266601562 + ], + "size": [ + 290.8019714355469, + 570.7747192382812 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "label": "图像", + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 30 + ] + }, + { + "label": "遮罩", + "name": "MASK", + "type": "MASK" + } + ], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.49", + "Node name for S&R": "LoadImage", + "ue_properties": { + "version": "7.0.1", + "widget_ue_connectable": { + "image": true, + "upload": true + } + } + }, + "widgets_values": [ + "ebe510f1a069ba899eec0f82fefd3746d26f2f95ec2378a10ec1cd56cbc53642.png", + "image" + ], + "color": "#232", + "bgcolor": "#353" + }, + { + "id": 19, + "type": "ModelScopeImageCaptionNode", + "pos": [ + 2266.803982765229, + 414.0277931000381 + ], + "size": [ + 400, + 214 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 30 + } + ], + "outputs": [ + { + "name": "description", + "type": "STRING", + "links": [ + 31 + ] + } + ], + "properties": { + "Node name for S&R": "ModelScopeImageCaptionNode" + }, + "widgets_values": [ + "***已保存1个Token***", + "帮我拆解这张图片的提示词,要求从主体内容、场景设定、风格参考、色条色彩、构图视角、细节补充等这些角度来用文字描述图片,并汇总生成一个能让我用 ai绘画工具的文生图提示词,字数在 800 字以内,只要求输出汇总后的最终提示词,不需要无用信息。如:提示词标题,总字数等信息。", + "Qwen/Qwen3-VL-235B-A22B-Instruct", + 1000, + 0.7 + ] + } + ], + "links": [ + [ + 30, + 5, + 0, + 19, + 0, + "IMAGE" + ], + [ + 31, + 19, + 0, + 17, + 0, + "*" + ] + ], + "groups": [ + { + "id": 2, + "title": "魔搭-图片编辑", + "bounding": [ + 1663.396484375, + 122.82080078125, + 1826.526123046875, + 960.4658203125 + ], + "color": "#3f789e", + "font_size": 24, + "flags": {} + } + ], + "config": {}, + "extra": { + "ds": { + "scale": 0.620921323059155, + "offset": [ + -1125.4324556670972, + 206.079530787193 + ] + }, + "frontendVersion": "1.30.1" + }, + "version": 0.4 +} \ No newline at end of file diff --git a/__init__.py b/__init__.py index 8dde8ea..5da10ac 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,22 @@ +# 原有导入 from .modelscope_image_node import NODE_CLASS_MAPPINGS as IMAGE_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as IMAGE_DISPLAY_MAPPINGS from .modelscope_vision_node import NODE_CLASS_MAPPINGS as VISION_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as VISION_DISPLAY_MAPPINGS from .modelscope_text_node import NODE_CLASS_MAPPINGS as TEXT_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as TEXT_DISPLAY_MAPPINGS - -NODE_CLASS_MAPPINGS = {**IMAGE_MAPPINGS, **VISION_MAPPINGS, **TEXT_MAPPINGS} -NODE_DISPLAY_NAME_MAPPINGS = {**IMAGE_DISPLAY_MAPPINGS, **VISION_DISPLAY_MAPPINGS, **TEXT_DISPLAY_MAPPINGS} - + +# 新增:导入图像描述节点的映射 +from .modelscope_image_caption_node import NODE_CLASS_MAPPINGS as IMAGE_CAPTION_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS as IMAGE_CAPTION_DISPLAY_MAPPINGS + +# 合并所有节点映射(包含新增的图像描述节点) +NODE_CLASS_MAPPINGS = { + **IMAGE_MAPPINGS,** VISION_MAPPINGS, + **TEXT_MAPPINGS,** IMAGE_CAPTION_MAPPINGS # 新增 +} +NODE_DISPLAY_NAME_MAPPINGS = { + **IMAGE_DISPLAY_MAPPINGS,** VISION_DISPLAY_MAPPINGS, + **TEXT_DISPLAY_MAPPINGS,** IMAGE_CAPTION_DISPLAY_MAPPINGS # 新增 +} + __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] - + WEB_DIRECTORY = "./js" __version__ = "1.0.0" \ No newline at end of file diff --git a/modelscope_image_caption_node.py b/modelscope_image_caption_node.py new file mode 100644 index 0000000..fd50a24 --- /dev/null +++ b/modelscope_image_caption_node.py @@ -0,0 +1,177 @@ +import requests +import json +import time +import torch +import numpy as np +from PIL import Image +from io import BytesIO +import os +import base64 +import re +from .modelscope_image_node import load_config, load_api_tokens, save_api_tokens, tensor_to_base64_url + +# 检查openai库是否可用 +try: + from openai import OpenAI + OPENAI_AVAILABLE = True +except ImportError: + OPENAI_AVAILABLE = False + +class ModelScopeImageCaptionNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + if not OPENAI_AVAILABLE: + return { + "required": { + "error_message": ("STRING", { + "default": "请先安装openai库: pip install openai", + "multiline": True + }), + } + } + saved_tokens = load_api_tokens() + # 定义支持的模型列表 + supported_models = [ + "Qwen/Qwen3-VL-8B-Instruct", + "Qwen/Qwen3-VL-235B-A22B-Instruct" + ] + return { + "required": { + "image": ("IMAGE",), + "api_tokens": ("STRING", { + "default": f"***已保存{len(saved_tokens)}个Token***" if saved_tokens else "", + "placeholder": "请输入API Token(支持多个,用逗号/换行分隔)", + "multiline": True + }), + }, + "optional": { + "prompt": ("STRING", { + "multiline": True, + "default": "详细描述这张图片的内容,包括主体、背景、颜色、风格等信息" + }), + # 添加模型下拉选择 + "model": (supported_models, { + "default": "Qwen/Qwen3-VL-8B-Instruct" # 默认选中原模型 + }), + "max_tokens": ("INT", { + "default": 1000, + "min": 100, + "max": 4000 + }), + "temperature": ("FLOAT", { + "default": 0.7, + "min": 0.1, + "max": 2.0, + "step": 0.1 + }), + } + } + + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("description",) + FUNCTION = "generate_caption" + CATEGORY = "ModelScopeAPI" + + def parse_api_tokens(self, token_input): + """解析输入的多个API Token(支持逗号、分号、换行分隔)""" + if not token_input or token_input.strip() in ["", f"***已保存{len(load_api_tokens())}个Token***"]: + return load_api_tokens() + + # 支持多种分隔符拆分Token + tokens = re.split(r'[,;\n]+', token_input) + return [token.strip() for token in tokens if token.strip()] + + # 调整参数顺序,加入model参数(与INPUT_TYPES顺序匹配) + def generate_caption(self, image=None, api_tokens="", prompt="详细描述这张图片的内容", model="Qwen/Qwen3-VL-8B-Instruct", max_tokens=1000, temperature=0.7): + if not OPENAI_AVAILABLE: + return ("请先安装openai库: pip install openai",) + + # 解析Token列表(支持多个) + tokens = self.parse_api_tokens(api_tokens) + if not tokens: + raise Exception("请提供至少一个有效的API Token") + + # 保存新Token(如果有变化) + saved_tokens = load_api_tokens() + if api_tokens.strip() not in ["", f"***已保存{len(saved_tokens)}个Token***"]: + if save_api_tokens(tokens): + print(f"✅ 已保存 {len(tokens)} 个API Token") + else: + print("⚠️ API Token保存失败,但不影响当前使用") + + try: + print(f"🔍 开始生成图像描述...") + print(f"📝 提示词: {prompt}") + print(f"🤖 模型: {model}") # 显示选中的模型 + print(f"🔑 可用Token数量: {len(tokens)}") + + # 转换图像为base64格式 + image_url = tensor_to_base64_url(image) + print(f"🖼️ 图像已转换为base64格式") + + # 构建消息体 + messages = [{ + 'role': 'user', + 'content': [{ + 'type': 'text', + 'text': prompt, + }, { + 'type': 'image_url', + 'image_url': { + 'url': image_url, + }, + }], + }] + + # 轮询尝试每个Token + last_exception = None + for i, token in enumerate(tokens): + try: + print(f"🔄 尝试使用第 {i+1}/{len(tokens)} 个Token...") + + # 初始化OpenAI客户端 + client = OpenAI( + base_url='https://api-inference.modelscope.cn/v1', + api_key=token + ) + + # 调用API(使用选中的模型) + response = client.chat.completions.create( + model=model, + messages=messages, + max_tokens=max_tokens, + temperature=temperature, + stream=False + ) + + # 成功获取结果 + description = response.choices[0].message.content + print(f"✅ 第 {i+1} 个Token调用成功!") + print(f"📄 结果预览: {description[:100]}...") + return (description,) + + except Exception as e: + last_exception = e + print(f"❌ 第 {i+1} 个Token调用失败: {str(e)}") + if i < len(tokens) - 1: + print(f"⏳ 准备尝试下一个Token...") + + # 所有Token都失败 + raise Exception(f"所有Token均调用失败: {str(last_exception)}") + + except Exception as e: + error_msg = f"图像描述生成失败: {str(e)}" + print(f"❌ {error_msg}") + return (error_msg,) + +# 节点映射 +NODE_CLASS_MAPPINGS = { + "ModelScopeImageCaptionNode": ModelScopeImageCaptionNode +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "ModelScopeImageCaptionNode": "ModelScope 图像描述生成" +} \ No newline at end of file