Spaces:
Running on Zero
Running on Zero
Download mcp_tools/image_requests.py from BlueSkyXN/ImageGen-Studio: direct link, hf CLI and curl.
- Browser
- Download file 9.01 kB
-
https://huggingface.co/spaces/BlueSkyXN/ImageGen-Studio/resolve/main/mcp_tools/image_requests.py
- Command line
-
hf download hf://spaces/BlueSkyXN/ImageGen-Studio/mcp_tools/image_requests.py
-
curl -L -o image_requests.py https://huggingface.co/spaces/BlueSkyXN/ImageGen-Studio/resolve/main/mcp_tools/image_requests.py
9.01 kB
| """高层 API 图片参数兼容层;顺序与能力校验复用 UI 的执行计划。""" | |
| import re | |
| from PIL import Image | |
| from core.execution_plan import ( | |
| MODE_MODEL_PK, MODE_MULTI_MODEL_GRID, MODE_MULTI_REFERENCE, MODE_SINGLE, | |
| available_run_modes, build_execution_plan, | |
| ) | |
| from core.reference_inputs import ImageInputError, REFERENCE_CHAIN_SPECS, SOURCE_IMAGE_KEYS, reference_choices | |
| from core.runtime_config import CONFIG | |
| NATIVE_CHAIN_ALIASES = { | |
| "reference_edit": "reference_latent", | |
| "mage_flow_reference_edit": "reference_image", | |
| "joyai_reference_edit": "joyai_image", | |
| "boogu_edit": "boogu_image_edit", | |
| } | |
| _IMAGE_KEY = re.compile(r"(?:图|img|image)([0-90-9]+)", re.IGNORECASE) | |
| def normalize_image_request(params: dict) -> None: | |
| """只修改 handle_run 的副本。显式混用入口时拒绝,不猜优先级。""" | |
| numbered = {} | |
| for key in list(params): | |
| match = _IMAGE_KEY.fullmatch(key) if isinstance(key, str) else None | |
| if not match: | |
| continue | |
| index = int(match.group(1)) | |
| if index in numbered: | |
| raise ImageInputError(f"图片编号 {index} 重复;图{index} / img{index} / image{index} 只能提供一个字段。") | |
| numbered[index] = params.pop(key) | |
| if numbered: | |
| if "images" in params or params.get("image") is not None: | |
| raise ImageInputError("编号字段、images 列表和 image 源图不能混用,请只保留一个入口。") | |
| if sorted(numbered) != list(range(1, len(numbered) + 1)): | |
| raise ImageInputError("图片编号必须从 1 开始且连续,不能缺少 img1 或跳号。") | |
| params["images"] = [numbered[i] for i in range(1, len(numbered) + 1)] | |
| if "images" in params: | |
| images = params["images"] | |
| if not isinstance(images, list) or not images: | |
| raise ImageInputError("images 必须是非空、有序图片列表。") | |
| if any(not isinstance(image, (str, Image.Image)) or (isinstance(image, str) and not image.strip()) for image in images): | |
| raise ImageInputError("images 不允许空图片;删除空项后重新按顺序编号。") | |
| if params.get("image") is not None: | |
| raise ImageInputError("image 与 images 不能混用。") | |
| if params["task_type"] == "reference": | |
| params.setdefault("run_mode", MODE_MULTI_REFERENCE) | |
| if params.get("image") is not None: | |
| if not isinstance(params["image"], (str, Image.Image)): | |
| raise ImageInputError("image 必须是图片 URL 或 Base64 Data URI。") | |
| params["images"] = [params.pop("image")] | |
| elif params["task_type"] == "txt2img" and params.get("image") is not None: | |
| raise ImageInputError("txt2img 不使用 image 源图;参考图编辑请用 task_type=reference 和 images。") | |
| if params.get("image") is not None and not isinstance(params["image"], (str, Image.Image)): | |
| raise ImageInputError("image 必须是图片 URL 或 Base64 Data URI。") | |
| for field in ("run_mode", "reference_role"): | |
| if field in params and not isinstance(params[field], str): | |
| raise ImageInputError(f"{field} 必须是字符串。") | |
| if "extra_models" in params and ( | |
| not isinstance(params["extra_models"], list) | |
| or not all(isinstance(model, str) for model in params["extra_models"]) | |
| ): | |
| raise ImageInputError("extra_models 必须是模型名称列表。") | |
| if "use_model_defaults" in params and not isinstance(params["use_model_defaults"], bool): | |
| raise ImageInputError("use_model_defaults 必须是 boolean。") | |
| def validate_image_plan(params: dict) -> None: | |
| """不下载图片、不分配 GPU,提交队列前校验完整调用语义。""" | |
| values = { | |
| "task_type": params["task_type"], "model_display_name": params["model"], | |
| "positive_prompt": params["prompt"], "negative_prompt": params.get("negative_prompt") or "", | |
| "seed": params.get("seed", -1), "batch_size": params.get("batch_size", 1), | |
| } | |
| task = params["task_type"] | |
| if task in SOURCE_IMAGE_KEYS and params.get("image") is not None: | |
| values[SOURCE_IMAGE_KEYS[task]] = params["image"] | |
| for item in params.get("chain", []): | |
| chain = NATIVE_CHAIN_ALIASES.get(item["injector_type"], item["injector_type"]) | |
| if chain in REFERENCE_CHAIN_SPECS: | |
| if params.get("images"): | |
| raise ImageInputError("images 与原生参考 chain 不能混用;自动路由用 images,旧调用只用 chain。") | |
| if item.get("image") is None: | |
| raise ImageInputError(f"{chain} 缺少 image。") | |
| if not isinstance(item["image"], (str, Image.Image)) or (isinstance(item["image"], str) and not item["image"].strip()): | |
| raise ImageInputError(f"{chain}.image 必须是图片 URL 或 Base64 Data URI。") | |
| values.setdefault(REFERENCE_CHAIN_SPECS[chain][0], []).append(item["image"]) | |
| mode = params.get("run_mode", MODE_SINGLE) | |
| if mode not in {MODE_MODEL_PK, MODE_MULTI_MODEL_GRID} and params.get("extra_models"): | |
| raise ImageInputError("extra_models 只用于 model_pk / multi_model_grid。") | |
| if mode != MODE_MULTI_REFERENCE and params.get("reference_role", "auto") != "auto": | |
| raise ImageInputError("reference_role 只用于 reference / multi_reference。") | |
| if mode in {MODE_MODEL_PK, MODE_MULTI_MODEL_GRID} and ( | |
| params.get("chain") or params.get("vae_id") or params.get("pid") or params.get("pid_settings") | |
| ): | |
| raise ImageInputError("模型对比仅使用基础模型,不支持 chain / VAE / PiD 覆盖。") | |
| build_execution_plan( | |
| values, mode=mode, images=params.get("images"), | |
| extra_models=params.get("extra_models"), reference_role=params.get("reference_role", "auto"), | |
| use_model_defaults=params.get("use_model_defaults", True), | |
| ) | |
| def image_workflow_contract(model: str) -> dict: | |
| """在现有 discovery 上增量公开高层图片契约,不要求 AI 猜 injector。""" | |
| roles = reference_choices(model) | |
| return { | |
| "reference_supported": bool(roles), | |
| "reference_roles": { | |
| role: {"chain": spec["chain"], "max_images": spec["max_images"]} | |
| for role, spec in roles.items() | |
| }, | |
| "run_modes_by_task": { | |
| task: [mode for _, mode in available_run_modes(task, model)] | |
| for task in ["txt2img", *SOURCE_IMAGE_KEYS, "reference"] | |
| }, | |
| "parameters_schema": { | |
| "type": "object", | |
| "properties": { | |
| "images": {"type": "array", "minItems": 1, "maxItems": CONFIG.max_multi_images, | |
| "items": {"type": "string", "description": "Public HTTP(S) image URL or Base64 Data URI; not a local path."}}, | |
| "run_mode": {"type": "string", "enum": ["single", "model_pk", "multi_independent", "multi_model_grid", "multi_reference"], | |
| "description": "Default single; task_type=reference defaults to multi_reference. Use run_modes_by_task."}, | |
| "reference_role": {"type": "string", "enum": list(roles) or ["auto"], "default": "auto"}, | |
| "extra_models": {"type": "array", "items": {"type": "string"}, "description": "Only for model_pk / multi_model_grid; current model is included automatically."}, | |
| "use_model_defaults": {"type": "boolean", "default": True}, | |
| }, | |
| "patternProperties": {r"^(图|img|image)[1-9][0-9]*$": {"type": "string", "description": "Alternative to images; indices must start at 1, be contiguous and unique."}}, | |
| }, | |
| "rules": [ | |
| "Use task_type=reference with images, prompt, width, height for instruction editing / fusion; no native chain needed. Model must support reference.", | |
| "images order is 1-based: 图1 = img1 = image1. Numbered prompt references become image 1; quoted/backticked text is literal. No auto translation or prompt expansion.", | |
| "Do not mix images, numbered fields, image, or native reference chain. Legacy txt2img + chain and single-image image calls remain supported.", | |
| "multi_independent / multi_model_grid use one shared prompt per image. Say 当前图片 / the current image; cross-image numbered references are rejected.", | |
| "Leave steps/cfg/sampler/scheduler omitted to use model defaults. Negative prompts depend on model/CFG and may be unused at CFG=1.", | |
| "Prefer async_execution=true; poll the same task_id with get_task_status until completed/failed; do not resubmit on a polling timeout.", | |
| "Read result.image_references, effective_prompt, captions and jobs. Existing result.images remains a flat URL list; partial_success signals mixed results.", | |
| ], | |
| "limits": {"max_images": CONFIG.max_multi_images, "max_pk_models": CONFIG.max_pk_models, | |
| "max_jobs": CONFIG.max_plan_jobs, "max_outputs": CONFIG.max_plan_outputs}, | |
| } | |