Spaces:
Running on Zero
Running on Zero
| """ | |
| MCP Tool: run | |
| Unified image generation task submission and execution interface. | |
| """ | |
| import time | |
| import uuid | |
| import threading | |
| from .common import ( | |
| _load_yaml, | |
| _MODEL_LIST_PATH, | |
| _TASK_DEFINITIONS, | |
| _TASKS_DB, | |
| _execute_imagegen_pipeline, | |
| ) | |
| from .error_schema import make_validation_error, make_not_found_error | |
| def handle_run(params: dict) -> dict: | |
| """ | |
| Unified image generation task execution interface. | |
| [OPTIONAL CONTROL PARAMETERS] | |
| - seed (int): Random seed for generation. Default: -1 (random seed). Specify >=0 for deterministic reproducibility. | |
| - batch_size (int): Number of images generated in a single batch (1 to 16, default: 1). | |
| - zero_gpu_duration (int): GPU quota allocation in seconds on HuggingFace ZeroGPU spaces (default: 60, max: 120). | |
| - negative_prompt (str): Text prompt specifying undesirable elements to avoid. | |
| - steps (int), cfg (float), sampler (str), scheduler (str): Inference hyperparams (auto-applied from model defaults if omitted). | |
| [Paste-and-Run json_params Example (Basic)] | |
| { | |
| "task_type": "txt2img", | |
| "model": "stabilityai/SDXL-Base-1.0", | |
| "prompt": "A majestic lion jumping from a big stone at night", | |
| "width": 1024, | |
| "height": 1024 | |
| } | |
| [Paste-and-Run json_params Example (With chain)] | |
| { | |
| "task_type": "txt2img", | |
| "model": "stabilityai/SDXL-Base-1.0", | |
| "prompt": "A majestic lion jumping from a big stone at night", | |
| "width": 1024, | |
| "height": 1024, | |
| "chain": [ | |
| { | |
| "injector_type": "lora", | |
| "source": "Civitai", | |
| "lora_value": "12345", | |
| "scale": 1.0 | |
| } | |
| ] | |
| } | |
| """ | |
| if not isinstance(params, dict): | |
| return make_validation_error("Request params must be an object.") | |
| missing = [] | |
| for req_field in ["task_type", "model", "prompt"]: | |
| if req_field not in params or not params[req_field]: | |
| missing.append(req_field) | |
| if missing: | |
| return make_validation_error( | |
| f"Missing required parameter(s): {', '.join(missing)}", | |
| missing_fields=missing, | |
| ) | |
| task_type = params["task_type"] | |
| valid_tasks = [t["task_type"] for t in _TASK_DEFINITIONS] | |
| if task_type not in valid_tasks: | |
| return make_validation_error( | |
| f"Invalid task_type '{task_type}'. Must be one of {valid_tasks}.", | |
| invalid_fields={"task_type": f"Must be in {valid_tasks}"}, | |
| ) | |
| if "chain" in params and params["chain"] is not None: | |
| chain_val = params["chain"] | |
| if isinstance(chain_val, dict): | |
| return make_validation_error( | |
| "Parameter 'chain' must be a JSON array (list) of injector objects [{'injector_type': 'lora', ...}], but received a dictionary. " | |
| "Do NOT structure chain as a dict like {'lora': [...]}. " | |
| "Example correct format: [{'injector_type': 'lora', 'source': 'Civitai', 'lora_value': '12345', 'scale': 1.0}]", | |
| invalid_fields={"chain": "Expected list of objects, received dict"}, | |
| ) | |
| if not isinstance(chain_val, list): | |
| return make_validation_error( | |
| "Parameter 'chain' must be a JSON array (list) of injector objects.", | |
| invalid_fields={"chain": f"Expected list, received {type(chain_val).__name__}"}, | |
| ) | |
| for idx, item in enumerate(chain_val): | |
| if not isinstance(item, dict): | |
| return make_validation_error( | |
| f"Item at chain[{idx}] must be an object (dict) containing 'injector_type'. " | |
| f"Example: {{'injector_type': 'lora', 'source': 'Civitai', 'lora_value': '12345', 'scale': 1.0}}", | |
| invalid_fields={f"chain[{idx}]": f"Expected dict, received {type(item).__name__}"}, | |
| ) | |
| if "injector_type" not in item or not item["injector_type"]: | |
| return make_validation_error( | |
| f"Item at chain[{idx}] is missing required string field 'injector_type'. " | |
| f"Example: {{'injector_type': 'lora', 'source': 'Civitai', 'lora_value': '12345', 'scale': 1.0}}", | |
| missing_fields=[f"chain[{idx}].injector_type"], | |
| ) | |
| model_list = _load_yaml(_MODEL_LIST_PATH) | |
| checkpoints = model_list.get("Checkpoint", {}) | |
| all_models = set() | |
| for arch_name, arch_data in checkpoints.items(): | |
| if isinstance(arch_data, dict): | |
| for m in arch_data.get("models", []): | |
| all_models.add(m.get("display_name")) | |
| if params["model"] not in all_models: | |
| return make_not_found_error("model", params["model"]) | |
| task_id = f"img_task_{uuid.uuid4().hex[:10]}" | |
| created_at = int(time.time()) | |
| _TASKS_DB[task_id] = { | |
| "task_id": task_id, | |
| "status": "queued", | |
| "progress": 0, | |
| "created_at": created_at, | |
| } | |
| async_exec = params.get("async_execution", False) | |
| if async_exec: | |
| t = threading.Thread(target=_execute_imagegen_pipeline, args=(task_id, params), daemon=True) | |
| t.start() | |
| return { | |
| "status": "queued", | |
| "task_id": task_id, | |
| "poll_interval_ms": 2000, | |
| "message": "Task queued successfully. Poll get_task_status for results.", | |
| } | |
| else: | |
| _execute_imagegen_pipeline(task_id, params) | |
| return _TASKS_DB[task_id] | |