diff --git a/ModelScope-API-图片解析.json b/ModelScope-API-图片解析.json index 6e0b319..864c02c 100644 --- a/ModelScope-API-图片解析.json +++ b/ModelScope-API-图片解析.json @@ -1,183 +1 @@ -{ - "id": "6d37ea84-d0ab-4123-8c8c-965b62a18230", - "revision": 0, - "last_node_id": 20, - "last_link_id": 33, - "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": 33 - } - ], - "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": [ - "一位身着精致淡紫色缎面旗袍的年轻东方女性,旗袍上饰有金色龙凤刺绣图案,高开叉设计优雅地露出修长美腿,搭配红色蕾丝袖口与红色长筒袜。她发髻高挽,佩戴华丽金色流苏发簪,妆容精致,眼神温柔而自信。她正轻盈地舞动,裙摆随动作飘逸,姿态曼妙动人。背景为典雅中式室内,后方悬挂一幅水墨山水画,暖色调灯光营造出温馨柔和的氛围。整体画面构图为中景特写,聚焦人物上半身及动态,视角平视,强调人物神韵与服饰细节。风格融合古典东方美学与现代时尚感,色彩以淡紫、金、红为主调,光影细腻柔美,呈现出高级写真质感。" - ] - }, - { - "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": [ - 32 - ] - }, - { - "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": 20, - "type": "ModelScopeImageCaptionNode", - "pos": [ - 2266.803982765229, - 414.0277931000381 - ], - "size": [ - 400, - 268 - ], - "flags": {}, - "order": 1, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 32 - } - ], - "outputs": [ - { - "name": "description", - "type": "STRING", - "links": [ - 33 - ] - } - ], - "properties": { - "Node name for S&R": "ModelScopeImageCaptionNode" - }, - "widgets_values": [ - "***已保存1个Token***", - "帮我拆解这张图片的提示词,要求从主体内容、场景设定、风格参考、色条色彩、构图视角、细节补充等这些角度来用文字描述图片,并汇总生成一个能让我用 ai绘画工具的文生图提示词,字数在 800 字以内,只要求输出汇总后的最终提示词,不需要无用信息。如:提示词标题,总字数等信息。结果使用中文输出", - "我要求在分析好信息后增加图中人物在跳舞的内容", - "Qwen/Qwen3-VL-8B-Instruct", - 1000, - 0.7 - ] - } - ], - "links": [ - [ - 32, - 5, - 0, - 20, - 0, - "IMAGE" - ], - [ - 33, - 20, - 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": 1.1000000000000003, - "offset": [ - -1628.9576538857762, - -206.80605453648508 - ] - }, - "frontendVersion": "1.30.1" - }, - "version": 0.4 -} \ No newline at end of file +{"id":"6d37ea84-d0ab-4123-8c8c-965b62a18230","revision":0,"last_node_id":21,"last_link_id":35,"nodes":[{"id":17,"type":"easy showAnything","pos":[2732.3588101299074,397.60778722933657],"size":[482.4299999999994,553.9499999999997],"flags":{},"order":2,"mode":0,"inputs":[{"label":"输入任何","localized_name":"输入任何","name":"anything","shape":7,"type":"*","link":35}],"outputs":[{"localized_name":"输出","name":"output","type":"*","links":null}],"properties":{"cnr_id":"comfyui-easy-use","ver":"1.3.3","Node name for S&R":"easy showAnything"},"widgets_values":["一位身着飘逸长裙的女性在梦幻般的云海之上翩翩起舞,裙摆如花瓣般随风扬起,姿态轻盈灵动。她周围环绕着半透明的光晕与漂浮的星尘,脚下是流动的彩色云雾,营造出超现实的仙境氛围。背景为渐变的暮色天空,从深紫到柔和的粉橙,点缀着稀疏的星光,远处隐约可见朦胧的山峦轮廓。整体画面采用电影级CG渲染风格,细腻写实与奇幻元素融合,光影柔和,色彩饱和度适中,带有轻微的柔焦效果。构图采用中景仰视视角,人物居于画面中央偏上,强调其优雅与自由感。细节上,女性发丝随风飘动,裙褶层次丰富,云层纹理细腻,星尘粒子动态自然,地面云雾呈现流动感。画面充满动感与诗意,传达出自由、浪漫与梦幻的主题。\n\n提示词:一位身着飘逸长裙的女性在梦幻云海之上翩翩起舞,裙摆如花瓣飞扬,周身环绕半透明光晕与星尘,脚下是流动彩云,背景为渐变暮色天空(深紫至粉橙),点缀稀疏星光与朦胧远山。电影级CG渲染风格,细腻写实融合奇幻元素,光影柔和,色彩饱和适中,带柔焦效果。中景仰视构图,人物居画面中央偏上,突出优雅自由感。细节:发丝飘动,裙褶层次丰富,云层纹理细腻,星尘粒子动态自然,云雾流动。画面充满动感诗意,传达自由浪漫梦幻主题。"]},{"id":21,"type":"ModelScopeImageCaptionNode","pos":[2266.803982765229,414.0277931000381],"size":[400,268],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"image","name":"image","shape":7,"type":"IMAGE","link":34},{"localized_name":"api_tokens","name":"api_tokens","type":"STRING","widget":{"name":"api_tokens"},"link":null},{"localized_name":"prompt1","name":"prompt1","shape":7,"type":"STRING","widget":{"name":"prompt1"},"link":null},{"localized_name":"prompt2","name":"prompt2","shape":7,"type":"STRING","widget":{"name":"prompt2"},"link":null},{"localized_name":"model","name":"model","shape":7,"type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"max_tokens","name":"max_tokens","shape":7,"type":"INT","widget":{"name":"max_tokens"},"link":null},{"localized_name":"temperature","name":"temperature","shape":7,"type":"FLOAT","widget":{"name":"temperature"},"link":null}],"outputs":[{"localized_name":"description","name":"description","type":"STRING","links":[35]}],"properties":{"Node name for S&R":"ModelScopeImageCaptionNode"},"widgets_values":["***已保存1个Token***","帮我拆解这张图片的提示词,要求从主体内容、场景设定、风格参考、色条色彩、构图视角、细节补充等这些角度来用文字描述图片,并汇总生成一个能让我用 ai绘画工具的文生图提示词,字数在 800 字以内,只要求输出汇总后的最终提示词,不需要无用信息。如:提示词标题,总字数等信息。结果使用中文输出","我要求在分析好信息后增加图中人物在跳舞的内容","Qwen/Qwen3-VL-8B-Instruct",1000,0.7]},{"id":5,"type":"LoadImage","pos":[1869.8370214843746,350.8748266601562],"size":[290.8019714355469,570.7747192382812],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"图像","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"选择文件上传","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"label":"图像","localized_name":"图像","name":"IMAGE","type":"IMAGE","links":[34]},{"label":"遮罩","localized_name":"遮罩","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"}],"links":[[34,5,0,21,0,"IMAGE"],[35,21,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":1.1000000000000005,"offset":[-1589.768328765161,-104.21134851168563]},"frontendVersion":"1.30.1"},"version":0.4} \ No newline at end of file diff --git a/ModelScope-API-生图改图.json b/ModelScope-API-生图改图.json index c42bd0a..253e06c 100644 --- a/ModelScope-API-生图改图.json +++ b/ModelScope-API-生图改图.json @@ -1 +1 @@ -{"id":"2d9bfea8-7816-47ac-afd9-4d11c9530c1d","revision":0,"last_node_id":15,"last_link_id":23,"nodes":[{"id":2,"type":"SaveImage","pos":[991.1607666015625,303],"size":[355.6989440917969,595.6929321289062],"flags":{},"order":7,"mode":0,"inputs":[{"label":"图像","localized_name":"图片","name":"images","type":"IMAGE","link":1},{"localized_name":"文件名前缀","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.50","Node name for S&R":"SaveImage","ue_properties":{"version":"7.0.1","widget_ue_connectable":{}}},"widgets_values":["ComfyUI"],"color":"#223","bgcolor":"#335"},{"id":8,"type":"SaveImage","pos":[2989.58154296875,323.7398376464844],"size":[435.7699890136719,653.5700073242188],"flags":{},"order":8,"mode":4,"inputs":[{"localized_name":"图片","name":"images","type":"IMAGE","link":23},{"localized_name":"文件名前缀","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"SaveImage"},"widgets_values":["ComfyUI"]},{"id":5,"type":"LoadImage","pos":[1768.197021484375,327.88482666015625],"size":[290.8019714355469,570.7747192382812],"flags":{},"order":0,"mode":4,"inputs":[{"localized_name":"图像","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"选择文件上传","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"label":"图像","localized_name":"图像","name":"IMAGE","type":"IMAGE","links":[3]},{"label":"遮罩","localized_name":"遮罩","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":4,"type":"LayerUtility: ImageScaleByAspectRatio V2","pos":[2097.364990234375,321.6758728027344],"size":[303.68515625,330],"flags":{},"order":4,"mode":4,"inputs":[{"label":"图像","localized_name":"image","name":"image","shape":7,"type":"IMAGE","link":3},{"label":"遮罩","localized_name":"mask","name":"mask","shape":7,"type":"MASK","link":null},{"localized_name":"aspect_ratio","name":"aspect_ratio","type":"COMBO","widget":{"name":"aspect_ratio"},"link":null},{"localized_name":"proportional_width","name":"proportional_width","type":"INT","widget":{"name":"proportional_width"},"link":null},{"localized_name":"proportional_height","name":"proportional_height","type":"INT","widget":{"name":"proportional_height"},"link":null},{"localized_name":"fit","name":"fit","type":"COMBO","widget":{"name":"fit"},"link":null},{"localized_name":"method","name":"method","type":"COMBO","widget":{"name":"method"},"link":null},{"localized_name":"round_to_multiple","name":"round_to_multiple","type":"COMBO","widget":{"name":"round_to_multiple"},"link":null},{"localized_name":"scale_to_side","name":"scale_to_side","type":"COMBO","widget":{"name":"scale_to_side"},"link":null},{"localized_name":"scale_to_length","name":"scale_to_length","type":"INT","widget":{"name":"scale_to_length"},"link":null},{"localized_name":"background_color","name":"background_color","type":"STRING","widget":{"name":"background_color"},"link":null}],"outputs":[{"label":"图像","localized_name":"image","name":"image","type":"IMAGE","links":[19]},{"label":"遮罩","localized_name":"mask","name":"mask","type":"MASK"},{"label":"原始大小","localized_name":"original_size","name":"original_size","type":"BOX"},{"localized_name":"width","name":"width","type":"INT","links":[21]},{"localized_name":"height","name":"height","type":"INT","links":[22]}],"properties":{"cnr_id":"comfyui_layerstyle","ver":"3bfe8e435d167b4a5bf08716729f89802dbbaa6f","Node name for S&R":"LayerUtility: ImageScaleByAspectRatio V2","ue_properties":{"version":"7.0.1","widget_ue_connectable":{"fit":true,"aspect_ratio":true,"method":true,"background_color":true,"round_to_multiple":true,"scale_to_side":true,"proportional_height":true,"proportional_width":true,"scale_to_length":true}}},"widgets_values":["original",1,1,"letterbox","lanczos","8","longest",1664,"#000000"],"color":"#232","bgcolor":"#353"},{"id":13,"type":"ModelScopeImageEditNode","pos":[2525.160888671875,336.3112487792969],"size":[400,412],"flags":{},"order":6,"mode":4,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":19},{"localized_name":"prompt","name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":20},{"localized_name":"api_tokens","name":"api_tokens","type":"STRING","widget":{"name":"api_tokens"},"link":null},{"localized_name":"image_gen_mode","name":"image_gen_mode","type":"BOOLEAN","widget":{"name":"image_gen_mode"},"link":null},{"localized_name":"gen_model","name":"gen_model","shape":7,"type":"COMBO","widget":{"name":"gen_model"},"link":null},{"localized_name":"edit_model","name":"edit_model","shape":7,"type":"COMBO","widget":{"name":"edit_model"},"link":null},{"localized_name":"negative_prompt","name":"negative_prompt","shape":7,"type":"STRING","widget":{"name":"negative_prompt"},"link":null},{"localized_name":"width","name":"width","shape":7,"type":"INT","widget":{"name":"width"},"link":21},{"localized_name":"height","name":"height","shape":7,"type":"INT","widget":{"name":"height"},"link":22},{"localized_name":"steps","name":"steps","shape":7,"type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"guidance","name":"guidance","shape":7,"type":"FLOAT","widget":{"name":"guidance"},"link":null},{"localized_name":"seed","name":"seed","shape":7,"type":"INT","widget":{"name":"seed"},"link":null}],"outputs":[{"localized_name":"edited_image","name":"edited_image","type":"IMAGE","links":[23]}],"properties":{"aux_id":"hujuying/ComfyUI-ModelScope-API","Node name for S&R":"ModelScopeImageEditNode","ver":"a69b40c91e02e1c84c22e2bb44e81f4bc287fe60"},"widgets_values":["修改图片中的内容","",false,"MAILAND/majicflus_v1","Qwen/Qwen-Image-Edit","lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry",512,512,30,3.5,873824660,"randomize"]},{"id":7,"type":"CR Text","pos":[2097.43505859375,718.3400268554688],"size":[400,200],"flags":{},"order":1,"mode":4,"inputs":[{"localized_name":"文本","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"label":"文本","localized_name":"text","name":"text","type":"*","links":[20]},{"label":"显示帮助","localized_name":"show_help","name":"show_help","type":"STRING"}],"properties":{"Node name for S&R":"CR Text","ue_properties":{"version":"7.0.1","widget_ue_connectable":{}}},"widgets_values":["给图中女孩戴上口罩,保持人物光影不变"],"color":"#223","bgcolor":"#335"},{"id":10,"type":"Fast Groups Bypasser (rgthree)","pos":[309.69769287109375,-15.034872055053711],"size":[530.6525268554688,129.8321533203125],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[{"label":"可选连接","name":"OPT_CONNECTION","type":"*","links":null}],"properties":{"matchColors":"","matchTitle":"","showNav":true,"showAllGraphs":true,"sort":"position","customSortAlphabet":"","toggleRestriction":"default"}},{"id":1,"type":"CR Text","pos":[491.8424377441406,729.388671875],"size":[400,200],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"文本","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"label":"文本","localized_name":"text","name":"text","type":"*","links":[2]},{"label":"显示帮助","localized_name":"show_help","name":"show_help","type":"STRING"}],"properties":{"Node name for S&R":"CR Text","ue_properties":{"version":"7.0.1","widget_ue_connectable":{}}},"widgets_values":["一位年轻美丽的亚洲女性,身着精致的淡紫色丝绸旗袍,旗袍上绣有金色祥云与花卉纹样,领口为高立领配盘扣设计,前襟开衩至大腿中部,展现优雅曲线。她佩戴半透明薄纱长袖,袖口拼接红色织锦并饰以金色龙纹刺绣,搭配同色系红色过膝袜。发型为古典盘发,插有金质流苏发簪与珠饰,刘海轻柔垂落,妆容精致,红唇微启,眼神温柔含情,直视镜头。背景为中式室内场景,墙面挂有水墨风格的山水或花鸟画作,色调柔和偏暖,营造静谧典雅氛围。整体构图采用中景人像视角,人物居中站立,略微侧身,突出服装细节与身材比例。光影柔和均匀,强调丝绸面料的光泽感与刺绣纹理,皮肤质感细腻光滑,呈现数字绘画的超写实美感。风格参考新中式美学与现代商业人像摄影,融合传统元素与时尚表现力,色彩主调为淡紫、金、红三色,辅以米白与浅棕背景,形成高级和谐的视觉层次。画面干净无杂物,焦点集中于人物神态与服饰工艺,适合用于AI绘图生成高质量东方美人肖像。"],"color":"#223","bgcolor":"#335"},{"id":3,"type":"ModelScopeImageNode","pos":[489,303.1618957519531],"size":[400,364],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"prompt","name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":2},{"localized_name":"api_tokens","name":"api_tokens","type":"STRING","widget":{"name":"api_tokens"},"link":null},{"localized_name":"model","name":"model","shape":7,"type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"negative_prompt","name":"negative_prompt","shape":7,"type":"STRING","widget":{"name":"negative_prompt"},"link":null},{"localized_name":"width","name":"width","shape":7,"type":"INT","widget":{"name":"width"},"link":null},{"localized_name":"height","name":"height","shape":7,"type":"INT","widget":{"name":"height"},"link":null},{"localized_name":"seed","name":"seed","shape":7,"type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"steps","name":"steps","shape":7,"type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"guidance","name":"guidance","shape":7,"type":"FLOAT","widget":{"name":"guidance"},"link":null}],"outputs":[{"localized_name":"image","name":"image","type":"IMAGE","links":[1]}],"properties":{"aux_id":"hujuying/ComfyUI-ModelScope-API-test","Node name for S&R":"ModelScopeImageNode","ver":"d3b0588ea61a4db0d3edfbea28e6bb7ae599d27a"},"widgets_values":["A beautiful landscape","","MusePublic/Qwen-image","lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry",1024,1536,579600024,"randomize",20,4.5]}],"links":[[1,3,0,2,0,"IMAGE"],[2,1,0,3,0,"STRING"],[3,5,0,4,0,"IMAGE"],[19,4,0,13,0,"IMAGE"],[20,7,0,13,1,"STRING"],[21,4,3,13,7,"INT"],[22,4,4,13,8,"INT"],[23,13,0,8,0,"IMAGE"]],"groups":[{"id":1,"title":"魔搭-文生图","bounding":[272.2100524902344,170.37083435058594,1283.3289794921875,812.0499877929688],"color":"#3f789e","font_size":24,"flags":{}},{"id":2,"title":"魔搭-图片编辑","bounding":[1663.396484375,122.82080078125,1826.526123046875,960.4658203125],"color":"#3f789e","font_size":24,"flags":{}}],"config":{},"extra":{"ds":{"scale":1.6105100000000008,"offset":[-151.8384234053457,-282.2059801268807]}},"version":0.4} \ No newline at end of file +{"id":"2d9bfea8-7816-47ac-afd9-4d11c9530c1d","revision":0,"last_node_id":20,"last_link_id":38,"nodes":[{"id":8,"type":"SaveImage","pos":[2989.58154296875,323.7398376464844],"size":[435.7699890136719,653.5700073242188],"flags":{},"order":9,"mode":4,"inputs":[{"localized_name":"图片","name":"images","type":"IMAGE","link":38},{"localized_name":"文件名前缀","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.56","Node name for S&R":"SaveImage"},"widgets_values":["ComfyUI"]},{"id":5,"type":"LoadImage","pos":[1768.197021484375,327.88482666015625],"size":[290.8019714355469,570.7747192382812],"flags":{},"order":0,"mode":4,"inputs":[{"localized_name":"图像","name":"image","type":"COMBO","widget":{"name":"image"},"link":null},{"localized_name":"选择文件上传","name":"upload","type":"IMAGEUPLOAD","widget":{"name":"upload"},"link":null}],"outputs":[{"label":"图像","localized_name":"图像","name":"IMAGE","type":"IMAGE","links":[3]},{"label":"遮罩","localized_name":"遮罩","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":4,"type":"LayerUtility: ImageScaleByAspectRatio V2","pos":[2097.364990234375,321.6758728027344],"size":[303.68515625,330],"flags":{},"order":5,"mode":4,"inputs":[{"label":"图像","localized_name":"image","name":"image","shape":7,"type":"IMAGE","link":3},{"label":"遮罩","localized_name":"mask","name":"mask","shape":7,"type":"MASK","link":null},{"localized_name":"aspect_ratio","name":"aspect_ratio","type":"COMBO","widget":{"name":"aspect_ratio"},"link":null},{"localized_name":"proportional_width","name":"proportional_width","type":"INT","widget":{"name":"proportional_width"},"link":null},{"localized_name":"proportional_height","name":"proportional_height","type":"INT","widget":{"name":"proportional_height"},"link":null},{"localized_name":"fit","name":"fit","type":"COMBO","widget":{"name":"fit"},"link":null},{"localized_name":"method","name":"method","type":"COMBO","widget":{"name":"method"},"link":null},{"localized_name":"round_to_multiple","name":"round_to_multiple","type":"COMBO","widget":{"name":"round_to_multiple"},"link":null},{"localized_name":"scale_to_side","name":"scale_to_side","type":"COMBO","widget":{"name":"scale_to_side"},"link":null},{"localized_name":"scale_to_length","name":"scale_to_length","type":"INT","widget":{"name":"scale_to_length"},"link":null},{"localized_name":"background_color","name":"background_color","type":"STRING","widget":{"name":"background_color"},"link":null}],"outputs":[{"label":"图像","localized_name":"image","name":"image","type":"IMAGE","links":[34]},{"label":"遮罩","localized_name":"mask","name":"mask","type":"MASK"},{"label":"原始大小","localized_name":"original_size","name":"original_size","type":"BOX"},{"localized_name":"width","name":"width","type":"INT","links":[36]},{"localized_name":"height","name":"height","type":"INT","links":[37]}],"properties":{"cnr_id":"comfyui_layerstyle","ver":"3bfe8e435d167b4a5bf08716729f89802dbbaa6f","Node name for S&R":"LayerUtility: ImageScaleByAspectRatio V2","ue_properties":{"version":"7.0.1","widget_ue_connectable":{"fit":true,"aspect_ratio":true,"method":true,"background_color":true,"round_to_multiple":true,"scale_to_side":true,"proportional_height":true,"proportional_width":true,"scale_to_length":true}}},"widgets_values":["original",1,1,"letterbox","lanczos","8","longest",1664,"#000000"],"color":"#232","bgcolor":"#353"},{"id":7,"type":"CR Text","pos":[2097.43505859375,718.3400268554688],"size":[400,200],"flags":{},"order":1,"mode":4,"inputs":[{"localized_name":"文本","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"label":"文本","localized_name":"text","name":"text","type":"*","links":[35]},{"label":"显示帮助","localized_name":"show_help","name":"show_help","type":"STRING"}],"properties":{"Node name for S&R":"CR Text","ue_properties":{"version":"7.0.1","widget_ue_connectable":{}}},"widgets_values":["给图中女孩戴上口罩,保持人物光影不变"],"color":"#223","bgcolor":"#335"},{"id":10,"type":"Fast Groups Bypasser (rgthree)","pos":[309.69769287109375,-15.034872055053711],"size":[530.6525268554688,129.8321533203125],"flags":{},"order":2,"mode":0,"inputs":[],"outputs":[{"label":"可选连接","name":"OPT_CONNECTION","type":"*","links":null}],"properties":{"matchColors":"","matchTitle":"","showNav":true,"showAllGraphs":true,"sort":"position","customSortAlphabet":"","toggleRestriction":"default"}},{"id":2,"type":"SaveImage","pos":[1152.2117666015624,270.78980000000007],"size":[355.6989440917969,595.6929321289062],"flags":{},"order":8,"mode":0,"inputs":[{"label":"图像","localized_name":"图片","name":"images","type":"IMAGE","link":27},{"localized_name":"文件名前缀","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{"cnr_id":"comfy-core","ver":"0.3.50","Node name for S&R":"SaveImage","ue_properties":{"version":"7.0.1","widget_ue_connectable":{}}},"widgets_values":["ComfyUI"],"color":"#223","bgcolor":"#335"},{"id":17,"type":"ModelScopeImageNode","pos":[699.6356563636361,277.8090793883167],"size":[400,508],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"prompt","name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":26},{"localized_name":"api_tokens","name":"api_tokens","type":"STRING","widget":{"name":"api_tokens"},"link":null},{"localized_name":"model","name":"model","shape":7,"type":"COMBO","widget":{"name":"model"},"link":null},{"localized_name":"negative_prompt","name":"negative_prompt","shape":7,"type":"STRING","widget":{"name":"negative_prompt"},"link":null},{"localized_name":"width","name":"width","shape":7,"type":"INT","widget":{"name":"width"},"link":null},{"localized_name":"height","name":"height","shape":7,"type":"INT","widget":{"name":"height"},"link":null},{"localized_name":"seed","name":"seed","shape":7,"type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"steps","name":"steps","shape":7,"type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"guidance","name":"guidance","shape":7,"type":"FLOAT","widget":{"name":"guidance"},"link":null},{"localized_name":"lora1_id","name":"lora1_id","shape":7,"type":"STRING","widget":{"name":"lora1_id"},"link":28},{"localized_name":"lora1_w","name":"lora1_w","shape":7,"type":"FLOAT","widget":{"name":"lora1_w"},"link":null},{"localized_name":"lora2_id","name":"lora2_id","shape":7,"type":"STRING","widget":{"name":"lora2_id"},"link":null},{"localized_name":"lora2_w","name":"lora2_w","shape":7,"type":"FLOAT","widget":{"name":"lora2_w"},"link":null},{"localized_name":"lora3_id","name":"lora3_id","shape":7,"type":"STRING","widget":{"name":"lora3_id"},"link":null},{"localized_name":"lora3_w","name":"lora3_w","shape":7,"type":"FLOAT","widget":{"name":"lora3_w"},"link":null}],"outputs":[{"localized_name":"image","name":"image","type":"IMAGE","links":[27]}],"properties":{"Node name for S&R":"ModelScopeImageNode"},"widgets_values":["A beautiful landscape","","MusePublic/Qwen-image","lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry",1024,1536,1893394128,"randomize",20,4.5,"",0.8,"",0.8,"",0.8]},{"id":18,"type":"ModelScopeSingleLoraLoaderNode","pos":[332.1272007729853,336.9393250613874],"size":[298.319921875,126],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"lora_preset","name":"lora_preset","type":"COMBO","widget":{"name":"lora_preset"},"link":null},{"localized_name":"lora_weight","name":"lora_weight","shape":7,"type":"FLOAT","widget":{"name":"lora_weight"},"link":null},{"localized_name":"use_custom_weight","name":"use_custom_weight","shape":7,"type":"BOOLEAN","widget":{"name":"use_custom_weight"},"link":null}],"outputs":[{"localized_name":"lora_id","name":"lora_id","type":"STRING","links":[28]},{"localized_name":"lora_weight","name":"lora_weight","type":"FLOAT","links":null}],"properties":{"Node name for S&R":"ModelScopeSingleLoraLoaderNode"},"widgets_values":["Qwen-抖音小红书手持手机自拍美女",0.8,false]},{"id":1,"type":"CR Text","pos":[372.63900559537984,619.0155193130165],"size":[210,167.7898],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"文本","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"label":"文本","localized_name":"text","name":"text","type":"*","links":[26]},{"label":"显示帮助","localized_name":"show_help","name":"show_help","type":"STRING"}],"properties":{"Node name for S&R":"CR Text","ue_properties":{"version":"7.0.1","widget_ue_connectable":{}}},"widgets_values":["Selfie with your mobile phone,a high-resolution photograph featuring a young woman with long, straight black hair and fair skin, she is standing in front of a mirror, taking a selfie with her phone, which is held in her right hand, her left hand is positioned near her face, holding the phone with a small, stylized logo, she wears a white, long-sleeved blouse that is slightly unbuttoned, revealing a portion of her chest, and a short, black mini-skirt that accentuates her curvy figure, she also wears black thigh-high stockings with lace tops and black leather shoes, the background shows a plain, beige wall and a door with a metallic texture, suggesting an indoor setting, the floor is tiled in a light beige color, and the overall ambiance suggests a modern, minimalist environment, the lighting is soft and natural, casting gentle shadows that enhance the texture of her clothing and the smoothness of her skin, the photograph is likely taken in a studio setting, likely in a private, private space, as indicated by the minimalistic setting,"],"color":"#223","bgcolor":"#335"},{"id":20,"type":"ModelScopeImageEditNode","pos":[2525.160888671875,336.3112487792969],"size":[400,556],"flags":{},"order":7,"mode":4,"inputs":[{"localized_name":"image","name":"image","type":"IMAGE","link":34},{"localized_name":"prompt","name":"prompt","type":"STRING","widget":{"name":"prompt"},"link":35},{"localized_name":"api_tokens","name":"api_tokens","type":"STRING","widget":{"name":"api_tokens"},"link":null},{"localized_name":"image_gen_mode","name":"image_gen_mode","type":"BOOLEAN","widget":{"name":"image_gen_mode"},"link":null},{"localized_name":"gen_model","name":"gen_model","shape":7,"type":"COMBO","widget":{"name":"gen_model"},"link":null},{"localized_name":"edit_model","name":"edit_model","shape":7,"type":"COMBO","widget":{"name":"edit_model"},"link":null},{"localized_name":"negative_prompt","name":"negative_prompt","shape":7,"type":"STRING","widget":{"name":"negative_prompt"},"link":null},{"localized_name":"width","name":"width","shape":7,"type":"INT","widget":{"name":"width"},"link":36},{"localized_name":"height","name":"height","shape":7,"type":"INT","widget":{"name":"height"},"link":37},{"localized_name":"steps","name":"steps","shape":7,"type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"guidance","name":"guidance","shape":7,"type":"FLOAT","widget":{"name":"guidance"},"link":null},{"localized_name":"seed","name":"seed","shape":7,"type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"lora1_id","name":"lora1_id","shape":7,"type":"STRING","widget":{"name":"lora1_id"},"link":null},{"localized_name":"lora1_w","name":"lora1_w","shape":7,"type":"FLOAT","widget":{"name":"lora1_w"},"link":null},{"localized_name":"lora2_id","name":"lora2_id","shape":7,"type":"STRING","widget":{"name":"lora2_id"},"link":null},{"localized_name":"lora2_w","name":"lora2_w","shape":7,"type":"FLOAT","widget":{"name":"lora2_w"},"link":null},{"localized_name":"lora3_id","name":"lora3_id","shape":7,"type":"STRING","widget":{"name":"lora3_id"},"link":null},{"localized_name":"lora3_w","name":"lora3_w","shape":7,"type":"FLOAT","widget":{"name":"lora3_w"},"link":null}],"outputs":[{"localized_name":"edited_image","name":"edited_image","type":"IMAGE","links":[38]}],"properties":{"Node name for S&R":"ModelScopeImageEditNode"},"widgets_values":["修改图片中的内容","",false,"MAILAND/majicflus_v1","Qwen/Qwen-Image-Edit","lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry",512,512,30,3.5,1767203483,"randomize","",0.8,"",0.8,"",0.8]}],"links":[[3,5,0,4,0,"IMAGE"],[26,1,0,17,0,"STRING"],[27,17,0,2,0,"IMAGE"],[28,18,0,17,9,"STRING"],[34,4,0,20,0,"IMAGE"],[35,7,0,20,1,"STRING"],[36,4,3,20,7,"INT"],[37,4,4,20,8,"INT"],[38,20,0,8,0,"IMAGE"]],"groups":[{"id":1,"title":"魔搭-文生图","bounding":[272.2100524902344,170.37083435058594,1283.3289794921875,812.0499877929688],"color":"#3f789e","font_size":24,"flags":{}},{"id":2,"title":"魔搭-图片编辑","bounding":[1663.396484375,122.82080078125,1826.526123046875,960.4658203125],"color":"#3f789e","font_size":24,"flags":{}}],"config":{},"extra":{"ds":{"scale":0.9090909090909091,"offset":[84.01624491641203,-8.219006754100473]}},"version":0.4} \ No newline at end of file diff --git a/image.png b/image.png new file mode 100644 index 0000000..4fece50 Binary files /dev/null and b/image.png differ diff --git a/modelscope_config.json b/modelscope_config.json index 6368582..0639b6f 100644 --- a/modelscope_config.json +++ b/modelscope_config.json @@ -19,7 +19,9 @@ "MusePublic/489_ckpt_FLUX_1", "MusePublic/flux-high-res", "black-forest-labs/FLUX.1-Krea-dev", - "MAILAND/majicflus_v1" + "MAILAND/majicflus_v1", + "MoYouuu/MYHuman-QWen", + "Tongyi-MAI/Z-Image-Turbo" ], "image_edit_models": [ "Qwen/Qwen-Image-Edit", @@ -47,5 +49,42 @@ "Qwen/Qwen2-VL-7B-Instruct", "Qwen/QVQ-72B-Preview", "PaddlePaddle/ERNIE-4.5-VL-28B-A3B-PT" + ], + "default_lora_model": "", + "default_lora_weight": 0.8, + "lora_presets": [ + { + "name": "无LoRA", + "model_id": "", + "weight": 0.8 + }, + { + "name": "Qwen-小红书风格美人", + "model_id": "qiyuanai/TikTok_Xiaohongshu_career_line_beauty_v1", + "weight": 0.8 + }, + { + "name": "Qwen-小红书甜妹", + "model_id": "wilderkid/Rednote_SweetGirl", + "weight": 0.8 + }, + { + "name": "Qwen-小红薯-风格插画-排版", + "model_id": "YJ777YJ/xiaohshu", + "weight": 0.8 + }, + { + "name": "Qwen-真实人像小红书风格", + "model_id": "chy1125677992/qwxhs", + "weight": 0.8 + }, + { + "name": "Qwen-抖音小红书手持手机自拍美女", + "model_id": "qiyuanai/shouchizipai_v1", + "weight": 0.8 + } + ], + "api_tokens": [ + "ms" ] -} +} \ No newline at end of file diff --git a/modelscope_image_caption_node.py b/modelscope_image_caption_node.py index f00a93e..f9cf566 100644 --- a/modelscope_image_caption_node.py +++ b/modelscope_image_caption_node.py @@ -8,7 +8,7 @@ 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 +from .modelscope_image_node import load_config, save_config, tensor_to_base64_url # 检查openai库是否可用 try: @@ -17,6 +17,26 @@ try: except ImportError: OPENAI_AVAILABLE = False +# 仅与modelscope_config.json交互的API Token管理函数 +def load_api_tokens(): + try: + cfg = load_config() + tokens_from_cfg = cfg.get("api_tokens", []) + if tokens_from_cfg and isinstance(tokens_from_cfg, list): + return [token.strip() for token in tokens_from_cfg if token.strip()] + except Exception as e: + print(f"读取config中的tokens失败: {e}") + return [] + +def save_api_tokens(tokens): + try: + cfg = load_config() + cfg["api_tokens"] = tokens + return save_config(cfg) + except Exception as e: + print(f"保存tokens到config失败: {e}") + return False + class ModelScopeImageCaptionNode: def __init__(self): pass @@ -40,7 +60,6 @@ class ModelScopeImageCaptionNode: ] return { "required": { - "image": ("IMAGE",), "api_tokens": ("STRING", { "default": f"***已保存{len(saved_tokens)}个Token***" if saved_tokens else "", "placeholder": "请输入API Token(支持多个,用逗号/换行分隔)", @@ -48,6 +67,8 @@ class ModelScopeImageCaptionNode: }), }, "optional": { + # 关键修改:将image设置为可选输入 + "image": ("IMAGE", {"optional": True}), "prompt1": ("STRING", { "multiline": True, "default": "详细描述这张图片的内容,包括主体、背景、颜色、风格等信息" @@ -56,9 +77,8 @@ class ModelScopeImageCaptionNode: "multiline": True, "default": "" }), - # 添加模型下拉选择 "model": (supported_models, { - "default": "Qwen/Qwen3-VL-8B-Instruct" # 默认选中原模型 + "default": "Qwen/Qwen3-VL-8B-Instruct" }), "max_tokens": ("INT", { "default": 1000, @@ -87,12 +107,24 @@ class ModelScopeImageCaptionNode: # 支持多种分隔符拆分Token tokens = re.split(r'[,;\n]+', token_input) return [token.strip() for token in tokens if token.strip()] + + def create_blank_image(self, width=64, height=64): + """创建空白图像张量(符合ComfyUI的图像格式要求)""" + # 创建白色背景的RGB图像 + blank_np = np.ones((height, width, 3), dtype=np.uint8) * 255 + # 转换为ComfyUI格式的张量 (batch, height, width, channels) + blank_tensor = torch.from_numpy(blank_np).unsqueeze(0).float() / 255.0 + return blank_tensor - # 调整参数顺序,加入新的prompt2参数 def generate_caption(self, image=None, api_tokens="", prompt1="详细描述这张图片的内容", prompt2="", model="Qwen/Qwen3-VL-8B-Instruct", max_tokens=1000, temperature=0.7): if not OPENAI_AVAILABLE: return ("请先安装openai库: pip install openai",) + # 关键修改:处理输入图像为空的情况 + if image is None: + print("⚠️ 未输入图像,自动生成空白图像作为输入") + image = self.create_blank_image() + # 处理提示词合并 prompt_parts = [] if prompt1.strip(): @@ -100,13 +132,12 @@ class ModelScopeImageCaptionNode: if prompt2.strip(): prompt_parts.append(prompt2.strip()) - # 如果两个提示词都为空,使用默认提示 if not prompt_parts: prompt = "详细描述这张图片的内容,包括主体、背景、颜色、风格等信息" else: prompt = ", ".join(prompt_parts) - # 解析Token列表(支持多个) + # 解析Token列表 tokens = self.parse_api_tokens(api_tokens) if not tokens: raise Exception("请提供至少一个有效的API Token") @@ -122,7 +153,7 @@ class ModelScopeImageCaptionNode: try: print(f"🔍 开始生成图像描述...") print(f"📝 提示词: {prompt}") - print(f"🤖 模型: {model}") # 显示选中的模型 + print(f"🤖 模型: {model}") print(f"🔑 可用Token数量: {len(tokens)}") # 转换图像为base64格式 @@ -149,13 +180,11 @@ class ModelScopeImageCaptionNode: 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, @@ -164,7 +193,6 @@ class ModelScopeImageCaptionNode: stream=False ) - # 成功获取结果 description = response.choices[0].message.content print(f"✅ 第 {i+1} 个Token调用成功!") print(f"📄 结果预览: {description[:100]}...") diff --git a/modelscope_image_node.py b/modelscope_image_node.py index 45d359e..f60acbe 100644 --- a/modelscope_image_node.py +++ b/modelscope_image_node.py @@ -9,80 +9,83 @@ import os import folder_paths import base64 import tempfile +import re +# -------------------------- 核心配置管理 -------------------------- def load_config(): + """从modelscope_config.json加载配置,确保优先使用配置文件中的lora_presets""" config_path = os.path.join(os.path.dirname(__file__), 'modelscope_config.json') + default_config = { + "default_model": "Qwen/Qwen-Image", + "timeout": 720, + "image_download_timeout": 30, + "default_prompt": "A beautiful landscape", + "default_negative_prompt": "", + "default_width": 512, + "default_height": 512, + "default_seed": -1, + "default_steps": 30, + "default_guidance": 7.5, + "default_lora_weight": 0.8, + "image_models": ["Qwen/Qwen-Image"], + "image_edit_models": ["Qwen/Qwen-Image-Edit"], + "lora_presets": [ + {"name": "无LoRA", "model_id": "", "weight": 0.8} + ], + "api_tokens": [] + } try: with open(config_path, 'r', encoding='utf-8') as f: - return json.load(f) - except: - return { - "default_model": "Qwen/Qwen-Image", - "timeout": 720, - "image_download_timeout": 30, - "default_prompt": "A beautiful landscape" - } + config = json.load(f) + # 确保配置文件中存在所有必要字段,缺失则补充则补充默认值 + for key, value in default_config.items(): + if key not in config: + config[key] = value + return config + except Exception as e: + print(f"读取配置文件失败,使用默认配置: {e}") + return default_config def save_config(config: dict) -> bool: + """保存配置到modelscope_config.json""" config_path = os.path.join(os.path.dirname(__file__), 'modelscope_config.json') try: with open(config_path, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) return True except Exception as e: - print(f"保存配置失败: {e}") + print(f"保存配置文件失败: {e}") return False +# -------------------------- API Token管理 -------------------------- def save_api_tokens(tokens): - """保存多个API Token""" - tokens_path = os.path.join(os.path.dirname(__file__), '.qwen_tokens') - try: - with open(tokens_path, 'w', encoding='utf-8') as f: - f.write('\n'.join(tokens)) # 每个token一行 - except Exception as e: - print(f"保存tokens失败(.qwen_tokens): {e}") - try: cfg = load_config() cfg["api_tokens"] = tokens - if save_config(cfg): - return True - return False + return save_config(cfg) except Exception as e: - print(f"保存tokens失败(config.json): {e}") + print(f"保存API tokens失败: {e}") return False def load_api_tokens(): - """加载多个API Token""" - tokens_path = os.path.join(os.path.dirname(__file__), '.qwen_tokens') try: cfg = load_config() tokens_from_cfg = cfg.get("api_tokens", []) if tokens_from_cfg and isinstance(tokens_from_cfg, list): return [token.strip() for token in tokens_from_cfg if token.strip()] - except Exception as e: - print(f"读取config.json中的tokens失败: {e}") - - try: - if os.path.exists(tokens_path): - with open(tokens_path, 'r', encoding='utf-8') as f: - tokens = [line.strip() for line in f.read().split('\n') if line.strip()] - return tokens if tokens else [] return [] except Exception as e: - print(f"加载tokens失败: {e}") + print(f"加载API tokens失败: {e}") return [] def parse_api_tokens(token_input): - """解析输入的API Tokens(支持逗号、分号、换行分隔)""" if not token_input or token_input.strip() in ["", "***已保存***"]: return load_api_tokens() - # 支持多种分隔符 - import re tokens = re.split(r'[,;\n]+', token_input) return [token.strip() for token in tokens if token.strip()] +# -------------------------- 图像转换工具 -------------------------- def tensor_to_base64_url(image_tensor): try: if len(image_tensor.shape) == 4: @@ -93,8 +96,7 @@ def tensor_to_base64_url(image_tensor): else: image_np = image_tensor.cpu().numpy().astype(np.uint8) - pil_image = Image.fromarray(image_np) - + pil_image = Image.fromarrayarray(image_np) buffer = BytesIO() pil_image.save(buffer, format='JPEG', quality=85) img_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8') @@ -102,10 +104,193 @@ def tensor_to_base64_url(image_tensor): return f"data:image/jpeg;base64,{img_base64}" except Exception as e: - print(f"图像转换失败: {e}") raise Exception(f"图像格式转换失败: {str(e)}") +# -------------------------- LoRA预设管理节点 -------------------------- +class ModelScopeLoraPresetNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + # 从配置文件加载LoRA预设列表 + config = load_config() + lora_presets = config.get("lora_presets", []) + preset_names = [preset.get("name", "无LoRA") for preset in lora_presets] + + return { + "required": { + "action": (["查看预设", "添加预设", "删除预设", "保存预设"], {"default": "查看预设"}), + }, + "optional": { + "preset_name": ("STRING", {"default": "自定义LoRA", "label": "预设名称"}), + "lora_model_id": ("STRING", {"default": "", "label": "LoRA模型ID", "placeholder": "例如:qiyuanai/TikTok_Xiaohongshu_career_line_beauty_v1"}), + "default_weight": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "默认权重"}), + "target_preset": (preset_names, {"default": preset_names[0] if preset_names else "无LoRA", "label": "目标预设"}), + } + } + + RETURN_TYPES = ("STRING", "FLOAT", "STRING") + RETURN_NAMES = ("lora_model_id", "lora_weight", "preset_info") + FUNCTION = "manage_lora_presets" + CATEGORY = "ModelScopeAPI/LoRA" + + def manage_lora_presets(self, action, preset_name="", lora_model_id="", default_weight=0.8, target_preset=""): + # 所有操作均基于配置文件中的LoRA预设 + config = load_config() + lora_presets = config.get("lora_presets", []) + preset_info = f"当前共有 {len(lora_presets)} 个LoRA预设" + + if action == "查看预设": + info_lines = ["=== LoRA预设列表 ==="] + for i, preset in enumerate(lora_presets): + info_lines.append(f"{i+1}. {preset.get('name')} | ID: {preset.get('model_id')} | 权重: {preset.get('weight')}") + preset_info = "\n".join(info_lines) + selected_preset = next((p for p in lora_presets if p.get("name") == target_preset), {"model_id": "", "weight": 0.8}) + return (selected_preset.get("model_id"), selected_preset.get("weight"), preset_info) + + elif action == "添加预设": + if not preset_name or preset_name.strip() == "": + raise Exception("预设名称不能为空") + + if any(p.get("name") == preset_name for p in lora_presets): + raise Exception(f"已存在名为 {preset_name} 的预设") + + new_preset = { + "name": preset_name.strip(), + "model_id": lora_model_id.strip(), + "weight": float(default_weight) + } + lora_presets.append(new_preset) + config["lora_presets"] = lora_presets + save_config(config) + preset_info = f"成功添加预设: {preset_name} | ID: {lora_model_id}" + return (lora_model_id, default_weight, preset_info) + + elif action == "删除预设": + if target_preset == "无LoRA": + raise Exception("不能删除默认的无LoRA预设") + + original_count = len(lora_presets) + lora_presets = [p for p in lora_presets if p.get("name") != target_preset] + if len(lora_presets) == original_count: + raise Exception(f"未找到预设: {target_preset}") + + config["lora_presets"] = lora_presets + save_config(config) + preset_info = f"成功删除预设: {target_preset}" + return ("", 0.8, preset_info) + + elif action == "保存预设": + updated = False + for i, preset in enumerate(lora_presets): + if preset.get("name") == target_preset: + lora_presets[i]["model_id"] = lora_model_id.strip() + lora_presets[i]["weight"] = float(default_weight) + updated = True + break + + if not updated: + raise Exception(f"未找到预设: {target_preset}") + + config["lora_presets"] = lora_presets + save_config(config) + preset_info = f"成功更新预设: {target_preset} | 新ID: {lora_model_id} | 新权重: {default_weight}" + return (lora_model_id, default_weight, preset_info) + + return ("", 0.8, preset_info) +# -------------------------- 单LoRA加载节点 -------------------------- +class ModelScopeSingleLoraLoaderNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + # 从配置文件加载LoRA预设选项 + config = load_config() + lora_presets = config.get("lora_presets", []) + preset_options = [preset.get("name", "无LoRA") for preset in lora_presets] + + return { + "required": { + "lora_preset": (preset_options, {"default": preset_options[0], "label": "LoRA预设"}), + }, + "optional": { + "lora_weight": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "自定义权重"}), + "use_custom_weight": ("BOOLEAN", {"default": False, "label_on": "使用自定义权重", "label_off": "使用预设权重"}), + } + } + + RETURN_TYPES = ("STRING", "FLOAT") + RETURN_NAMES = ("lora_id", "lora_weight") + FUNCTION = "load_single_lora" + CATEGORY = "ModelScopeAPI/LoRA" + + def load_single_lora(self, lora_preset, lora_weight=0.8, use_custom_weight=False): + # 从配置文件读取选中的LoRA信息 + config = load_config() + lora_presets = config.get("lora_presets", []) + + selected_preset = next((p for p in lora_presets if p.get("name") == lora_preset), {"model_id": "", "weight": 0.8}) + lora_id = selected_preset.get("model_id", "") + final_weight = lora_weight if use_custom_weight else selected_preset.get("weight", 0.8) + + return (lora_id, final_weight) + +# -------------------------- 多LoRA加载节点 -------------------------- +class ModelScopeMultiLoraLoaderNode: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls): + # 从配置文件加载LoRA预设选项 + config = load_config() + lora_presets = config.get("lora_presets", []) + preset_options = [preset.get("name", "无LoRA") for preset in lora_presets] + + return { + "required": { + "lora1_preset": (preset_options, {"default": preset_options[0], "label": "LoRA 1 预设"}), + "lora2_preset": (preset_options, {"default": preset_options[0], "label": "LoRA 2 预设"}), + "lora3_preset": (preset_options, {"default": preset_options[0], "label": "LoRA 3 预设"}), + }, + "optional": { + "lora1_weight": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA 1 权重"}), + "lora2_weight": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA 2 权重"}), + "lora3_weight": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA 3 权重"}), + "lora1_use_custom": ("BOOLEAN", {"default": False, "label_on": "LoRA1用自定义权重", "label_off": "用预设权重"}), + "lora2_use_custom": ("BOOLEAN", {"default": False, "label_on": "LoRA2用自定义权重", "label_off": "用预设权重"}), + "lora3_use_custom": ("BOOLEAN", {"default": False, "label_on": "LoRA3用自定义权重", "label_off": "用预设权重"}), + } + } + + RETURN_TYPES = ("STRING", "STRING", "STRING", "FLOAT", "FLOAT", "FLOAT") + RETURN_NAMES = ("lora1_id", "lora2_id", "lora3_id", "lora1_w", "lora2_w", "lora3_w") + FUNCTION = "load_multi_lora" + CATEGORY = "ModelScopeAPI/LoRA" + + def load_multi_lora(self, lora1_preset, lora2_preset, lora3_preset, + lora1_weight=0.8, lora2_weight=0.8, lora3_weight=0.8, + lora1_use_custom=False, lora2_use_custom=False, lora3_use_custom=False): + # 从配置文件读取多个LoRA信息 + config = load_config() + lora_presets = config.get("lora_presets", []) + + def get_lora_info(preset_name, custom_weight, use_custom): + preset = next((p for p in lora_presets if p.get("name") == preset_name), {"model_id": "", "weight": 0.8}) + model_id = preset.get("model_id", "") + final_weight = custom_weight if use_custom else preset.get("weight", 0.8) + return model_id, final_weight + + lora1_id, lora1_w = get_lora_info(lora1_preset, lora1_weight, lora1_use_custom) + lora2_id, lora2_w = get_lora_info(lora2_preset, lora2_weight, lora2_use_custom) + lora3_id, lora3_w = get_lora_info(lora3_preset, lora3_weight, lora3_use_custom) + + return (lora1_id, lora2_id, lora3_id, lora1_w, lora2_w, lora3_w) + +# -------------------------- 生图节点 -------------------------- class ModelScopeImageNode: def __init__(self): pass @@ -114,6 +299,7 @@ class ModelScopeImageNode: def INPUT_TYPES(cls): config = load_config() saved_tokens = load_api_tokens() + return { "required": { "prompt": ("STRING", { @@ -162,6 +348,12 @@ class ModelScopeImageNode: "max": 20.0, "step": 0.1 }), + "lora1_id": ("STRING", {"default": "", "label": "LoRA1 模型ID"}), + "lora1_w": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA1 权重"}), + "lora2_id": ("STRING", {"default": "", "label": "LoRA2 模型ID"}), + "lora2_w": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA2 权重"}), + "lora3_id": ("STRING", {"default": "", "label": "LoRA3 模型ID"}), + "lora3_w": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA3 权重"}), } } @@ -170,25 +362,49 @@ class ModelScopeImageNode: FUNCTION = "generate_image" CATEGORY = "ModelScopeAPI" - def generate_image(self, prompt, api_tokens, model="Qwen/Qwen-Image", negative_prompt="", width=512, height=512, seed=-1, steps=30, guidance=7.5): + def generate_image(self, prompt, api_tokens, model="Qwen/Qwen-Image", negative_prompt="", width=512, height=512, seed=-1, steps=30, guidance=7.5, + lora1_id="", lora1_w=0.8, lora2_id="", lora2_w=0.8, lora3_id="", lora3_w=0.8): config = load_config() tokens = parse_api_tokens(api_tokens) if not tokens: raise Exception("请提供至少一个有效的API Token") - # 保存Token(如果提供了新的) + # 保存新Token(如果有变化) if api_tokens and api_tokens.strip() not in ["", "***已保存{}个Token***".format(len(load_api_tokens()))]: if save_api_tokens(tokens): print(f"✅ 已保存 {len(tokens)} 个API Token") else: print("⚠️ API Token保存失败,但不影响当前使用") - # 轮询尝试每个Token + print(f"🔍 开始生成图像...") + print(f"📝 提示词: {prompt}") + print(f"❌ 反向提示词: {negative_prompt if negative_prompt else '无'}") + print(f"🤖 模型: {model}") + print(f"🔑 可用Token数量: {len(tokens)}") + print(f"📐 尺寸: {width}x{height}") + print(f"🔄 步数: {steps}") + print(f"🧭 引导系数: {guidance}") + print(f"🔢 种子: {seed if seed != -1 else '随机'}") + + # 打印LoRA信息 + lora_info = [] + if lora1_id.strip(): + lora_info.append(f"LoRA1: {lora1_id} (权重: {lora1_w})") + if lora2_id.strip(): + lora_info.append(f"LoRA2: {lora2_id} (权重: {lora2_w})") + if lora3_id.strip(): + lora_info.append(f"LoRA3: {lora3_id} (权重: {lora3_w})") + if lora_info: + print(f"🔧 LoRA配置: {', '.join(lora_info)}") + else: + print("🔧 未使用LoRA") + last_exception = None for i, token in enumerate(tokens): try: - print(f"🔄 尝试使用第 {i+1} 个API Token...") + print(f"🔄 尝试使用第 {i+1}/{len(tokens)} 个Token...") + url = 'https://api-inference.modelscope.cn/v1/images/generations' payload = { 'model': model, @@ -197,21 +413,40 @@ class ModelScopeImageNode: 'steps': steps, 'guidance': guidance } + + lora_dict = {} + if lora1_id and lora1_id.strip() != "": + lora_dict[lora1_id.strip()] = float(lora1_w) + if lora2_id and lora2_id.strip() != "": + lora_dict[lora2_id.strip()] = float(lora2_w) + if lora3_id and lora3_id.strip() != "": + lora_dict[lora3_id.strip()] = float(lora3_w) + + if lora_dict: + payload['loras'] = lora_dict + first_lora_id = next(iter(lora_dict.keys())) + first_lora_w = next(iter(lora_dict.values())) + payload['lora'] = first_lora_id + payload['lora_weight'] = first_lora_w + if negative_prompt.strip(): payload['negative_prompt'] = negative_prompt if seed != -1: payload['seed'] = seed else: import random - random_seed = random.randint(0, 2147483647) - payload['seed'] = random_seed + payload['seed'] = random.randint(0, 2147483647) + print(f"🎲 随机生成种子: {payload['seed']}") headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json', - 'X-ModelScope-Async-Mode': 'true' + 'X-ModelScope-Async-Mode': 'true', + 'X-ModelScope-Task-Type': 'text-to-image-generation', + 'X-ModelScope-Request-Params': json.dumps({'loras': lora_dict} if lora_dict else {}) } + print(f"🚀 发送API请求到 {model}...") submission_response = requests.post( url, data=json.dumps(payload, ensure_ascii=False).encode('utf-8'), @@ -220,11 +455,16 @@ class ModelScopeImageNode: ) if submission_response.status_code == 400: - # 尝试使用最小参数重试 + print("⚠️ 标准请求参数失败,尝试简化参数...") minimal_payload = { 'model': model, 'prompt': prompt } + if lora_dict: + minimal_payload['loras'] = lora_dict + minimal_payload['lora'] = first_lora_id + minimal_payload['lora_weight'] = first_lora_w + submission_response = requests.post( url, data=json.dumps(minimal_payload, ensure_ascii=False).encode('utf-8'), @@ -237,9 +477,10 @@ class ModelScopeImageNode: submission_json = submission_response.json() image_url = None + if 'task_id' in submission_json: task_id = submission_json['task_id'] - print(f"🕒 已提交任务,任务ID: {task_id},开始轮询...") + print(f"📌 获取任务ID: {task_id}, 开始轮询结果...") poll_start = time.time() max_wait_seconds = max(60, config.get('timeout', 720)) while True: @@ -251,53 +492,59 @@ class ModelScopeImageNode: }, timeout=config.get("image_download_timeout", 120) ) + if task_resp.status_code != 200: raise Exception(f"任务查询失败: {task_resp.status_code}, {task_resp.text}") + task_data = task_resp.json() status = task_data.get('task_status') + print(f"⌛ 任务状态: {status} (已等待 {int(time.time() - poll_start)} 秒)") + if status == 'SUCCEED': output_images = task_data.get('output_images') or [] if not output_images: raise Exception("任务成功但未返回图片URL") image_url = output_images[0] - print("✅ 任务完成,开始下载图片...") + print(f"✅ 任务完成,获取图片URL") break if status == 'FAILED': raise Exception(f"任务失败: {task_data}") if time.time() - poll_start > max_wait_seconds: - raise Exception("任务轮询超时,请稍后重试或降低并发") + raise Exception(f"任务轮询超时 ({max_wait_seconds}秒),请稍后重试或降低并发") time.sleep(5) elif 'images' in submission_json and len(submission_json['images']) > 0: image_url = submission_json['images'][0]['url'] - print(f"⬇️ 下载生成的图片...") + print(f"✅ 直接获取图片URL") else: raise Exception(f"未识别的API返回格式: {submission_json}") + print(f"📥 下载图片...") img_response = requests.get(image_url, timeout=config.get("image_download_timeout", 30)) if img_response.status_code != 200: raise Exception(f"图片下载失败: {img_response.status_code}") + print(f"🖼️ 处理图片数据...") pil_image = Image.open(BytesIO(img_response.content)) if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') image_np = np.array(pil_image).astype(np.float32) / 255.0 image_tensor = torch.from_numpy(image_np)[None,] - print(f"🎉 图片处理完成!使用的第 {i+1} 个API Token") + + print(f"✅ 第 {i+1} 个Token调用成功,图像生成完成!") return (image_tensor,) except Exception as e: last_exception = e - print(f"⚠️ 第 {i+1} 个API Token失败: {str(e)}") - if i < len(tokens) - 1: # 不是最后一个Token - print(f"➡️ 尝试下一个API Token...") + print(f"❌ 第 {i+1} 个Token调用失败: {str(e)}") + if i < len(tokens) - 1: + print(f"⏳ 准备尝试下一个Token...") continue else: - break # 所有Token都失败了 + break - # 所有Token都失败 raise Exception(f"所有 {len(tokens)} 个API Token都失败了。最后的错误: {str(last_exception)}") - +# -------------------------- 编辑节点(已添加LoRA功能) -------------------------- class ModelScopeImageEditNode: def __init__(self): pass @@ -307,7 +554,6 @@ class ModelScopeImageEditNode: config = load_config() saved_tokens = load_api_tokens() - # 获取模型列表 edit_models = config.get("image_edit_models", ["Qwen/Qwen-Image-Edit"]) gen_models = config.get("image_models", ["Qwen/Qwen-Image"]) @@ -369,6 +615,13 @@ class ModelScopeImageEditNode: "min": -1, "max": 2147483647 }), + # LoRA相关参数(与生图节点保持一致) + "lora1_id": ("STRING", {"default": "", "label": "LoRA1 模型ID"}), + "lora1_w": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA1 权重"}), + "lora2_id": ("STRING", {"default": "", "label": "LoRA2 模型ID"}), + "lora2_w": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA2 权重"}), + "lora3_id": ("STRING", {"default": "", "label": "LoRA3 模型ID"}), + "lora3_w": ("FLOAT", {"default": 0.8, "min": 0.0, "max": 2.0, "step": 0.1, "label": "LoRA3 权重"}), } } @@ -379,51 +632,68 @@ class ModelScopeImageEditNode: def edit_image(self, image, prompt, api_tokens, image_gen_mode=False, gen_model="Qwen/Qwen-Image", edit_model="Qwen/Qwen-Image-Edit", negative_prompt="", - width=512, height=512, steps=30, guidance=3.5, seed=-1): + width=512, height=512, steps=30, guidance=3.5, seed=-1, + lora1_id="", lora1_w=0.8, lora2_id="", lora2_w=0.8, lora3_id="", lora3_w=0.8): config = load_config() tokens = parse_api_tokens(api_tokens) if not tokens: raise Exception("请提供至少一个有效的API Token") - # 保存Token(如果提供了新的) + # 保存新Token(如果有变化) if api_tokens and api_tokens.strip() not in ["", "***已保存{}个Token***".format(len(load_api_tokens()))]: if save_api_tokens(tokens): print(f"✅ 已保存 {len(tokens)} 个API Token") else: print("⚠️ API Token保存失败,但不影响当前使用") - # 根据开关选择使用的模型 - if image_gen_mode: - model = gen_model - mode_name = "图生图" + mode = "图生图模式" if image_gen_mode else "图像编辑模式" + model = gen_model if image_gen_mode else edit_model + + print(f"🔍 开始图像编辑...") + print(f"📝 提示词: {prompt}") + print(f"❌ 反向提示词: {negative_prompt if negative_prompt else '无'}") + print(f"🤖 模型: {model} ({mode})") + print(f"🔑 可用Token数量: {len(tokens)}") + print(f"📐 尺寸: {width}x{height}") + print(f"🔄 步数: {steps}") + print(f"🧭 引导系数: {guidance}") + print(f"🔢 种子: {seed if seed != -1 else '随机'}") + + # 打印LoRA信息 + lora_info = [] + if lora1_id.strip(): + lora_info.append(f"LoRA1: {lora1_id} (权重: {lora1_w})") + if lora2_id.strip(): + lora_info.append(f"LoRA2: {lora2_id} (权重: {lora2_w})") + if lora3_id.strip(): + lora_info.append(f"LoRA3: {lora3_id} (权重: {lora3_w})") + if lora_info: + print(f"🔧 LoRA配置: {', '.join(lora_info)}") else: - model = edit_model - mode_name = "图像编辑" + print("🔧 未使用LoRA") - # 轮询尝试每个Token last_exception = None for i, token in enumerate(tokens): try: - print(f"🔄 尝试使用第 {i+1} 个API Token...") + print(f"🔄 尝试使用第 {i+1}/{len(tokens)} 个Token...") - # 将图像转换为临时文件并上传获取URL temp_img_path = None image_url = None try: - # 保存图像到临时文件 + # 保存临时图像并上传 temp_img_path = os.path.join(tempfile.gettempdir(), f"qwen_edit_temp_{int(time.time())}.jpg") if len(image.shape) == 4: img = image[0] else: img = image - i = 255. * img.cpu().numpy() - img_pil = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) + img_np = 255. * img.cpu().numpy() + img_pil = Image.fromarray(np.clip(img_np, 0, 255).astype(np.uint8)) img_pil.save(temp_img_path) - print(f"✅ 图像已保存到临时文件: {temp_img_path}") + print(f"💾 已保存临时图像到 {temp_img_path}") - # 上传图像到kefan.cn获取URL + # 上传图像 upload_url = 'https://ai.kefan.cn/api/upload/local' with open(temp_img_path, 'rb') as img_file: files = {'file': img_file} @@ -436,17 +706,13 @@ class ModelScopeImageEditNode: upload_data = upload_response.json() if upload_data.get('success') == True and 'data' in upload_data: image_url = upload_data['data'] - print(f"✅ 图像已上传成功,获取URL: {image_url}") - else: - print(f"⚠️ 图像上传返回错误: {upload_response.text}") - else: - print(f"⚠️ 图像上传失败: {upload_response.status_code}, {upload_response.text}") + print(f"📤 图像上传成功,URL: {image_url[:50]}...") except Exception as e: - print(f"⚠️ 图像上传异常: {str(e)}") + print(f"⚠️ 图像上传失败,将使用base64编码: {str(e)}") - # 如果上传失败,回退到base64 + # 构建请求 payload if not image_url: - print("⚠️ 图像URL获取失败,回退到使用base64") + print("🔄 转换图像为base64格式...") image_data = tensor_to_base64_url(image) payload = { 'model': model, @@ -460,33 +726,48 @@ class ModelScopeImageEditNode: 'image_url': image_url } + # 构建LoRA参数 + lora_dict = {} + if lora1_id and lora1_id.strip() != "": + lora_dict[lora1_id.strip()] = float(lora1_w) + if lora2_id and lora2_id.strip() != "": + lora_dict[lora2_id.strip()] = float(lora2_w) + if lora3_id and lora3_id.strip() != "": + lora_dict[lora3_id.strip()] = float(lora3_w) + + if lora_dict: + payload['loras'] = lora_dict + first_lora_id = next(iter(lora_dict.keys())) + first_lora_w = next(iter(lora_dict.values())) + payload['lora'] = first_lora_id + payload['lora_weight'] = first_lora_w + + # 添加其他参数 if negative_prompt.strip(): payload['negative_prompt'] = negative_prompt - - # 添加新参数 if width != 512 or height != 512: - size = f"{width}x{height}" - payload['size'] = size - + payload['size'] = f"{width}x{height}" if steps != 30: payload['steps'] = steps - if guidance != 3.5: payload['guidance'] = guidance - if seed != -1: payload['seed'] = seed + else: + import random + payload['seed'] = random.randint(0, 2147483647) + print(f"🎲 随机生成种子: {payload['seed']}") + # 设置请求头 headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json', - 'X-ModelScope-Async-Mode': 'true' + 'X-ModelScope-Async-Mode': 'true', + 'X-ModelScope-Task-Type': 'image-to-image-generation', + 'X-ModelScope-Request-Params': json.dumps({'loras': lora_dict} if lora_dict else {}) } - print(f"🖼️ 开始{mode_name}...") - print(f"✏️ 编辑提示: {prompt}") - print(f"🧠 使用模型: {model}") - + print(f"🚀 发送API请求到 {model}...") url = 'https://api-inference.modelscope.cn/v1/images/generations' submission_response = requests.post( url, @@ -503,7 +784,7 @@ class ModelScopeImageEditNode: if 'task_id' in submission_json: task_id = submission_json['task_id'] - print(f"🕒 已提交任务,任务ID: {task_id},开始轮询...") + print(f"📌 获取任务ID: {task_id}, 开始轮询结果...") poll_start = time.time() max_wait_seconds = max(60, config.get('timeout', 720)) @@ -522,31 +803,31 @@ class ModelScopeImageEditNode: task_data = task_resp.json() status = task_data.get('task_status') + print(f"⌛ 任务状态: {status} (已等待 {int(time.time() - poll_start)} 秒)") if status == 'SUCCEED': output_images = task_data.get('output_images') or [] if not output_images: raise Exception("任务成功但未返回图片URL") result_image_url = output_images[0] - print("✅ 任务完成,开始下载编辑后的图片...") + print(f"✅ 任务完成,获取图片URL") break - if status == 'FAILED': error_message = task_data.get('errors', {}).get('message', '未知错误') error_code = task_data.get('errors', {}).get('code', '未知错误码') raise Exception(f"任务失败: 错误码 {error_code}, 错误信息: {error_message}") - if time.time() - poll_start > max_wait_seconds: - raise Exception("任务轮询超时,请稍后重试或降低并发") - + raise Exception(f"任务轮询超时 ({max_wait_seconds}秒),请稍后重试或降低并发") time.sleep(5) else: raise Exception(f"未识别的API返回格式: {submission_json}") + print(f"📥 下载编辑后的图片...") img_response = requests.get(result_image_url, timeout=config.get("image_download_timeout", 30)) if img_response.status_code != 200: raise Exception(f"图片下载失败: {img_response.status_code}") + print(f"🖼️ 处理图片数据...") pil_image = Image.open(BytesIO(img_response.content)) if pil_image.mode != 'RGB': pil_image = pil_image.convert('RGB') @@ -558,38 +839,43 @@ class ModelScopeImageEditNode: if temp_img_path and os.path.exists(temp_img_path): try: os.remove(temp_img_path) + print(f"🧹 已删除临时图像文件") except: - pass + print(f"⚠️ 无法删除临时图像文件 {temp_img_path}") - print(f"🎉 {mode_name}完成!使用的第 {i+1} 个API Token") + print(f"✅ 第 {i+1} 个Token调用成功,图像编辑完成!") return (image_tensor,) except Exception as e: last_exception = e - print(f"⚠️ 第 {i+1} 个API Token失败: {str(e)}") + print(f"❌ 第 {i+1} 个Token调用失败: {str(e)}") # 清理临时文件 if temp_img_path and os.path.exists(temp_img_path): try: os.remove(temp_img_path) except: pass - if i < len(tokens) - 1: # 不是最后一个Token - print(f"➡️ 尝试下一个API Token...") + if i < len(tokens) - 1: + print(f"⏳ 准备尝试下一个Token...") continue else: - break # 所有Token都失败了 + break - # 所有Token都失败 raise Exception(f"所有 {len(tokens)} 个API Token都失败了。最后的错误: {str(last_exception)}") - -# 节点映射 +# -------------------------- 节点映射 -------------------------- NODE_CLASS_MAPPINGS = { "ModelScopeImageNode": ModelScopeImageNode, - "ModelScopeImageEditNode": ModelScopeImageEditNode + "ModelScopeImageEditNode": ModelScopeImageEditNode, + "ModelScopeLoraPresetNode": ModelScopeLoraPresetNode, + "ModelScopeSingleLoraLoaderNode": ModelScopeSingleLoraLoaderNode, + "ModelScopeMultiLoraLoaderNode": ModelScopeMultiLoraLoaderNode } NODE_DISPLAY_NAME_MAPPINGS = { "ModelScopeImageNode": "ModelScope-Image 生图节点", - "ModelScopeImageEditNode": "ModelScope-Image 图像编辑节点" -} + "ModelScopeImageEditNode": "ModelScope-Image 图像编辑节点", + "ModelScopeLoraPresetNode": "ModelScope-LoRA 预设管理", + "ModelScopeSingleLoraLoaderNode": "ModelScope-LoRA 单LoRA加载", + "ModelScopeMultiLoraLoaderNode": "ModelScope-LoRA 多LoRA加载" +} \ No newline at end of file diff --git a/modelscope_text_node.py b/modelscope_text_node.py index c0458dc..d6f75b1 100644 --- a/modelscope_text_node.py +++ b/modelscope_text_node.py @@ -22,39 +22,36 @@ def load_config(): "default_model": "Qwen/Qwen-Image", "timeout": 720, "image_download_timeout": 30, - "default_prompt": "A beautiful landscape" + "default_prompt": "A beautiful landscape", + "default_text_model": "Qwen/Qwen3-Coder-480B-A35B-Instruct", + "default_system_prompt": "You are a helpful assistant.", + "default_user_prompt": "你好", + "api_token": "" } +def save_config(config): + config_path = os.path.join(os.path.dirname(__file__), 'modelscope_config.json') + try: + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config, f, ensure_ascii=False, indent=2) + return True + except Exception as e: + print(f"保存配置失败: {e}") + return False + def load_api_token(): - token_path = os.path.join(os.path.dirname(__file__), '.qwen_token') try: cfg = load_config() - token_from_cfg = cfg.get("api_token", "").strip() - if token_from_cfg: - return token_from_cfg + return cfg.get("api_token", "").strip() except Exception as e: - print(f"读取config.json中的token失败: {e}") - try: - if os.path.exists(token_path): - with open(token_path, 'r', encoding='utf-8') as f: - token = f.read().strip() - return token if token else "" - return "" - except Exception as e: - print(f"加载token失败: {e}") + print(f"读取 config.json中的token失败: {e}") return "" def save_api_token(token): - token_path = os.path.join(os.path.dirname(__file__), '.qwen_token') try: - with open(token_path, 'w', encoding='utf-8') as f: - f.write(token) cfg = load_config() cfg["api_token"] = token - config_path = os.path.join(os.path.dirname(__file__), 'config.json') - with open(config_path, 'w', encoding='utf-8') as f: - json.dump(cfg, f, ensure_ascii=False, indent=2) - return True + return save_config(cfg) except Exception as e: print(f"保存token失败: {e}") return False @@ -83,7 +80,7 @@ class ModelScopeTextNode: "default": config.get("default_user_prompt", "你好") }), "api_token": ("STRING", { - "default": "", + "default": saved_token, "placeholder": "请输入您的魔搭API Token", "multiline": False }), @@ -132,7 +129,7 @@ class ModelScopeTextNode: saved_token = load_api_token() if api_token != saved_token: if save_api_token(api_token): - print("✅ API Token已自动保存") + print("✅ API Token已自动保存到modelscope_config.json") else: print("⚠️ API Token保存失败,但不影响当前使用") diff --git a/modelscope_vision_node.py b/modelscope_vision_node.py index 10d30e3..5f4ef65 100644 --- a/modelscope_vision_node.py +++ b/modelscope_vision_node.py @@ -19,7 +19,7 @@ except ImportError: OpenAI = None def load_config(): - config_path = os.path.join(os.path.dirname(__file__), 'modelscope_config.json') + config_path = os.path.join.join(os.path.dirname(__file__), 'modelscope_config.json') try: with open(config_path, 'r', encoding='utf-8') as f: return json.load(f) @@ -28,39 +28,36 @@ def load_config(): "default_model": "Qwen/Qwen-Image", "timeout": 720, "image_download_timeout": 30, - "default_prompt": "A beautiful landscape" + "default_prompt": "A beautiful landscape", + "api_token": "" # 确保默认默认配置中添加api_token字段 } +def save_config(config): + """保存配置到modelscope_config.json""" + config_path = os.path.join(os.path.dirname(__file__), 'modelscope_config.json') + try: + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config, f, ensure_ascii=False, indent=2) + return True + except Exception as e: + print(f"保存配置失败: {e}") + return False + def load_api_token(): - token_path = os.path.join(os.path.dirname(__file__), '.qwen_token') + """仅从modelscope_config.json读取API Token""" try: cfg = load_config() - token_from_cfg = cfg.get("api_token", "").strip() - if token_from_cfg: - return token_from_cfg + return cfg.get("api_token", "").strip() except Exception as e: print(f"读取config.json中的token失败: {e}") - try: - if os.path.exists(token_path): - with open(token_path, 'r', encoding='utf-8') as f: - token = f.read().strip() - return token if token else "" - return "" - except Exception as e: - print(f"加载token失败: {e}") return "" def save_api_token(token): - token_path = os.path.join(os.path.dirname(__file__), '.qwen_token') + """仅将API Token保存到modelscope_config.json""" try: - with open(token_path, 'w', encoding='utf-8') as f: - f.write(token) cfg = load_config() - cfg["api_token"] = token - config_path = os.path.join(os.path.dirname(__file__), 'config.json') - with open(config_path, 'w', encoding='utf-8') as f: - json.dump(cfg, f, ensure_ascii=False, indent=2) - return True + cfg["api_token"] = token.strip() + return save_config(cfg) except Exception as e: print(f"保存token失败: {e}") return False @@ -154,7 +151,7 @@ class ModelScopeVisionNode: saved_token = load_api_token() if api_token != saved_token: if save_api_token(api_token): - print("✅ API Token已自动保存") + print("✅ API Token已自动保存到modelscope_config.json") else: print("⚠️ API Token保存失败,但不影响当前使用")