mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Add ComfyMetadataParser for Civitai ComfyUI metadata handling
- Introduced ComfyMetadataParser class to parse metadata from Civitai ComfyUI JSON format. - Implemented methods to validate metadata structure, extract LoRA and checkpoint information, and retrieve additional model details from Civitai. - Enhanced error handling and logging for metadata parsing failures. - Updated RecipeParserFactory to prioritize ComfyMetadataParser for valid JSON inputs.
This commit is contained in:
@@ -523,6 +523,250 @@ class A1111MetadataParser(RecipeMetadataParser):
|
|||||||
return {"error": str(e), "loras": []}
|
return {"error": str(e), "loras": []}
|
||||||
|
|
||||||
|
|
||||||
|
class ComfyMetadataParser(RecipeMetadataParser):
|
||||||
|
"""Parser for Civitai ComfyUI metadata JSON format"""
|
||||||
|
|
||||||
|
METADATA_MARKER = r"class_type"
|
||||||
|
|
||||||
|
def is_metadata_matching(self, user_comment: str) -> bool:
|
||||||
|
"""Check if the user comment matches the ComfyUI metadata format"""
|
||||||
|
try:
|
||||||
|
data = json.loads(user_comment)
|
||||||
|
# Check if it contains class_type nodes typical of ComfyUI workflow
|
||||||
|
return isinstance(data, dict) and any(isinstance(v, dict) and 'class_type' in v for v in data.values())
|
||||||
|
except (json.JSONDecodeError, TypeError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def parse_metadata(self, user_comment: str, recipe_scanner=None, civitai_client=None) -> Dict[str, Any]:
|
||||||
|
"""Parse metadata from Civitai ComfyUI metadata format"""
|
||||||
|
try:
|
||||||
|
data = json.loads(user_comment)
|
||||||
|
loras = []
|
||||||
|
|
||||||
|
# Find all LoraLoader nodes
|
||||||
|
lora_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and v.get('class_type') == 'LoraLoader'}
|
||||||
|
|
||||||
|
if not lora_nodes:
|
||||||
|
return {"error": "No LoRA information found in this ComfyUI workflow", "loras": []}
|
||||||
|
|
||||||
|
# Process each LoraLoader node
|
||||||
|
for node_id, node in lora_nodes.items():
|
||||||
|
if 'inputs' not in node or 'lora_name' not in node['inputs']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
lora_name = node['inputs'].get('lora_name', '')
|
||||||
|
|
||||||
|
# Parse the URN to extract model ID and version ID
|
||||||
|
# Format: "urn:air:sdxl:lora:civitai:1107767@1253442"
|
||||||
|
lora_id_match = re.search(r'civitai:(\d+)@(\d+)', lora_name)
|
||||||
|
if not lora_id_match:
|
||||||
|
continue
|
||||||
|
|
||||||
|
model_id = lora_id_match.group(1)
|
||||||
|
model_version_id = lora_id_match.group(2)
|
||||||
|
|
||||||
|
# Get strength from node inputs
|
||||||
|
weight = node['inputs'].get('strength_model', 1.0)
|
||||||
|
|
||||||
|
# Initialize lora entry with default values
|
||||||
|
lora_entry = {
|
||||||
|
'id': model_version_id,
|
||||||
|
'modelId': model_id,
|
||||||
|
'name': f"Lora {model_id}", # Default name
|
||||||
|
'version': '',
|
||||||
|
'type': 'lora',
|
||||||
|
'weight': weight,
|
||||||
|
'existsLocally': False,
|
||||||
|
'localPath': None,
|
||||||
|
'file_name': '',
|
||||||
|
'hash': '',
|
||||||
|
'thumbnailUrl': '/loras_static/images/no-preview.png',
|
||||||
|
'baseModel': '',
|
||||||
|
'size': 0,
|
||||||
|
'downloadUrl': '',
|
||||||
|
'isDeleted': False
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get additional info from Civitai if client is available
|
||||||
|
if civitai_client:
|
||||||
|
try:
|
||||||
|
civitai_info = await civitai_client.get_model_version_info(model_version_id)
|
||||||
|
if civitai_info and civitai_info.get("error") != "Model not found":
|
||||||
|
# Update lora entry with model name and version
|
||||||
|
if 'model' in civitai_info and 'name' in civitai_info['model']:
|
||||||
|
lora_entry['name'] = civitai_info['model']['name']
|
||||||
|
lora_entry['version'] = civitai_info.get('name', '')
|
||||||
|
|
||||||
|
# Check if this is an early access lora
|
||||||
|
if civitai_info.get('earlyAccessEndsAt'):
|
||||||
|
early_access_date = civitai_info.get('earlyAccessEndsAt', '')
|
||||||
|
lora_entry['isEarlyAccess'] = True
|
||||||
|
lora_entry['earlyAccessEndsAt'] = early_access_date
|
||||||
|
|
||||||
|
# Get thumbnail URL from first image
|
||||||
|
if 'images' in civitai_info and civitai_info['images']:
|
||||||
|
lora_entry['thumbnailUrl'] = civitai_info['images'][0].get('url', '')
|
||||||
|
|
||||||
|
# Get base model
|
||||||
|
lora_entry['baseModel'] = civitai_info.get('baseModel', '')
|
||||||
|
|
||||||
|
# Get download URL
|
||||||
|
lora_entry['downloadUrl'] = civitai_info.get('downloadUrl', '')
|
||||||
|
|
||||||
|
# Check if this LoRA exists locally by SHA256 hash
|
||||||
|
if 'files' in civitai_info:
|
||||||
|
model_file = next((file for file in civitai_info.get('files', [])
|
||||||
|
if file.get('type') == 'Model'), None)
|
||||||
|
if model_file and recipe_scanner:
|
||||||
|
sha256 = model_file.get('hashes', {}).get('SHA256', '')
|
||||||
|
if sha256:
|
||||||
|
lora_scanner = recipe_scanner._lora_scanner
|
||||||
|
exists_locally = lora_scanner.has_lora_hash(sha256)
|
||||||
|
if exists_locally:
|
||||||
|
local_path = lora_scanner.get_lora_path_by_hash(sha256)
|
||||||
|
lora_entry['existsLocally'] = True
|
||||||
|
lora_entry['localPath'] = local_path
|
||||||
|
lora_entry['file_name'] = os.path.splitext(os.path.basename(local_path))[0]
|
||||||
|
else:
|
||||||
|
# For missing LoRAs, get file_name from model_file.name
|
||||||
|
file_name = model_file.get('name', '')
|
||||||
|
lora_entry['file_name'] = os.path.splitext(file_name)[0] if file_name else ''
|
||||||
|
|
||||||
|
lora_entry['hash'] = sha256
|
||||||
|
lora_entry['size'] = model_file.get('sizeKB', 0) * 1024
|
||||||
|
else:
|
||||||
|
lora_entry['isDeleted'] = True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error fetching Civitai info for LoRA: {e}")
|
||||||
|
|
||||||
|
loras.append(lora_entry)
|
||||||
|
|
||||||
|
# Find checkpoint info
|
||||||
|
checkpoint_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and v.get('class_type') == 'CheckpointLoaderSimple'}
|
||||||
|
checkpoint = None
|
||||||
|
checkpoint_id = None
|
||||||
|
checkpoint_version_id = None
|
||||||
|
|
||||||
|
if checkpoint_nodes:
|
||||||
|
# Get the first checkpoint node
|
||||||
|
checkpoint_node = next(iter(checkpoint_nodes.values()))
|
||||||
|
if 'inputs' in checkpoint_node and 'ckpt_name' in checkpoint_node['inputs']:
|
||||||
|
checkpoint_name = checkpoint_node['inputs']['ckpt_name']
|
||||||
|
# Parse checkpoint URN
|
||||||
|
checkpoint_match = re.search(r'civitai:(\d+)@(\d+)', checkpoint_name)
|
||||||
|
if checkpoint_match:
|
||||||
|
checkpoint_id = checkpoint_match.group(1)
|
||||||
|
checkpoint_version_id = checkpoint_match.group(2)
|
||||||
|
checkpoint = {
|
||||||
|
'id': checkpoint_version_id,
|
||||||
|
'modelId': checkpoint_id,
|
||||||
|
'name': f"Checkpoint {checkpoint_id}",
|
||||||
|
'version': '',
|
||||||
|
'type': 'checkpoint'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get additional checkpoint info from Civitai
|
||||||
|
if civitai_client:
|
||||||
|
try:
|
||||||
|
civitai_info = await civitai_client.get_model_version_info(checkpoint_version_id)
|
||||||
|
if civitai_info and civitai_info.get("error") != "Model not found":
|
||||||
|
if 'model' in civitai_info and 'name' in civitai_info['model']:
|
||||||
|
checkpoint['name'] = civitai_info['model']['name']
|
||||||
|
checkpoint['version'] = civitai_info.get('name', '')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error fetching Civitai info for checkpoint: {e}")
|
||||||
|
|
||||||
|
# Extract generation parameters
|
||||||
|
gen_params = {}
|
||||||
|
|
||||||
|
# First try to get from extraMetadata
|
||||||
|
if 'extraMetadata' in data:
|
||||||
|
try:
|
||||||
|
# extraMetadata is a JSON string that needs to be parsed
|
||||||
|
extra_metadata = json.loads(data['extraMetadata'])
|
||||||
|
|
||||||
|
# Map fields from extraMetadata to our standard format
|
||||||
|
mapping = {
|
||||||
|
'prompt': 'prompt',
|
||||||
|
'negativePrompt': 'negative_prompt',
|
||||||
|
'steps': 'steps',
|
||||||
|
'sampler': 'sampler',
|
||||||
|
'cfgScale': 'cfg_scale',
|
||||||
|
'seed': 'seed'
|
||||||
|
}
|
||||||
|
|
||||||
|
for src_key, dest_key in mapping.items():
|
||||||
|
if src_key in extra_metadata:
|
||||||
|
gen_params[dest_key] = extra_metadata[src_key]
|
||||||
|
|
||||||
|
# If size info is available, format as "width x height"
|
||||||
|
if 'width' in extra_metadata and 'height' in extra_metadata:
|
||||||
|
gen_params['size'] = f"{extra_metadata['width']}x{extra_metadata['height']}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error parsing extraMetadata: {e}")
|
||||||
|
|
||||||
|
# If extraMetadata doesn't have all the info, try to get from nodes
|
||||||
|
if not gen_params or len(gen_params) < 3: # At least we want prompt, negative_prompt, and steps
|
||||||
|
# Find positive prompt node
|
||||||
|
positive_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and
|
||||||
|
v.get('class_type', '').endswith('CLIPTextEncode') and
|
||||||
|
v.get('_meta', {}).get('title') == 'Positive'}
|
||||||
|
|
||||||
|
if positive_nodes:
|
||||||
|
positive_node = next(iter(positive_nodes.values()))
|
||||||
|
if 'inputs' in positive_node and 'text' in positive_node['inputs']:
|
||||||
|
gen_params['prompt'] = positive_node['inputs']['text']
|
||||||
|
|
||||||
|
# Find negative prompt node
|
||||||
|
negative_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and
|
||||||
|
v.get('class_type', '').endswith('CLIPTextEncode') and
|
||||||
|
v.get('_meta', {}).get('title') == 'Negative'}
|
||||||
|
|
||||||
|
if negative_nodes:
|
||||||
|
negative_node = next(iter(negative_nodes.values()))
|
||||||
|
if 'inputs' in negative_node and 'text' in negative_node['inputs']:
|
||||||
|
gen_params['negative_prompt'] = negative_node['inputs']['text']
|
||||||
|
|
||||||
|
# Find KSampler node for other parameters
|
||||||
|
ksampler_nodes = {k: v for k, v in data.items() if isinstance(v, dict) and v.get('class_type') == 'KSampler'}
|
||||||
|
|
||||||
|
if ksampler_nodes:
|
||||||
|
ksampler_node = next(iter(ksampler_nodes.values()))
|
||||||
|
if 'inputs' in ksampler_node:
|
||||||
|
inputs = ksampler_node['inputs']
|
||||||
|
if 'sampler_name' in inputs:
|
||||||
|
gen_params['sampler'] = inputs['sampler_name']
|
||||||
|
if 'steps' in inputs:
|
||||||
|
gen_params['steps'] = inputs['steps']
|
||||||
|
if 'cfg' in inputs:
|
||||||
|
gen_params['cfg_scale'] = inputs['cfg']
|
||||||
|
if 'seed' in inputs:
|
||||||
|
gen_params['seed'] = inputs['seed']
|
||||||
|
|
||||||
|
# Determine base model from loras info
|
||||||
|
base_model = None
|
||||||
|
if loras:
|
||||||
|
# Use the most common base model from loras
|
||||||
|
base_models = [lora['baseModel'] for lora in loras if lora.get('baseModel')]
|
||||||
|
if base_models:
|
||||||
|
from collections import Counter
|
||||||
|
base_model_counts = Counter(base_models)
|
||||||
|
base_model = base_model_counts.most_common(1)[0][0]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'base_model': base_model,
|
||||||
|
'loras': loras,
|
||||||
|
'checkpoint': checkpoint,
|
||||||
|
'gen_params': gen_params,
|
||||||
|
'from_comfy_metadata': True
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error parsing ComfyUI metadata: {e}", exc_info=True)
|
||||||
|
return {"error": str(e), "loras": []}
|
||||||
|
|
||||||
|
|
||||||
class RecipeParserFactory:
|
class RecipeParserFactory:
|
||||||
"""Factory for creating recipe metadata parsers"""
|
"""Factory for creating recipe metadata parsers"""
|
||||||
|
|
||||||
@@ -537,6 +781,14 @@ class RecipeParserFactory:
|
|||||||
Returns:
|
Returns:
|
||||||
Appropriate RecipeMetadataParser implementation
|
Appropriate RecipeMetadataParser implementation
|
||||||
"""
|
"""
|
||||||
|
# Try ComfyMetadataParser first since it requires valid JSON
|
||||||
|
try:
|
||||||
|
if ComfyMetadataParser().is_metadata_matching(user_comment):
|
||||||
|
return ComfyMetadataParser()
|
||||||
|
except Exception:
|
||||||
|
# If JSON parsing fails, move on to other parsers
|
||||||
|
pass
|
||||||
|
|
||||||
if RecipeFormatParser().is_metadata_matching(user_comment):
|
if RecipeFormatParser().is_metadata_matching(user_comment):
|
||||||
return RecipeFormatParser()
|
return RecipeFormatParser()
|
||||||
elif StandardMetadataParser().is_metadata_matching(user_comment):
|
elif StandardMetadataParser().is_metadata_matching(user_comment):
|
||||||
|
|||||||
153
refs/civitai_comfy_metadata.json
Normal file
153
refs/civitai_comfy_metadata.json
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
{
|
||||||
|
"resource-stack": {
|
||||||
|
"class_type": "CheckpointLoaderSimple",
|
||||||
|
"inputs": { "ckpt_name": "urn:air:sdxl:checkpoint:civitai:827184@1410435" }
|
||||||
|
},
|
||||||
|
"resource-stack-1": {
|
||||||
|
"class_type": "LoraLoader",
|
||||||
|
"inputs": {
|
||||||
|
"lora_name": "urn:air:sdxl:lora:civitai:1107767@1253442",
|
||||||
|
"strength_model": 1,
|
||||||
|
"strength_clip": 1,
|
||||||
|
"model": ["resource-stack", 0],
|
||||||
|
"clip": ["resource-stack", 1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resource-stack-2": {
|
||||||
|
"class_type": "LoraLoader",
|
||||||
|
"inputs": {
|
||||||
|
"lora_name": "urn:air:sdxl:lora:civitai:1342708@1516344",
|
||||||
|
"strength_model": 1,
|
||||||
|
"strength_clip": 1,
|
||||||
|
"model": ["resource-stack-1", 0],
|
||||||
|
"clip": ["resource-stack-1", 1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"resource-stack-3": {
|
||||||
|
"class_type": "LoraLoader",
|
||||||
|
"inputs": {
|
||||||
|
"lora_name": "urn:air:sdxl:lora:civitai:122359@135867",
|
||||||
|
"strength_model": 1.55,
|
||||||
|
"strength_clip": 1,
|
||||||
|
"model": ["resource-stack-2", 0],
|
||||||
|
"clip": ["resource-stack-2", 1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"class_type": "smZ CLIPTextEncode",
|
||||||
|
"inputs": {
|
||||||
|
"text": "masterpiece, best quality, amazing quality, detailed setting, detailed background, 1girl, yunyun (konosuba), nude, red eyes, hair ornament, braid, hair between eyes,low twintails, pink ribbon, bow, hair bow, pussy, frilled skirt, layered skirt, belt, pink thighhighs, (pussy juice), large insertion, vaginal tugging, pussy grip, detailed skin, detailed soles, stretched pussy, feet in stockings, ass, nipples, medium breasts, french kiss, anus, shocked, nervous, penis awe, BREAK Professor\u0027s office, college student, pornographic, 1boy, close eyes, (musscular male, detailed large cock), vaginal sex, college office setting, ass grab, fucking, riding, cowgirl, erotic, side view, deep fucking",
|
||||||
|
"parser": "comfy",
|
||||||
|
"text_g": "",
|
||||||
|
"text_l": "",
|
||||||
|
"ascore": 2.5,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"crop_w": 0,
|
||||||
|
"crop_h": 0,
|
||||||
|
"target_width": 0,
|
||||||
|
"target_height": 0,
|
||||||
|
"smZ_steps": 1,
|
||||||
|
"mean_normalization": true,
|
||||||
|
"multi_conditioning": true,
|
||||||
|
"use_old_emphasis_implementation": false,
|
||||||
|
"with_SDXL": false,
|
||||||
|
"clip": ["resource-stack-3", 1]
|
||||||
|
},
|
||||||
|
"_meta": { "title": "Positive" }
|
||||||
|
},
|
||||||
|
"7": {
|
||||||
|
"class_type": "smZ CLIPTextEncode",
|
||||||
|
"inputs": {
|
||||||
|
"text": "bad quality,worst quality,worst detail,sketch,censor",
|
||||||
|
"parser": "comfy",
|
||||||
|
"text_g": "",
|
||||||
|
"text_l": "",
|
||||||
|
"ascore": 2.5,
|
||||||
|
"width": 0,
|
||||||
|
"height": 0,
|
||||||
|
"crop_w": 0,
|
||||||
|
"crop_h": 0,
|
||||||
|
"target_width": 0,
|
||||||
|
"target_height": 0,
|
||||||
|
"smZ_steps": 1,
|
||||||
|
"mean_normalization": true,
|
||||||
|
"multi_conditioning": true,
|
||||||
|
"use_old_emphasis_implementation": false,
|
||||||
|
"with_SDXL": false,
|
||||||
|
"clip": ["resource-stack-3", 1]
|
||||||
|
},
|
||||||
|
"_meta": { "title": "Negative" }
|
||||||
|
},
|
||||||
|
"20": {
|
||||||
|
"class_type": "UpscaleModelLoader",
|
||||||
|
"inputs": { "model_name": "urn:air:other:upscaler:civitai:147759@164821" },
|
||||||
|
"_meta": { "title": "Load Upscale Model" }
|
||||||
|
},
|
||||||
|
"17": {
|
||||||
|
"class_type": "LoadImage",
|
||||||
|
"inputs": {
|
||||||
|
"image": "https://orchestration.civitai.com/v2/consumer/blobs/5KZ6358TW8CNEGPZKD08NVDB30",
|
||||||
|
"upload": "image"
|
||||||
|
},
|
||||||
|
"_meta": { "title": "Image Load" }
|
||||||
|
},
|
||||||
|
"19": {
|
||||||
|
"class_type": "ImageUpscaleWithModel",
|
||||||
|
"inputs": { "upscale_model": ["20", 0], "image": ["17", 0] },
|
||||||
|
"_meta": { "title": "Upscale Image (using Model)" }
|
||||||
|
},
|
||||||
|
"23": {
|
||||||
|
"class_type": "ImageScale",
|
||||||
|
"inputs": {
|
||||||
|
"upscale_method": "nearest-exact",
|
||||||
|
"crop": "disabled",
|
||||||
|
"width": 1280,
|
||||||
|
"height": 1856,
|
||||||
|
"image": ["19", 0]
|
||||||
|
},
|
||||||
|
"_meta": { "title": "Upscale Image" }
|
||||||
|
},
|
||||||
|
"21": {
|
||||||
|
"class_type": "VAEEncode",
|
||||||
|
"inputs": { "pixels": ["23", 0], "vae": ["resource-stack", 2] },
|
||||||
|
"_meta": { "title": "VAE Encode" }
|
||||||
|
},
|
||||||
|
"11": {
|
||||||
|
"class_type": "KSampler",
|
||||||
|
"inputs": {
|
||||||
|
"sampler_name": "euler_ancestral",
|
||||||
|
"scheduler": "normal",
|
||||||
|
"seed": 2088370631,
|
||||||
|
"steps": 47,
|
||||||
|
"cfg": 6.5,
|
||||||
|
"denoise": 0.3,
|
||||||
|
"model": ["resource-stack-3", 0],
|
||||||
|
"positive": ["6", 0],
|
||||||
|
"negative": ["7", 0],
|
||||||
|
"latent_image": ["21", 0]
|
||||||
|
},
|
||||||
|
"_meta": { "title": "KSampler" }
|
||||||
|
},
|
||||||
|
"13": {
|
||||||
|
"class_type": "VAEDecode",
|
||||||
|
"inputs": { "samples": ["11", 0], "vae": ["resource-stack", 2] },
|
||||||
|
"_meta": { "title": "VAE Decode" }
|
||||||
|
},
|
||||||
|
"12": {
|
||||||
|
"class_type": "SaveImage",
|
||||||
|
"inputs": { "filename_prefix": "ComfyUI", "images": ["13", 0] },
|
||||||
|
"_meta": { "title": "Save Image" }
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"airs": [
|
||||||
|
"urn:air:other:upscaler:civitai:147759@164821",
|
||||||
|
"urn:air:sdxl:checkpoint:civitai:827184@1410435",
|
||||||
|
"urn:air:sdxl:lora:civitai:1107767@1253442",
|
||||||
|
"urn:air:sdxl:lora:civitai:1342708@1516344",
|
||||||
|
"urn:air:sdxl:lora:civitai:122359@135867"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extraMetadata": "{\u0022prompt\u0022:\u0022masterpiece, best quality, amazing quality, detailed setting, detailed background, 1girl, yunyun (konosuba), nude, red eyes, hair ornament, braid, hair between eyes,low twintails, pink ribbon, bow, hair bow, pussy, frilled skirt, layered skirt, belt, pink thighhighs, (pussy juice), large insertion, vaginal tugging, pussy grip, detailed skin, detailed soles, stretched pussy, feet in stockings, ass, nipples, medium breasts, french kiss, anus, shocked, nervous, penis awe, BREAK Professor\u0027s office, college student, pornographic, 1boy, close eyes, (musscular male, detailed large cock), vaginal sex, college office setting, ass grab, fucking, riding, cowgirl, erotic, side view, deep fucking\u0022,\u0022negativePrompt\u0022:\u0022bad quality,worst quality,worst detail,sketch,censor\u0022,\u0022steps\u0022:47,\u0022cfgScale\u0022:6.5,\u0022sampler\u0022:\u0022euler_ancestral\u0022,\u0022workflowId\u0022:\u0022img2img-hires\u0022,\u0022resources\u0022:[{\u0022modelVersionId\u0022:1410435,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:1410435,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:1253442,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:1516344,\u0022strength\u0022:1},{\u0022modelVersionId\u0022:135867,\u0022strength\u0022:1.55}],\u0022remixOfId\u0022:32140259}"
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user