diff --git a/CLI/__init__.py b/CLI/__init__.py new file mode 100644 index 000000000..7631c1404 --- /dev/null +++ b/CLI/__init__.py @@ -0,0 +1 @@ +"""Project command-line integrations.""" diff --git a/CLI/macos/__init__.py b/CLI/macos/__init__.py new file mode 100644 index 000000000..f0c42bf17 --- /dev/null +++ b/CLI/macos/__init__.py @@ -0,0 +1 @@ +"""macOS-specific integrations.""" diff --git a/CLI/macos/drawthings/README.md b/CLI/macos/drawthings/README.md new file mode 100644 index 000000000..cfcd48ecc --- /dev/null +++ b/CLI/macos/drawthings/README.md @@ -0,0 +1,65 @@ +# Draw Things gRPCServerCLI + +这是 Infinite-Canvas 的 Draw Things gRPCServerCLI 独立依赖说明,仅适用于 macOS Apple Silicon(M 芯片)。Draw Things gRPCServerCLI 需要由用户单独启动,Infinite-Canvas 只负责连接已运行的服务。 + +Draw Things gRPCServerCLI 借助了 +https://github.com/drawthingsai/draw-things-comfyui.git 这个comfyui插件代码的能力。 + +当前版本已将 Draw Things gRPC 所需的协议文件和客户端代码放在本目录中,不再运行时调用或依赖 `draw-things-comfyui` 插件,也不需要启动 ComfyUI。只需在画布所使用的 Python 环境中安装下面列出的 Python 依赖。 +## 安装gPRCServerCLI +以下是drawthings作者编译好的gPRCServerCLI执行文件下载地址 +https://github.com/drawthingsai/draw-things-community/releases +参考drawthings作者GitHub仓库中的安装方法安装: +``` +Self-host gRPCServerCLI from Packaged Binaries + +We provide pre-built self-hosted gRPCServerCLI binaries through this repository. Latest version should be available at Releases. + +These pre-built binaries provide a quick way to host Draw Things gRPC Server on your Mac or Linux systems without download the Draw Things app. Draw Things app then can connect to these self-hosted servers through Server-Offload feature within your network. + +macOS + +On macOS, simply download the gRPCServerCLI-macOS on your macOS systems. You can put it under /usr/local/bin or anywhere you feel comfortable, and launch it with: + +gRPCServerCLI-macOS /the-path-to-host-the-models +If you have Draw Things app installed, you can simply refer the model path by doing: + +gRPCServerCLI-macOS ~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models +``` + +## 安装依赖 + +请在 Infinite-Canvas 项目根目录执行。建议使用 conda 或 Miniforge 创建的独立环境,不要把这些依赖追加到项目根目录的 `requirements.txt`。 + +```bash +python -m pip install -r CLI/macos/drawthings/requirements-Dt-gRPC.txt +``` + +也可以明确使用当前环境的 Python: + +```bash +/Users/hanqingren/miniforge3/bin/python -m pip install -r CLI/macos/drawthings/requirements-Dt-gRPC.txt +``` + +## 启动服务 + +### 方法1(推荐) +直接启动gPRCServerCLI(drawthings的模型路径没改动的话直接执行以下命令)不用开启drawthings本身 +``` +gRPCServerCLI ~/Library/Containers/com.liuliu.draw-things/Data/Documents/Models --model-browser +``` +默认连接地址为 `127.0.0.1:7859`,TLS 默认开启 +注:(如果启动服务时使用了自定义主机或端口,请在 Infinite-Canvas 的 Draw Things gRPC 设置中填写对应的 `主机:端口`。) +### 方法2 +先在 Draw Things 中启动 gRPCServerCLI。 + +## 大雄画布中的API设置 + +在 Infinite-Canvas 的 API 设置中添加 Draw Things gRPCServerCLI provider 后,再选择实际连接到 gRPCServerCLI 的本地路径和端口号。 +模型列表由后端实时读取,不在画布节点中固定保存模型名称。 + +该连接目前用于图片生成、单图图生图和 Hint 多图编辑,不提供聊天模型能力。Hint 最多支持四张图片,每张图片可在画布节点中独立设置控制类型。 + + + + diff --git a/CLI/macos/drawthings/__init__.py b/CLI/macos/drawthings/__init__.py new file mode 100644 index 000000000..963b861f5 --- /dev/null +++ b/CLI/macos/drawthings/__init__.py @@ -0,0 +1 @@ +"""Draw Things gRPCServerCLI integration for Infinite-Canvas.""" diff --git a/CLI/macos/drawthings/credentials.py b/CLI/macos/drawthings/credentials.py new file mode 100644 index 000000000..8cc3b9dcc --- /dev/null +++ b/CLI/macos/drawthings/credentials.py @@ -0,0 +1,35 @@ +import grpc + +_cert = b'''-----BEGIN CERTIFICATE----- +MIIFHTCCAwWgAwIBAgIUWxJuoygy7Hsb9bcSfggNGLGZJW4wDQYJKoZIhvcNAQEL +BQAwHjEcMBoGA1UEAwwTRHJhdyBUaGluZ3MgUm9vdCBDQTAeFw0yNDEwMTUxNzI3 +NTJaFw0zNDEwMTMxNzI3NTJaMB4xHDAaBgNVBAMME0RyYXcgVGhpbmdzIFJvb3Qg +Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDe/RKAuabH2pEadZj6 +JRTOaEIMYXsAI7ZIG+LSAEkyK/QZAMdLq+wBq6uJDIEvTXMyyhNgkI3oUnS2PJqi +y9lzGAh1s2y6MDG17BFboyriW0y6BKd42amX/g9A40ZC1cBs2NI9e0zjy/vhHLw1 +EHK1XDLsIYAZvqQLJR3zRslHTHN6BysNWNmO/s1myLHQzbjyg4+/JHqma5Xatz0W +I5Wi6zxu/G1IdWeO6tlWWBSArDbhru+rb2U9p9/jKGW7fOom7sH9oBpj7q+xcrr5 +h2Aoam4xRqxc3SG7TRc1inEki86/FoWCARSqGo2t7q/brkwwGbeZsuwKhIuhWGzW +CJKp0NvD11HyCqsJsLMTx9PXzEsCDFsios+zI6zu1aIVomO5h8d59oxMGEvNozIc +gSHJI3pCiHmJt0o9xoRi0UGiB6PP3k4ZzxTV30wt0oMOzS8dgMdl1u0zpAc2aEGG +4cdWQaDP2UgZlNQyzGbGUC2Q2ln1ghTlEBAs23/yDZyEbtWj+Qo1Isk80CXISs8/ +H4cdM9Xw/Rt5fGxSaNzHJZJ9gK8YFI0z7IDiQp9nWkMqyDhGjhT4ZR847Nz52gcK +zuqmSK6B7ksumilchQ8hq79VAAvZqQoyVIvLvkbb6pXZbH0qTK5yk0YQVJ49JU1L +XnB4Iu8IuDxTLmtW2WoCjUZaqQIDAQABo1MwUTAdBgNVHQ4EFgQUhmFk2qHWAU6/ +3u6FyCnk2vaV0fswHwYDVR0jBBgwFoAUhmFk2qHWAU6/3u6FyCnk2vaV0fswDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAdvryE1xbhpyDjtP+I95Z +tgmlkmIWTPoHL5WO20SWtWjryHTs0XGXkohqSFKBqYTOVTyRCTtUTF4nWoNfBhlz +aOExf64UgvYHO4NxcPNjUH2Yx/AKFWBeHx50jfjz/zTSqhAHv8rlYDt6rlLs1aFm +rNj3DObqmTfDoI8qkdLK8bekjhul6PusmezhW+qa/DMvDRy3moUugpXwzvyG5GRW +C3+nNbBdCdblUyiEgFu5htH6hSSu2IX5t/ryoKNjAAfUxMKcNFdYCnzWiHKOlrmp +wYL4YhVQZZYmis8ZIFOQ+BKVQHJcqE5bdrbNbCpurMNODEuDDB/VkbGHEVFVgB0n +x+ZtaGnfTeJJ6h7IIl+Gnpx0u9k+2pu78cEQ+6ZYKaGUoOKccxgipsSXWL75qHl9 +7/scB3imqRq0Q7/jKP6mvcB3/5irQwVmczsFwELLP0LJdsCZMcQQQsSCGuskzcAJ +iiiGzRVTfYFUu2hJ5JIgewg+NEzMCwzR5yyWacBcrrDxQTymTNW9NWahHxvdZJHd +zRd4Y3HNLPikGg37mCYIPWtUxJCU7/lZleNSqlMBhDdbIZcAqaHOQlYJQSZaTMwK +kWF1y/C6TdCKWyXhAEV8zp/0q4b6vC1ynn/GfopROPXceLbGA+BLG9JEQ1AiGae3 +ejQ40oILyZjEclMPGLYjqoQ= +-----END CERTIFICATE----- +''' + +credentials = grpc.ssl_channel_credentials(_cert) diff --git a/CLI/macos/drawthings/draw_things_grpc.py b/CLI/macos/drawthings/draw_things_grpc.py new file mode 100644 index 000000000..58366a708 --- /dev/null +++ b/CLI/macos/drawthings/draw_things_grpc.py @@ -0,0 +1,695 @@ +"""Minimal Draw Things gRPC client used by the Infinite-Canvas image path.""" + +from __future__ import annotations + +import base64 +import io +import os +import secrets +import struct +import time +from pathlib import Path +from urllib.parse import urlsplit + +PROJECT_ROOT = Path(__file__).resolve().parents[3] +DEFAULT_HOST = "127.0.0.1" +DEFAULT_PORT = 7859 +DEFAULT_SIZE = (1024, 1024) + + +def draw_things_model_supports_editing(model: str) -> bool: + normalized = str(model or "").strip().lower().replace("-", "_") + if "klein" in normalized: + return True + return "qwen" in normalized and "edit" in normalized + + +def _settings(endpoint: str = "") -> tuple[str, int, bool, str]: + host = str(os.getenv("DRAW_THINGS_GRPC_HOST", DEFAULT_HOST)).strip() or DEFAULT_HOST + try: + port = int(os.getenv("DRAW_THINGS_GRPC_PORT", str(DEFAULT_PORT))) + except ValueError: + port = DEFAULT_PORT + custom_endpoint = str(endpoint or "").strip() + if custom_endpoint: + # A provider-specific host:port takes precedence over environment + # defaults, while grpc:// and https:// prefixes remain accepted. + parsed = urlsplit( + custom_endpoint + if "://" in custom_endpoint + else f"//{custom_endpoint}" + ) + if not parsed.hostname: + raise ValueError( + "Draw Things gRPCServerCLI 地址无效,请填写主机:端口,例如 127.0.0.1:7859。" + ) + host = parsed.hostname + if parsed.port is not None: + port = parsed.port + # gRPCServerCLI enables TLS by default. Plaintext remains available only + # when a user explicitly sets DRAW_THINGS_GRPC_TLS=false. + use_tls = str(os.getenv("DRAW_THINGS_GRPC_TLS", "true")).strip().lower() not in { + "0", + "false", + "no", + "off", + } + shared_secret = str(os.getenv("DRAW_THINGS_GRPC_SHARED_SECRET", "")).strip() + return host, port, use_tls, shared_secret + + +def _parse_size(size: str) -> tuple[int, int]: + import re + + match = re.fullmatch(r"\s*(\d+)\s*[xX*]\s*(\d+)\s*", str(size or "")) + if not match: + # The upstream Infinite-Canvas API defaults to 1024x1024. Normal + # canvas requests carry an explicit size from the node's settings. + return DEFAULT_SIZE + width = max(64, min(2048, int(match.group(1)) // 64 * 64)) + height = max(64, min(2048, int(match.group(2)) // 64 * 64)) + return width, height + + +def _build_configuration( + model: str, + width: int, + height: int, + seed: int | None = None, + strength: float | None = None, + batch_size: int = 1, + loras: list[dict] | None = None, +) -> bytes: + import flatbuffers + from .generated import config_generated + + model_name = str(model or "").lower() + is_klein_9b = "flux_2_klein_9b" in model_name + is_klein_4b = "flux_2_klein_4b" in model_name + is_klein = "klein" in model_name + is_z_image = "z_image" in model_name or "zimage" in model_name + is_qwen_edit = "qwen" in model_name and "edit" in model_name + config = config_generated.GenerationConfigurationT() + config.model = model + config.startWidth = width // 64 + config.startHeight = height // 64 + # Draw Things' Klein 9B preset uses four DDIM-trailing steps. Keeping it + # at eight steps doubles the diffusion work for the same request. + default_steps = "8" if is_z_image else "4" + config.steps = int(os.getenv("DRAW_THINGS_GRPC_STEPS", default_steps)) + default_guidance = "1.0" if (is_klein or is_z_image or is_qwen_edit) else "3.5" + config.guidanceScale = float( + os.getenv("DRAW_THINGS_GRPC_GUIDANCE", default_guidance) + ) + config.strength = 1.0 + if seed is not None: + # The canvas dice control supplies an explicit seed for this request. + config.seed = int(seed) % 4294967295 + else: + configured_seed = os.getenv("DRAW_THINGS_GRPC_SEED") + if configured_seed is None or not configured_seed.strip(): + # Generate a fresh seed per request so batch generations do not + # repeat the same image when no canvas seed was supplied. + config.seed = secrets.randbelow(4294967295) + else: + # An explicit environment value intentionally enables deterministic + # generation for debugging and reproduction. + config.seed = int(configured_seed) % 4294967295 + config.batchCount = 1 + config.batchSize = max(1, min(8, int(batch_size or 1))) + config.loras = [] + for item in loras or []: + if not isinstance(item, dict): + continue + file_name = str(item.get("file") or "").strip() + if not file_name: + continue + try: + weight = float(item.get("weight", 1.0)) + except (TypeError, ValueError): + weight = 1.0 + lora = config_generated.LoRAT() + lora.file = file_name + lora.weight = max(-5.0, min(5.0, weight)) + lora.mode = 0 + config.loras.append(lora) + if strength is not None: + # Draw Things uses strength for image-to-image denoising. Keep it in + # the same [0, 1] range exposed by the ComfyUI plugin. + config.strength = max(0.0, min(1.0, float(strength))) + if is_klein: + # FLUX.2 Klein uses model-specific Draw Things presets. + config.sampler = 16 # SamplerType.DDIMTrailing + config.seedMode = 2 # SeedMode.ScaleAlike + config.shift = 3.0 + config.resolutionDependentShift = False + if is_klein_9b: + config.maskBlur = 2.5 + elif is_klein_4b: + config.maskBlur = 2.5 + config.speedUpWithGuidanceEmbed = True + config.guidanceEmbed = 3.5 + elif is_z_image: + # Z Image Turbo uses the official eight-step Draw Things setup. + config.sampler = 17 # SamplerType.UniPCTrailing + config.seedMode = 2 # SeedMode.ScaleAlike + config.shift = 3.0 + config.resolutionDependentShift = False + config.maskBlur = 2.5 + config.speedUpWithGuidanceEmbed = False + config.guidanceEmbed = 0.0 + elif is_qwen_edit: + config.sampler = 17 # SamplerType.UniPCTrailing + config.seedMode = 2 # SeedMode.ScaleAlike + config.shift = 3.0 + config.resolutionDependentShift = False + config.maskBlur = 2.5 + + builder = flatbuffers.Builder(0) + builder.Finish(config.Pack(builder)) + return bytes(builder.Output()) + + +def _reference_image_bytes(reference: object) -> bytes: + """Read one Infinite-Canvas reference image from a local source.""" + value = reference + if isinstance(reference, dict): + value = ( + reference.get("url") + or reference.get("path") + or reference.get("file") + or reference.get("data") + ) + if isinstance(value, bytes): + return value + source = str(value or "").strip() + if not source: + raise ValueError("参考图缺少本地 url、path、file 或 data 字段。") + if source.startswith("data:"): + try: + _, encoded = source.split(",", 1) + return base64.b64decode(encoded) + except (ValueError, base64.binascii.Error) as exc: + raise ValueError("参考图 data URL 无法解码。") from exc + if source.startswith("http://") or source.startswith("https://"): + raise ValueError("阶段 3 只读取本地参考图,不下载远程 URL。") + if source.startswith("file://"): + source = urlsplit(source).path + path = Path(source).expanduser() + if not path.is_absolute(): + candidates = ( + Path.cwd() / path, + Path(__file__).resolve().parent / path, + PROJECT_ROOT / path, + ) + path = next((candidate for candidate in candidates if candidate.is_file()), candidates[0]) + try: + last_data = b"" + for attempt in range(3): + before = path.stat() + last_data = path.read_bytes() + after = path.stat() + if before.st_size == after.st_size == len(last_data): + return last_data + if attempt < 2: + time.sleep(0.05) + return last_data + except OSError as exc: + raise ValueError(f"参考图无法读取:{path}") from exc + + +def _open_reference_image(reference: object, label: str): + from PIL import Image + + raw = _reference_image_bytes(reference) + source_name = "" + if isinstance(reference, dict): + source_name = str( + reference.get("path") + or reference.get("url") + or reference.get("name") + or "" + ).strip() + try: + with Image.open(io.BytesIO(raw)) as source: + source.load() + return source.copy() + except OSError as exc: + raise ValueError( + f"{label}文件损坏或不完整:{source_name or '未知文件'}," + f"读取到 {len(raw)} 字节;{exc}" + ) from exc + + +def _resize_crop_reference(image, width: int, height: int): + """Match Draw Things' resize-then-center-crop behavior.""" + from PIL import Image + + image = image.convert("RGB") + if image.size == (width, height): + return image + source_width, source_height = image.size + scale = max(width / source_width, height / source_height) + resized = image.resize( + (max(width, int(source_width * scale)), max(height, int(source_height * scale))), + Image.Resampling.BILINEAR, + ) + left = max(0, (resized.width - width) // 2) + top = max(0, (resized.height - height) // 2) + return resized.crop((left, top, left + width, top + height)) + + +def _resize_crop_mask(image, width: int, height: int): + """Resize a mask without losing alpha or introducing soft edges.""" + from PIL import Image + + if "A" in image.getbands(): + background = Image.new("RGBA", image.size, (0, 0, 0, 255)) + image = Image.alpha_composite(background, image.convert("RGBA")) + image = image.convert("L") + if image.size == (width, height): + return image + source_width, source_height = image.size + scale = max(width / source_width, height / source_height) + resized = image.resize( + (max(width, int(source_width * scale)), max(height, int(source_height * scale))), + Image.Resampling.NEAREST, + ) + left = max(0, (resized.width - width) // 2) + top = max(0, (resized.height - height) // 2) + return resized.crop((left, top, left + width, top + height)) + + +def _encode_image_for_request(reference: object, width: int, height: int) -> bytes: + """Encode a local image as Draw Things' RGB NHWC FP16 tensor.""" + import numpy as np + from PIL import Image + + source = _open_reference_image(reference, "输入图") + image = _resize_crop_reference(source, width, height) + pixels = np.asarray(image, dtype=np.float32) / 255.0 * 2.0 - 1.0 + + # This is Draw Things' 68-byte CCV header and HWC FP16 image payload. + # It is an image input, not a HintProto. + encoded = bytearray(68 + width * height * 3 * 2) + struct.pack_into( + "<9I", + encoded, + 0, + 0, + 0x1, # CCV_TENSOR_CPU_MEMORY + 0x2, # CCV_TENSOR_FORMAT_NHWC + 0x20000, # CCV_16F + 0, + 1, + height, + width, + 3, + ) + encoded[68:] = pixels.astype(np.float16, copy=False).tobytes(order="C") + return bytes(encoded) + + +def _encode_mask_for_request(reference: object, width: int, height: int) -> bytes: + """Encode a black/white mask as Draw Things' NCHW 8-bit tensor.""" + from PIL import Image + + source = _open_reference_image(reference, "遮罩图") + image = _resize_crop_mask(source, width, height) + + # Draw Things uses 0 for retained pixels and 2 for pixels redrawn with + # the configured img2img strength. This matches the ComfyUI node's mask + # conversion and keeps the mask separate from request.image. + encoded = bytearray(68 + width * height) + struct.pack_into( + "<9I", + encoded, + 0, + 0, + 0x1, # CCV_TENSOR_CPU_MEMORY + 0x01, # CCV_TENSOR_FORMAT_NCHW + 0x1000, # CCV_8U + 0, + height, + width, + 0, + 0, + ) + for y in range(height): + for x in range(width): + encoded[68 + y * width + x] = 2 if image.getpixel((x, y)) >= 50 else 0 + return bytes(encoded) + + +def _parse_strength(value: object, default: float = 0.75) -> float: + try: + strength = float(value) + except (TypeError, ValueError): + strength = default + return max(0.0, min(1.0, strength)) + + +def _parse_hint_weight(value: object, default: float = 1.0) -> float: + """Normalize one Hint tensor weight to the range accepted by Draw Things.""" + try: + weight = float(value) + except (TypeError, ValueError): + weight = default + if weight < 0: + raise ValueError("Draw Things Hint 权重不能小于 0。") + return weight + + +def _build_hint_protos( + references: list[object], + width: int, + height: int, + hint_type: str = "shuffle", + weights: list[object] | None = None, +): + """Build one HintProto from one or more local images. + + Draw Things expects all images belonging to one control type inside the + same HintProto. Keep ordinary request.image input separate from Hint + inputs while constructing the request from the local protocol types. + """ + from .generated import imageService_pb2 + + normalized_type = str(hint_type or "").strip().lower() + if not normalized_type: + raise ValueError("Draw Things Hint 类型不能为空。") + if not references: + raise ValueError("Draw Things Hint 至少需要一张参考图。") + if weights is not None and len(weights) not in {0, len(references)}: + raise ValueError("Hint 权重数量必须与参考图数量一致。") + + tensor_weights = [] + for index, reference in enumerate(references): + reference_weight = None + if isinstance(reference, dict): + reference_weight = reference.get("weight") + if weights: + reference_weight = weights[index] + tensor_weights.append( + ( + _encode_image_for_request(reference, width, height), + _parse_hint_weight(reference_weight), + ) + ) + + hint = imageService_pb2.HintProto(hintType=normalized_type) + hint.tensors.extend( + imageService_pb2.TensorAndWeight(tensor=tensor, weight=weight) + for tensor, weight in tensor_weights + ) + return [hint] + + +def _decode_response_image(response_image: bytes) -> bytes: + import fpzip + import numpy as np + from PIL import Image + + header = np.frombuffer(response_image, dtype=np.uint32, count=17) + height, width, channels = (int(value) for value in header[6:9]) + sample_count = width * height * channels + payload = response_image[68:] + if int(header[0]) == 1012247: + values = fpzip.decompress(payload, order="C").astype(np.float16).reshape(-1) + values = values[:sample_count] + else: + values = np.frombuffer(payload, dtype=np.float16, count=sample_count) + if values.size != sample_count: + raise ValueError( + f"Draw Things returned an invalid image payload: " + f"expected {sample_count} values, got {values.size}" + ) + pixels = np.clip((values + 1) * 127.5, 0, 255).astype(np.uint8) + mode = "RGBA" if channels == 4 else "RGB" + image = Image.frombytes(mode, (width, height), pixels.tobytes()) + from io import BytesIO + + output = BytesIO() + image.save(output, format="PNG") + return output.getvalue() + + +def _channel(target: str, use_tls: bool): + import grpc + from .credentials import credentials + + options = [ + ("grpc.max_send_message_length", -1), + ("grpc.max_receive_message_length", -1), + ] + if use_tls: + return grpc.aio.secure_channel(target, credentials, options=options) + return grpc.aio.insecure_channel(target, options=options) + + +def _model_files(echo_reply) -> list[str]: + models = _metadata_items(echo_reply, "models") + + # Older/newer server builds may expose the model browser as EchoReply.files + # instead of MetadataOverride.models. Keep both forms compatible. + if not models: + models = list(getattr(echo_reply, "files", ()) or ()) + + files = [] + for model in models if isinstance(models, list) else []: + if isinstance(model, str): + filename = model.strip() + elif isinstance(model, dict): + filename = str(model.get("file") or "").strip() + else: + filename = "" + if filename and filename not in files: + files.append(filename) + return files + + +def _metadata_items(echo_reply, field: str) -> list[object]: + """Decode a Draw Things MetadataOverride JSON field.""" + import json + + try: + raw_value = bytes(getattr(echo_reply.override, field, b"") or b"") + if not raw_value: + return [] + try: + decoded = json.loads(raw_value.decode("utf-8")) + except (UnicodeDecodeError, json.JSONDecodeError): + decoded = json.loads( + base64.b64decode(raw_value.strip(), validate=True).decode("utf-8") + ) + except (AttributeError, UnicodeDecodeError, json.JSONDecodeError, TypeError, ValueError): + decoded = [] + return decoded if isinstance(decoded, list) else [] + + +def _metadata_files(items: list[object]) -> list[dict]: + """Keep the metadata shape used by the Draw Things model browser.""" + normalized = [] + seen = set() + for item in items: + if isinstance(item, str): + file_name = item.strip() + value = {"file": file_name, "name": file_name} + elif isinstance(item, dict): + file_name = str(item.get("file") or "").strip() + value = dict(item) + value["file"] = file_name + value.setdefault("name", file_name) + else: + continue + if file_name and file_name not in seen: + seen.add(file_name) + normalized.append(value) + return normalized + + +async def list_draw_things_models(endpoint: str = "") -> dict: + """Read the live model list exposed by gRPCServerCLI's model browser.""" + import grpc + from .generated import imageService_pb2, imageService_pb2_grpc + + host, port, use_tls, shared_secret = _settings(endpoint) + target = f"{host}:{port}" + try: + async with _channel(target, use_tls) as channel: + stub = imageService_pb2_grpc.ImageGenerationServiceStub(channel) + echo_request = imageService_pb2.EchoRequest(name="Infinite-Canvas") + if shared_secret: + echo_request.sharedSecret = shared_secret + reply = await stub.Echo(echo_request, timeout=10) + model_metadata = _metadata_files(_metadata_items(reply, "models")) + loras = _metadata_files(_metadata_items(reply, "loras")) + return { + "connected": True, + "models": _model_files(reply), + "model_metadata": model_metadata, + "loras": loras, + "host": host, + "port": port, + } + except grpc.aio.AioRpcError as exc: + return { + "connected": False, + "models": [], + "model_metadata": [], + "loras": [], + "host": host, + "port": port, + "error": ( + "无法连接 Draw Things gRPCServerCLI。请确认服务已由用户手动启动," + f"并检查地址 {host}:{port}、TLS 和 shared secret 配置。" + f" gRPC={exc.code().name}: {exc.details()}" + ), + } + + +async def generate_draw_things_image( + prompt: str, + size: str, + model: str = "", + reference_images: list[dict] | None = None, + endpoint: str = "", + seed: int | None = None, + strength: float | None = None, + hint_images: list[dict] | None = None, + mask_images: list[dict] | None = None, + hint_type: str = "shuffle", + hint_weights: list[object] | None = None, + batch_size: int = 1, + loras: list[dict] | None = None, +) -> tuple[dict, dict]: + """Generate one image and return the project's standard image item shape.""" + import grpc + from .generated import imageService_pb2, imageService_pb2_grpc + + selected_model = str(model or "").strip() + if not selected_model: + raise RuntimeError( + "Draw Things gRPCServerCLI 未选择模型。请连接服务后从实时模型列表中选择。" + ) + editing_model = draw_things_model_supports_editing(selected_model) + references = [item for item in (reference_images or []) if item] + if len(references) > 1 and not editing_model: + raise RuntimeError( + "当前 Draw Things 模型不支持多图图像编辑,请只保留一张输入图,或切换到支持多图编辑的 Klein/Qwen Edit 模型。" + ) + masks = [item for item in (mask_images or []) if item] + if len(masks) > 1: + raise RuntimeError("当前 Draw Things 请求只支持一张遮罩图。") + if masks and not references: + raise RuntimeError("Draw Things 遮罩需要与一张输入图一起使用。") + hints = [item for item in (hint_images or []) if item] + if references and hints and not editing_model: + raise RuntimeError( + "当前 Draw Things 非编辑模型不能同时使用普通图生图 image 和 Hint 输入。" + ) + + host, port, use_tls, shared_secret = _settings(endpoint) + target = f"{host}:{port}" + width, height = _parse_size(size) + input_image = None + image_strength = None + if references: + # Ordinary image-to-image is carried by request.image. It must remain + # separate from HintProto, which is reserved for later control inputs. + input_image = _encode_image_for_request(references[0], width, height) + if strength is not None: + image_strength = _parse_strength(strength) + else: + configured_strength = os.getenv("DRAW_THINGS_GRPC_STRENGTH") + if configured_strength is None or not configured_strength.strip(): + # Infinite-Canvas always supplies a prompt for image edits; + # use Draw Things' full-strength edit configuration. + configured_strength = "1.0" + image_strength = _parse_strength(configured_strength) + mask_image = _encode_mask_for_request(masks[0], width, height) if masks else None + request_hints = _build_hint_protos( + hints, + width, + height, + hint_type=hint_type, + weights=hint_weights, + ) if hints else [] + + try: + async with _channel(target, use_tls) as channel: + stub = imageService_pb2_grpc.ImageGenerationServiceStub(channel) + echo_request = imageService_pb2.EchoRequest(name="Infinite-Canvas") + if shared_secret: + echo_request.sharedSecret = shared_secret + echo_reply = await stub.Echo(echo_request, timeout=10) + available_models = _model_files(echo_reply) + if available_models and selected_model not in available_models: + raise RuntimeError( + f"Draw Things 模型不可用:{selected_model}。" + "请从当前 gRPCServerCLI 模型列表中重新选择。" + ) + + request = imageService_pb2.ImageGenerationRequest( + image=input_image or b"", + scaleFactor=1, + mask=mask_image or b"", + hints=request_hints, + prompt=str(prompt or ""), + negativePrompt="", + configuration=_build_configuration( + selected_model, + width, + height, + seed, + strength=image_strength, + batch_size=batch_size, + loras=loras, + ), + user="Infinite-Canvas", + device=imageService_pb2.LAPTOP, + ) + if shared_secret: + request.sharedSecret = shared_secret + + generated = [] + async for response in stub.GenerateImage(request, timeout=1800): + generated.extend(response.generatedImages) + if not generated: + raise RuntimeError("Draw Things gRPCServerCLI 未返回图片。") + + generated_items = [] + for generated_image in generated: + png = _decode_response_image(generated_image) + generated_items.append({ + "b64_json": base64.b64encode(png).decode("ascii"), + "mime_type": "image/png", + }) + image_item = { + "type": "b64", + "value": generated_items[0]["b64_json"], + "mime_type": "image/png", + } + return image_item, { + "provider": "drawthings", + "model": selected_model, + "width": width, + "height": height, + "image_to_image": bool(input_image), + "masked": bool(mask_image), + "strength": image_strength, + "hint_type": str(hint_type or "").strip().lower() if hints else "", + "hint_count": len(hints), + "hint_weights": [ + float(tensor.weight) + for hint in request_hints + for tensor in hint.tensors + ], + "images": generated_items, + } + except grpc.aio.AioRpcError as exc: + raise RuntimeError( + "无法连接 Draw Things gRPCServerCLI。请确认服务已由用户手动启动," + f"并检查地址 {host}:{port}、TLS 和 shared secret 配置。" + f" gRPC={exc.code().name}: {exc.details()}" + ) from exc diff --git a/CLI/macos/drawthings/generated/__init__.py b/CLI/macos/drawthings/generated/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/CLI/macos/drawthings/generated/config_generated.py b/CLI/macos/drawthings/generated/config_generated.py new file mode 100644 index 000000000..cb21cb084 --- /dev/null +++ b/CLI/macos/drawthings/generated/config_generated.py @@ -0,0 +1,1782 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: + +import flatbuffers +from flatbuffers.compat import import_numpy +from typing import Any +from typing import Optional +np = import_numpy() + +class SamplerType(object): + DPMPP2MKarras = 0 + EulerA = 1 + DDIM = 2 + PLMS = 3 + DPMPPSDEKarras = 4 + UniPC = 5 + LCM = 6 + EulerASubstep = 7 + DPMPPSDESubstep = 8 + TCD = 9 + EulerATrailing = 10 + DPMPPSDETrailing = 11 + DPMPP2MAYS = 12 + EulerAAYS = 13 + DPMPPSDEAYS = 14 + DPMPP2MTrailing = 15 + DDIMTrailing = 16 + UniPCTrailing = 17 + UniPCAYS = 18 + TCDTrailing = 19 + + +class SeedMode(object): + Legacy = 0 + TorchCpuCompatible = 1 + ScaleAlike = 2 + NvidiaGpuCompatible = 3 + + +class ControlMode(object): + Balanced = 0 + Prompt = 1 + Control = 2 + + +class ControlInputType(object): + Unspecified = 0 + Custom = 1 + Depth = 2 + Canny = 3 + Scribble = 4 + Pose = 5 + Normalbae = 6 + Color = 7 + Lineart = 8 + Softedge = 9 + Seg = 10 + Inpaint = 11 + Ip2p = 12 + Shuffle = 13 + Mlsd = 14 + Tile = 15 + Blur = 16 + Lowquality = 17 + Gray = 18 + + +class LoRAMode(object): + All = 0 + Base = 1 + Refiner = 2 + + +class CompressionMethod(object): + Disabled = 0 + H264 = 1 + H265 = 2 + Jpeg = 3 + + +class ColorCalibration(object): + Disabled = 0 + Lab = 1 + + +class Control(object): + __slots__ = ['_tab'] + + @classmethod + def GetRootAs(cls, buf, offset: int = 0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Control() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsControl(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) + # Control + def Init(self, buf: bytes, pos: int): + self._tab = flatbuffers.table.Table(buf, pos) + + # Control + def File(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # Control + def Weight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # Control + def GuidanceStart(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.0 + + # Control + def GuidanceEnd(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # Control + def NoPrompt(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # Control + def GlobalAveragePooling(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return True + + # Control + def DownSamplingRate(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # Control + def ControlMode(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + + # Control + def TargetBlocks(self, j: int): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + if o != 0: + a = self._tab.Vector(o) + return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return "" + + # Control + def TargetBlocksLength(self) -> int: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + + # Control + def TargetBlocksIsNone(self) -> bool: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + return o == 0 + + # Control + def InputOverride(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + +def ControlStart(builder: flatbuffers.Builder): + builder.StartObject(10) + +def ControlAddFile(builder: flatbuffers.Builder, file: int): + builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(file), 0) + +def ControlAddWeight(builder: flatbuffers.Builder, weight: float): + builder.PrependFloat32Slot(1, weight, 1.0) + +def ControlAddGuidanceStart(builder: flatbuffers.Builder, guidanceStart: float): + builder.PrependFloat32Slot(2, guidanceStart, 0.0) + +def ControlAddGuidanceEnd(builder: flatbuffers.Builder, guidanceEnd: float): + builder.PrependFloat32Slot(3, guidanceEnd, 1.0) + +def ControlAddNoPrompt(builder: flatbuffers.Builder, noPrompt: bool): + builder.PrependBoolSlot(4, noPrompt, 0) + +def ControlAddGlobalAveragePooling(builder: flatbuffers.Builder, globalAveragePooling: bool): + builder.PrependBoolSlot(5, globalAveragePooling, 1) + +def ControlAddDownSamplingRate(builder: flatbuffers.Builder, downSamplingRate: float): + builder.PrependFloat32Slot(6, downSamplingRate, 1.0) + +def ControlAddControlMode(builder: flatbuffers.Builder, controlMode: int): + builder.PrependInt8Slot(7, controlMode, 0) + +def ControlAddTargetBlocks(builder: flatbuffers.Builder, targetBlocks: int): + builder.PrependUOffsetTRelativeSlot(8, flatbuffers.number_types.UOffsetTFlags.py_type(targetBlocks), 0) + +def ControlStartTargetBlocksVector(builder, numElems: int) -> int: + return builder.StartVector(4, numElems, 4) + +def ControlAddInputOverride(builder: flatbuffers.Builder, inputOverride: int): + builder.PrependInt8Slot(9, inputOverride, 0) + +def ControlEnd(builder: flatbuffers.Builder) -> int: + return builder.EndObject() + + +try: + from typing import List +except: + pass + +class ControlT(object): + + # ControlT + def __init__( + self, + file = None, + weight = 1.0, + guidanceStart = 0.0, + guidanceEnd = 1.0, + noPrompt = False, + globalAveragePooling = True, + downSamplingRate = 1.0, + controlMode = 0, + targetBlocks = None, + inputOverride = 0, + ): + self.file = file # type: Optional[str] + self.weight = weight # type: float + self.guidanceStart = guidanceStart # type: float + self.guidanceEnd = guidanceEnd # type: float + self.noPrompt = noPrompt # type: bool + self.globalAveragePooling = globalAveragePooling # type: bool + self.downSamplingRate = downSamplingRate # type: float + self.controlMode = controlMode # type: int + self.targetBlocks = targetBlocks # type: Optional[List[Optional[str]]] + self.inputOverride = inputOverride # type: int + + @classmethod + def InitFromBuf(cls, buf, pos): + control = Control() + control.Init(buf, pos) + return cls.InitFromObj(control) + + @classmethod + def InitFromPackedBuf(cls, buf, pos=0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, pos) + return cls.InitFromBuf(buf, pos+n) + + @classmethod + def InitFromObj(cls, control): + x = ControlT() + x._UnPack(control) + return x + + # ControlT + def _UnPack(self, control): + if control is None: + return + self.file = control.File() + self.weight = control.Weight() + self.guidanceStart = control.GuidanceStart() + self.guidanceEnd = control.GuidanceEnd() + self.noPrompt = control.NoPrompt() + self.globalAveragePooling = control.GlobalAveragePooling() + self.downSamplingRate = control.DownSamplingRate() + self.controlMode = control.ControlMode() + if not control.TargetBlocksIsNone(): + self.targetBlocks = [] + for i in range(control.TargetBlocksLength()): + self.targetBlocks.append(control.TargetBlocks(i)) + self.inputOverride = control.InputOverride() + + # ControlT + def Pack(self, builder): + if self.file is not None: + file = builder.CreateString(self.file) + if self.targetBlocks is not None: + targetBlockslist = [] + for i in range(len(self.targetBlocks)): + targetBlockslist.append(builder.CreateString(self.targetBlocks[i])) + ControlStartTargetBlocksVector(builder, len(self.targetBlocks)) + for i in reversed(range(len(self.targetBlocks))): + builder.PrependUOffsetTRelative(targetBlockslist[i]) + targetBlocks = builder.EndVector() + ControlStart(builder) + if self.file is not None: + ControlAddFile(builder, file) + ControlAddWeight(builder, self.weight) + ControlAddGuidanceStart(builder, self.guidanceStart) + ControlAddGuidanceEnd(builder, self.guidanceEnd) + ControlAddNoPrompt(builder, self.noPrompt) + ControlAddGlobalAveragePooling(builder, self.globalAveragePooling) + ControlAddDownSamplingRate(builder, self.downSamplingRate) + ControlAddControlMode(builder, self.controlMode) + if self.targetBlocks is not None: + ControlAddTargetBlocks(builder, targetBlocks) + ControlAddInputOverride(builder, self.inputOverride) + control = ControlEnd(builder) + return control + + +class LoRA(object): + __slots__ = ['_tab'] + + @classmethod + def GetRootAs(cls, buf, offset: int = 0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = LoRA() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsLoRA(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) + # LoRA + def Init(self, buf: bytes, pos: int): + self._tab = flatbuffers.table.Table(buf, pos) + + # LoRA + def File(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # LoRA + def Weight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.6 + + # LoRA + def Mode(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + +def LoRAStart(builder: flatbuffers.Builder): + builder.StartObject(3) + +def LoRAAddFile(builder: flatbuffers.Builder, file: int): + builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(file), 0) + +def LoRAAddWeight(builder: flatbuffers.Builder, weight: float): + builder.PrependFloat32Slot(1, weight, 0.6) + +def LoRAAddMode(builder: flatbuffers.Builder, mode: int): + builder.PrependInt8Slot(2, mode, 0) + +def LoRAEnd(builder: flatbuffers.Builder) -> int: + return builder.EndObject() + + + +class LoRAT(object): + + # LoRAT + def __init__( + self, + file = None, + weight = 0.6, + mode = 0, + ): + self.file = file # type: Optional[str] + self.weight = weight # type: float + self.mode = mode # type: int + + @classmethod + def InitFromBuf(cls, buf, pos): + loRa = LoRA() + loRa.Init(buf, pos) + return cls.InitFromObj(loRa) + + @classmethod + def InitFromPackedBuf(cls, buf, pos=0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, pos) + return cls.InitFromBuf(buf, pos+n) + + @classmethod + def InitFromObj(cls, loRa): + x = LoRAT() + x._UnPack(loRa) + return x + + # LoRAT + def _UnPack(self, loRa): + if loRa is None: + return + self.file = loRa.File() + self.weight = loRa.Weight() + self.mode = loRa.Mode() + + # LoRAT + def Pack(self, builder): + if self.file is not None: + file = builder.CreateString(self.file) + LoRAStart(builder) + if self.file is not None: + LoRAAddFile(builder, file) + LoRAAddWeight(builder, self.weight) + LoRAAddMode(builder, self.mode) + loRa = LoRAEnd(builder) + return loRa + + +class GenerationConfiguration(object): + __slots__ = ['_tab'] + + @classmethod + def GetRootAs(cls, buf, offset: int = 0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = GenerationConfiguration() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsGenerationConfiguration(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) + # GenerationConfiguration + def Init(self, buf: bytes, pos: int): + self._tab = flatbuffers.table.Table(buf, pos) + + # GenerationConfiguration + def Id(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int64Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def StartWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def StartHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def Seed(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def Steps(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def GuidanceScale(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.0 + + # GenerationConfiguration + def Strength(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.0 + + # GenerationConfiguration + def Model(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def Sampler(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def BatchCount(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 1 + + # GenerationConfiguration + def BatchSize(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 1 + + # GenerationConfiguration + def HiresFix(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def HiresFixStartWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def HiresFixStartHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def HiresFixStrength(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.7 + + # GenerationConfiguration + def Upscaler(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def ImageGuidanceScale(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.5 + + # GenerationConfiguration + def SeedMode(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def ClipSkip(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(40)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 1 + + # GenerationConfiguration + def Controls(self, j: int) -> Optional[Control]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) + if o != 0: + x = self._tab.Vector(o) + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + obj = Control() + obj.Init(self._tab.Bytes, x) + return obj + return None + + # GenerationConfiguration + def ControlsLength(self) -> int: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + + # GenerationConfiguration + def ControlsIsNone(self) -> bool: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) + return o == 0 + + # GenerationConfiguration + def Loras(self, j: int) -> Optional[LoRA]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(44)) + if o != 0: + x = self._tab.Vector(o) + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + obj = LoRA() + obj.Init(self._tab.Bytes, x) + return obj + return None + + # GenerationConfiguration + def LorasLength(self) -> int: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(44)) + if o != 0: + return self._tab.VectorLen(o) + return 0 + + # GenerationConfiguration + def LorasIsNone(self) -> bool: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(44)) + return o == 0 + + # GenerationConfiguration + def MaskBlur(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(46)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.0 + + # GenerationConfiguration + def FaceRestoration(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(48)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def ClipWeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(54)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # GenerationConfiguration + def NegativePromptForImagePrior(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(56)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return True + + # GenerationConfiguration + def ImagePriorSteps(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(58)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 5 + + # GenerationConfiguration + def RefinerModel(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(60)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def OriginalImageHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(62)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def OriginalImageWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(64)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def CropTop(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(66)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def CropLeft(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(68)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def TargetImageHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(70)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def TargetImageWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(72)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def AestheticScore(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(74)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 6.0 + + # GenerationConfiguration + def NegativeAestheticScore(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(76)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 2.5 + + # GenerationConfiguration + def ZeroNegativePrompt(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(78)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def RefinerStart(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(80)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.7 + + # GenerationConfiguration + def NegativeOriginalImageHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(82)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def NegativeOriginalImageWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(84)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def Name(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(86)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def FpsId(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(88)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 5 + + # GenerationConfiguration + def MotionBucketId(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(90)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 127 + + # GenerationConfiguration + def CondAug(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(92)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.02 + + # GenerationConfiguration + def StartFrameCfg(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(94)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # GenerationConfiguration + def NumFrames(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(96)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 14 + + # GenerationConfiguration + def MaskBlurOutset(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(98)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def Sharpness(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(100)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.0 + + # GenerationConfiguration + def Shift(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(102)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # GenerationConfiguration + def Stage2Steps(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(104)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return 10 + + # GenerationConfiguration + def Stage2Cfg(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(106)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # GenerationConfiguration + def Stage2Shift(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(108)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 1.0 + + # GenerationConfiguration + def TiledDecoding(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(110)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def DecodingTileWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(112)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 10 + + # GenerationConfiguration + def DecodingTileHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(114)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 10 + + # GenerationConfiguration + def DecodingTileOverlap(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(116)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 2 + + # GenerationConfiguration + def StochasticSamplingGamma(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(118)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.3 + + # GenerationConfiguration + def PreserveOriginalAfterInpaint(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(120)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return True + + # GenerationConfiguration + def TiledDiffusion(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(122)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def DiffusionTileWidth(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(124)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 16 + + # GenerationConfiguration + def DiffusionTileHeight(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(126)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 16 + + # GenerationConfiguration + def DiffusionTileOverlap(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(128)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return 2 + + # GenerationConfiguration + def UpscalerScaleFactor(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(130)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def T5TextEncoder(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(132)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return True + + # GenerationConfiguration + def SeparateClipL(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(134)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def ClipLText(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(136)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def SeparateOpenClipG(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(138)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def OpenClipGText(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(140)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def SpeedUpWithGuidanceEmbed(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(142)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return True + + # GenerationConfiguration + def GuidanceEmbed(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(144)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 3.5 + + # GenerationConfiguration + def ResolutionDependentShift(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(146)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return True + + # GenerationConfiguration + def TeaCacheStart(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(148)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 5 + + # GenerationConfiguration + def TeaCacheEnd(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(150)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return -1 + + # GenerationConfiguration + def TeaCacheThreshold(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(152)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 0.06 + + # GenerationConfiguration + def TeaCache(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(154)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def SeparateT5(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(156)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def T5Text(self) -> Optional[str]: + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(158)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # GenerationConfiguration + def TeaCacheMaxSkipSteps(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(160)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 3 + + # GenerationConfiguration + def CausalInferenceEnabled(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(162)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def CausalInference(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(164)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 3 + + # GenerationConfiguration + def CausalInferencePad(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(166)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def CfgZeroStar(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(168)) + if o != 0: + return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return False + + # GenerationConfiguration + def CfgZeroInitSteps(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(170)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def CompressionArtifacts(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(172)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + + # GenerationConfiguration + def CompressionArtifactsQuality(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(174)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return 43.1 + + # GenerationConfiguration + def ColorCalibration(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(176)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int8Flags, o + self._tab.Pos) + return 0 + +def GenerationConfigurationStart(builder: flatbuffers.Builder): + builder.StartObject(87) + +def GenerationConfigurationAddId(builder: flatbuffers.Builder, id: int): + builder.PrependInt64Slot(0, id, 0) + +def GenerationConfigurationAddStartWidth(builder: flatbuffers.Builder, startWidth: int): + builder.PrependUint16Slot(1, startWidth, 0) + +def GenerationConfigurationAddStartHeight(builder: flatbuffers.Builder, startHeight: int): + builder.PrependUint16Slot(2, startHeight, 0) + +def GenerationConfigurationAddSeed(builder: flatbuffers.Builder, seed: int): + builder.PrependUint32Slot(3, seed, 0) + +def GenerationConfigurationAddSteps(builder: flatbuffers.Builder, steps: int): + builder.PrependUint32Slot(4, steps, 0) + +def GenerationConfigurationAddGuidanceScale(builder: flatbuffers.Builder, guidanceScale: float): + builder.PrependFloat32Slot(5, guidanceScale, 0.0) + +def GenerationConfigurationAddStrength(builder: flatbuffers.Builder, strength: float): + builder.PrependFloat32Slot(6, strength, 0.0) + +def GenerationConfigurationAddModel(builder: flatbuffers.Builder, model: int): + builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(model), 0) + +def GenerationConfigurationAddSampler(builder: flatbuffers.Builder, sampler: int): + builder.PrependInt8Slot(8, sampler, 0) + +def GenerationConfigurationAddBatchCount(builder: flatbuffers.Builder, batchCount: int): + builder.PrependUint32Slot(9, batchCount, 1) + +def GenerationConfigurationAddBatchSize(builder: flatbuffers.Builder, batchSize: int): + builder.PrependUint32Slot(10, batchSize, 1) + +def GenerationConfigurationAddHiresFix(builder: flatbuffers.Builder, hiresFix: bool): + builder.PrependBoolSlot(11, hiresFix, 0) + +def GenerationConfigurationAddHiresFixStartWidth(builder: flatbuffers.Builder, hiresFixStartWidth: int): + builder.PrependUint16Slot(12, hiresFixStartWidth, 0) + +def GenerationConfigurationAddHiresFixStartHeight(builder: flatbuffers.Builder, hiresFixStartHeight: int): + builder.PrependUint16Slot(13, hiresFixStartHeight, 0) + +def GenerationConfigurationAddHiresFixStrength(builder: flatbuffers.Builder, hiresFixStrength: float): + builder.PrependFloat32Slot(14, hiresFixStrength, 0.7) + +def GenerationConfigurationAddUpscaler(builder: flatbuffers.Builder, upscaler: int): + builder.PrependUOffsetTRelativeSlot(15, flatbuffers.number_types.UOffsetTFlags.py_type(upscaler), 0) + +def GenerationConfigurationAddImageGuidanceScale(builder: flatbuffers.Builder, imageGuidanceScale: float): + builder.PrependFloat32Slot(16, imageGuidanceScale, 1.5) + +def GenerationConfigurationAddSeedMode(builder: flatbuffers.Builder, seedMode: int): + builder.PrependInt8Slot(17, seedMode, 0) + +def GenerationConfigurationAddClipSkip(builder: flatbuffers.Builder, clipSkip: int): + builder.PrependUint32Slot(18, clipSkip, 1) + +def GenerationConfigurationAddControls(builder: flatbuffers.Builder, controls: int): + builder.PrependUOffsetTRelativeSlot(19, flatbuffers.number_types.UOffsetTFlags.py_type(controls), 0) + +def GenerationConfigurationStartControlsVector(builder, numElems: int) -> int: + return builder.StartVector(4, numElems, 4) + +def GenerationConfigurationAddLoras(builder: flatbuffers.Builder, loras: int): + builder.PrependUOffsetTRelativeSlot(20, flatbuffers.number_types.UOffsetTFlags.py_type(loras), 0) + +def GenerationConfigurationStartLorasVector(builder, numElems: int) -> int: + return builder.StartVector(4, numElems, 4) + +def GenerationConfigurationAddMaskBlur(builder: flatbuffers.Builder, maskBlur: float): + builder.PrependFloat32Slot(21, maskBlur, 0.0) + +def GenerationConfigurationAddFaceRestoration(builder: flatbuffers.Builder, faceRestoration: int): + builder.PrependUOffsetTRelativeSlot(22, flatbuffers.number_types.UOffsetTFlags.py_type(faceRestoration), 0) + +def GenerationConfigurationAddClipWeight(builder: flatbuffers.Builder, clipWeight: float): + builder.PrependFloat32Slot(25, clipWeight, 1.0) + +def GenerationConfigurationAddNegativePromptForImagePrior(builder: flatbuffers.Builder, negativePromptForImagePrior: bool): + builder.PrependBoolSlot(26, negativePromptForImagePrior, 1) + +def GenerationConfigurationAddImagePriorSteps(builder: flatbuffers.Builder, imagePriorSteps: int): + builder.PrependUint32Slot(27, imagePriorSteps, 5) + +def GenerationConfigurationAddRefinerModel(builder: flatbuffers.Builder, refinerModel: int): + builder.PrependUOffsetTRelativeSlot(28, flatbuffers.number_types.UOffsetTFlags.py_type(refinerModel), 0) + +def GenerationConfigurationAddOriginalImageHeight(builder: flatbuffers.Builder, originalImageHeight: int): + builder.PrependUint32Slot(29, originalImageHeight, 0) + +def GenerationConfigurationAddOriginalImageWidth(builder: flatbuffers.Builder, originalImageWidth: int): + builder.PrependUint32Slot(30, originalImageWidth, 0) + +def GenerationConfigurationAddCropTop(builder: flatbuffers.Builder, cropTop: int): + builder.PrependInt32Slot(31, cropTop, 0) + +def GenerationConfigurationAddCropLeft(builder: flatbuffers.Builder, cropLeft: int): + builder.PrependInt32Slot(32, cropLeft, 0) + +def GenerationConfigurationAddTargetImageHeight(builder: flatbuffers.Builder, targetImageHeight: int): + builder.PrependUint32Slot(33, targetImageHeight, 0) + +def GenerationConfigurationAddTargetImageWidth(builder: flatbuffers.Builder, targetImageWidth: int): + builder.PrependUint32Slot(34, targetImageWidth, 0) + +def GenerationConfigurationAddAestheticScore(builder: flatbuffers.Builder, aestheticScore: float): + builder.PrependFloat32Slot(35, aestheticScore, 6.0) + +def GenerationConfigurationAddNegativeAestheticScore(builder: flatbuffers.Builder, negativeAestheticScore: float): + builder.PrependFloat32Slot(36, negativeAestheticScore, 2.5) + +def GenerationConfigurationAddZeroNegativePrompt(builder: flatbuffers.Builder, zeroNegativePrompt: bool): + builder.PrependBoolSlot(37, zeroNegativePrompt, 0) + +def GenerationConfigurationAddRefinerStart(builder: flatbuffers.Builder, refinerStart: float): + builder.PrependFloat32Slot(38, refinerStart, 0.7) + +def GenerationConfigurationAddNegativeOriginalImageHeight(builder: flatbuffers.Builder, negativeOriginalImageHeight: int): + builder.PrependUint32Slot(39, negativeOriginalImageHeight, 0) + +def GenerationConfigurationAddNegativeOriginalImageWidth(builder: flatbuffers.Builder, negativeOriginalImageWidth: int): + builder.PrependUint32Slot(40, negativeOriginalImageWidth, 0) + +def GenerationConfigurationAddName(builder: flatbuffers.Builder, name: int): + builder.PrependUOffsetTRelativeSlot(41, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + +def GenerationConfigurationAddFpsId(builder: flatbuffers.Builder, fpsId: int): + builder.PrependUint32Slot(42, fpsId, 5) + +def GenerationConfigurationAddMotionBucketId(builder: flatbuffers.Builder, motionBucketId: int): + builder.PrependUint32Slot(43, motionBucketId, 127) + +def GenerationConfigurationAddCondAug(builder: flatbuffers.Builder, condAug: float): + builder.PrependFloat32Slot(44, condAug, 0.02) + +def GenerationConfigurationAddStartFrameCfg(builder: flatbuffers.Builder, startFrameCfg: float): + builder.PrependFloat32Slot(45, startFrameCfg, 1.0) + +def GenerationConfigurationAddNumFrames(builder: flatbuffers.Builder, numFrames: int): + builder.PrependUint32Slot(46, numFrames, 14) + +def GenerationConfigurationAddMaskBlurOutset(builder: flatbuffers.Builder, maskBlurOutset: int): + builder.PrependInt32Slot(47, maskBlurOutset, 0) + +def GenerationConfigurationAddSharpness(builder: flatbuffers.Builder, sharpness: float): + builder.PrependFloat32Slot(48, sharpness, 0.0) + +def GenerationConfigurationAddShift(builder: flatbuffers.Builder, shift: float): + builder.PrependFloat32Slot(49, shift, 1.0) + +def GenerationConfigurationAddStage2Steps(builder: flatbuffers.Builder, stage2Steps: int): + builder.PrependUint32Slot(50, stage2Steps, 10) + +def GenerationConfigurationAddStage2Cfg(builder: flatbuffers.Builder, stage2Cfg: float): + builder.PrependFloat32Slot(51, stage2Cfg, 1.0) + +def GenerationConfigurationAddStage2Shift(builder: flatbuffers.Builder, stage2Shift: float): + builder.PrependFloat32Slot(52, stage2Shift, 1.0) + +def GenerationConfigurationAddTiledDecoding(builder: flatbuffers.Builder, tiledDecoding: bool): + builder.PrependBoolSlot(53, tiledDecoding, 0) + +def GenerationConfigurationAddDecodingTileWidth(builder: flatbuffers.Builder, decodingTileWidth: int): + builder.PrependUint16Slot(54, decodingTileWidth, 10) + +def GenerationConfigurationAddDecodingTileHeight(builder: flatbuffers.Builder, decodingTileHeight: int): + builder.PrependUint16Slot(55, decodingTileHeight, 10) + +def GenerationConfigurationAddDecodingTileOverlap(builder: flatbuffers.Builder, decodingTileOverlap: int): + builder.PrependUint16Slot(56, decodingTileOverlap, 2) + +def GenerationConfigurationAddStochasticSamplingGamma(builder: flatbuffers.Builder, stochasticSamplingGamma: float): + builder.PrependFloat32Slot(57, stochasticSamplingGamma, 0.3) + +def GenerationConfigurationAddPreserveOriginalAfterInpaint(builder: flatbuffers.Builder, preserveOriginalAfterInpaint: bool): + builder.PrependBoolSlot(58, preserveOriginalAfterInpaint, 1) + +def GenerationConfigurationAddTiledDiffusion(builder: flatbuffers.Builder, tiledDiffusion: bool): + builder.PrependBoolSlot(59, tiledDiffusion, 0) + +def GenerationConfigurationAddDiffusionTileWidth(builder: flatbuffers.Builder, diffusionTileWidth: int): + builder.PrependUint16Slot(60, diffusionTileWidth, 16) + +def GenerationConfigurationAddDiffusionTileHeight(builder: flatbuffers.Builder, diffusionTileHeight: int): + builder.PrependUint16Slot(61, diffusionTileHeight, 16) + +def GenerationConfigurationAddDiffusionTileOverlap(builder: flatbuffers.Builder, diffusionTileOverlap: int): + builder.PrependUint16Slot(62, diffusionTileOverlap, 2) + +def GenerationConfigurationAddUpscalerScaleFactor(builder: flatbuffers.Builder, upscalerScaleFactor: int): + builder.PrependUint8Slot(63, upscalerScaleFactor, 0) + +def GenerationConfigurationAddT5TextEncoder(builder: flatbuffers.Builder, t5TextEncoder: bool): + builder.PrependBoolSlot(64, t5TextEncoder, 1) + +def GenerationConfigurationAddSeparateClipL(builder: flatbuffers.Builder, separateClipL: bool): + builder.PrependBoolSlot(65, separateClipL, 0) + +def GenerationConfigurationAddClipLText(builder: flatbuffers.Builder, clipLText: int): + builder.PrependUOffsetTRelativeSlot(66, flatbuffers.number_types.UOffsetTFlags.py_type(clipLText), 0) + +def GenerationConfigurationAddSeparateOpenClipG(builder: flatbuffers.Builder, separateOpenClipG: bool): + builder.PrependBoolSlot(67, separateOpenClipG, 0) + +def GenerationConfigurationAddOpenClipGText(builder: flatbuffers.Builder, openClipGText: int): + builder.PrependUOffsetTRelativeSlot(68, flatbuffers.number_types.UOffsetTFlags.py_type(openClipGText), 0) + +def GenerationConfigurationAddSpeedUpWithGuidanceEmbed(builder: flatbuffers.Builder, speedUpWithGuidanceEmbed: bool): + builder.PrependBoolSlot(69, speedUpWithGuidanceEmbed, 1) + +def GenerationConfigurationAddGuidanceEmbed(builder: flatbuffers.Builder, guidanceEmbed: float): + builder.PrependFloat32Slot(70, guidanceEmbed, 3.5) + +def GenerationConfigurationAddResolutionDependentShift(builder: flatbuffers.Builder, resolutionDependentShift: bool): + builder.PrependBoolSlot(71, resolutionDependentShift, 1) + +def GenerationConfigurationAddTeaCacheStart(builder: flatbuffers.Builder, teaCacheStart: int): + builder.PrependInt32Slot(72, teaCacheStart, 5) + +def GenerationConfigurationAddTeaCacheEnd(builder: flatbuffers.Builder, teaCacheEnd: int): + builder.PrependInt32Slot(73, teaCacheEnd, -1) + +def GenerationConfigurationAddTeaCacheThreshold(builder: flatbuffers.Builder, teaCacheThreshold: float): + builder.PrependFloat32Slot(74, teaCacheThreshold, 0.06) + +def GenerationConfigurationAddTeaCache(builder: flatbuffers.Builder, teaCache: bool): + builder.PrependBoolSlot(75, teaCache, 0) + +def GenerationConfigurationAddSeparateT5(builder: flatbuffers.Builder, separateT5: bool): + builder.PrependBoolSlot(76, separateT5, 0) + +def GenerationConfigurationAddT5Text(builder: flatbuffers.Builder, t5Text: int): + builder.PrependUOffsetTRelativeSlot(77, flatbuffers.number_types.UOffsetTFlags.py_type(t5Text), 0) + +def GenerationConfigurationAddTeaCacheMaxSkipSteps(builder: flatbuffers.Builder, teaCacheMaxSkipSteps: int): + builder.PrependInt32Slot(78, teaCacheMaxSkipSteps, 3) + +def GenerationConfigurationAddCausalInferenceEnabled(builder: flatbuffers.Builder, causalInferenceEnabled: bool): + builder.PrependBoolSlot(79, causalInferenceEnabled, 0) + +def GenerationConfigurationAddCausalInference(builder: flatbuffers.Builder, causalInference: int): + builder.PrependInt32Slot(80, causalInference, 3) + +def GenerationConfigurationAddCausalInferencePad(builder: flatbuffers.Builder, causalInferencePad: int): + builder.PrependInt32Slot(81, causalInferencePad, 0) + +def GenerationConfigurationAddCfgZeroStar(builder: flatbuffers.Builder, cfgZeroStar: bool): + builder.PrependBoolSlot(82, cfgZeroStar, 0) + +def GenerationConfigurationAddCfgZeroInitSteps(builder: flatbuffers.Builder, cfgZeroInitSteps: int): + builder.PrependInt32Slot(83, cfgZeroInitSteps, 0) + +def GenerationConfigurationAddCompressionArtifacts(builder: flatbuffers.Builder, compressionArtifacts: int): + builder.PrependInt8Slot(84, compressionArtifacts, 0) + +def GenerationConfigurationAddCompressionArtifactsQuality(builder: flatbuffers.Builder, compressionArtifactsQuality: float): + builder.PrependFloat32Slot(85, compressionArtifactsQuality, 43.1) + +def GenerationConfigurationAddColorCalibration(builder: flatbuffers.Builder, colorCalibration: int): + builder.PrependInt8Slot(86, colorCalibration, 0) + +def GenerationConfigurationEnd(builder: flatbuffers.Builder) -> int: + return builder.EndObject() + + +try: + from typing import List +except: + pass + +class GenerationConfigurationT(object): + + # GenerationConfigurationT + def __init__( + self, + id = 0, + startWidth = 0, + startHeight = 0, + seed = 0, + steps = 0, + guidanceScale = 0.0, + strength = 0.0, + model = None, + sampler = 0, + batchCount = 1, + batchSize = 1, + hiresFix = False, + hiresFixStartWidth = 0, + hiresFixStartHeight = 0, + hiresFixStrength = 0.7, + upscaler = None, + imageGuidanceScale = 1.5, + seedMode = 0, + clipSkip = 1, + controls = None, + loras = None, + maskBlur = 0.0, + faceRestoration = None, + clipWeight = 1.0, + negativePromptForImagePrior = True, + imagePriorSteps = 5, + refinerModel = None, + originalImageHeight = 0, + originalImageWidth = 0, + cropTop = 0, + cropLeft = 0, + targetImageHeight = 0, + targetImageWidth = 0, + aestheticScore = 6.0, + negativeAestheticScore = 2.5, + zeroNegativePrompt = False, + refinerStart = 0.7, + negativeOriginalImageHeight = 0, + negativeOriginalImageWidth = 0, + name = None, + fpsId = 5, + motionBucketId = 127, + condAug = 0.02, + startFrameCfg = 1.0, + numFrames = 14, + maskBlurOutset = 0, + sharpness = 0.0, + shift = 1.0, + stage2Steps = 10, + stage2Cfg = 1.0, + stage2Shift = 1.0, + tiledDecoding = False, + decodingTileWidth = 10, + decodingTileHeight = 10, + decodingTileOverlap = 2, + stochasticSamplingGamma = 0.3, + preserveOriginalAfterInpaint = True, + tiledDiffusion = False, + diffusionTileWidth = 16, + diffusionTileHeight = 16, + diffusionTileOverlap = 2, + upscalerScaleFactor = 0, + t5TextEncoder = True, + separateClipL = False, + clipLText = None, + separateOpenClipG = False, + openClipGText = None, + speedUpWithGuidanceEmbed = True, + guidanceEmbed = 3.5, + resolutionDependentShift = True, + teaCacheStart = 5, + teaCacheEnd = -1, + teaCacheThreshold = 0.06, + teaCache = False, + separateT5 = False, + t5Text = None, + teaCacheMaxSkipSteps = 3, + causalInferenceEnabled = False, + causalInference = 3, + causalInferencePad = 0, + cfgZeroStar = False, + cfgZeroInitSteps = 0, + compressionArtifacts = 0, + compressionArtifactsQuality = 43.1, + colorCalibration = 0, + ): + self.id = id # type: int + self.startWidth = startWidth # type: int + self.startHeight = startHeight # type: int + self.seed = seed # type: int + self.steps = steps # type: int + self.guidanceScale = guidanceScale # type: float + self.strength = strength # type: float + self.model = model # type: Optional[str] + self.sampler = sampler # type: int + self.batchCount = batchCount # type: int + self.batchSize = batchSize # type: int + self.hiresFix = hiresFix # type: bool + self.hiresFixStartWidth = hiresFixStartWidth # type: int + self.hiresFixStartHeight = hiresFixStartHeight # type: int + self.hiresFixStrength = hiresFixStrength # type: float + self.upscaler = upscaler # type: Optional[str] + self.imageGuidanceScale = imageGuidanceScale # type: float + self.seedMode = seedMode # type: int + self.clipSkip = clipSkip # type: int + self.controls = controls # type: Optional[List[ControlT]] + self.loras = loras # type: Optional[List[LoRAT]] + self.maskBlur = maskBlur # type: float + self.faceRestoration = faceRestoration # type: Optional[str] + self.clipWeight = clipWeight # type: float + self.negativePromptForImagePrior = negativePromptForImagePrior # type: bool + self.imagePriorSteps = imagePriorSteps # type: int + self.refinerModel = refinerModel # type: Optional[str] + self.originalImageHeight = originalImageHeight # type: int + self.originalImageWidth = originalImageWidth # type: int + self.cropTop = cropTop # type: int + self.cropLeft = cropLeft # type: int + self.targetImageHeight = targetImageHeight # type: int + self.targetImageWidth = targetImageWidth # type: int + self.aestheticScore = aestheticScore # type: float + self.negativeAestheticScore = negativeAestheticScore # type: float + self.zeroNegativePrompt = zeroNegativePrompt # type: bool + self.refinerStart = refinerStart # type: float + self.negativeOriginalImageHeight = negativeOriginalImageHeight # type: int + self.negativeOriginalImageWidth = negativeOriginalImageWidth # type: int + self.name = name # type: Optional[str] + self.fpsId = fpsId # type: int + self.motionBucketId = motionBucketId # type: int + self.condAug = condAug # type: float + self.startFrameCfg = startFrameCfg # type: float + self.numFrames = numFrames # type: int + self.maskBlurOutset = maskBlurOutset # type: int + self.sharpness = sharpness # type: float + self.shift = shift # type: float + self.stage2Steps = stage2Steps # type: int + self.stage2Cfg = stage2Cfg # type: float + self.stage2Shift = stage2Shift # type: float + self.tiledDecoding = tiledDecoding # type: bool + self.decodingTileWidth = decodingTileWidth # type: int + self.decodingTileHeight = decodingTileHeight # type: int + self.decodingTileOverlap = decodingTileOverlap # type: int + self.stochasticSamplingGamma = stochasticSamplingGamma # type: float + self.preserveOriginalAfterInpaint = preserveOriginalAfterInpaint # type: bool + self.tiledDiffusion = tiledDiffusion # type: bool + self.diffusionTileWidth = diffusionTileWidth # type: int + self.diffusionTileHeight = diffusionTileHeight # type: int + self.diffusionTileOverlap = diffusionTileOverlap # type: int + self.upscalerScaleFactor = upscalerScaleFactor # type: int + self.t5TextEncoder = t5TextEncoder # type: bool + self.separateClipL = separateClipL # type: bool + self.clipLText = clipLText # type: Optional[str] + self.separateOpenClipG = separateOpenClipG # type: bool + self.openClipGText = openClipGText # type: Optional[str] + self.speedUpWithGuidanceEmbed = speedUpWithGuidanceEmbed # type: bool + self.guidanceEmbed = guidanceEmbed # type: float + self.resolutionDependentShift = resolutionDependentShift # type: bool + self.teaCacheStart = teaCacheStart # type: int + self.teaCacheEnd = teaCacheEnd # type: int + self.teaCacheThreshold = teaCacheThreshold # type: float + self.teaCache = teaCache # type: bool + self.separateT5 = separateT5 # type: bool + self.t5Text = t5Text # type: Optional[str] + self.teaCacheMaxSkipSteps = teaCacheMaxSkipSteps # type: int + self.causalInferenceEnabled = causalInferenceEnabled # type: bool + self.causalInference = causalInference # type: int + self.causalInferencePad = causalInferencePad # type: int + self.cfgZeroStar = cfgZeroStar # type: bool + self.cfgZeroInitSteps = cfgZeroInitSteps # type: int + self.compressionArtifacts = compressionArtifacts # type: int + self.compressionArtifactsQuality = compressionArtifactsQuality # type: float + self.colorCalibration = colorCalibration # type: int + + @classmethod + def InitFromBuf(cls, buf, pos): + generationConfiguration = GenerationConfiguration() + generationConfiguration.Init(buf, pos) + return cls.InitFromObj(generationConfiguration) + + @classmethod + def InitFromPackedBuf(cls, buf, pos=0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, pos) + return cls.InitFromBuf(buf, pos+n) + + @classmethod + def InitFromObj(cls, generationConfiguration): + x = GenerationConfigurationT() + x._UnPack(generationConfiguration) + return x + + # GenerationConfigurationT + def _UnPack(self, generationConfiguration): + if generationConfiguration is None: + return + self.id = generationConfiguration.Id() + self.startWidth = generationConfiguration.StartWidth() + self.startHeight = generationConfiguration.StartHeight() + self.seed = generationConfiguration.Seed() + self.steps = generationConfiguration.Steps() + self.guidanceScale = generationConfiguration.GuidanceScale() + self.strength = generationConfiguration.Strength() + self.model = generationConfiguration.Model() + self.sampler = generationConfiguration.Sampler() + self.batchCount = generationConfiguration.BatchCount() + self.batchSize = generationConfiguration.BatchSize() + self.hiresFix = generationConfiguration.HiresFix() + self.hiresFixStartWidth = generationConfiguration.HiresFixStartWidth() + self.hiresFixStartHeight = generationConfiguration.HiresFixStartHeight() + self.hiresFixStrength = generationConfiguration.HiresFixStrength() + self.upscaler = generationConfiguration.Upscaler() + self.imageGuidanceScale = generationConfiguration.ImageGuidanceScale() + self.seedMode = generationConfiguration.SeedMode() + self.clipSkip = generationConfiguration.ClipSkip() + if not generationConfiguration.ControlsIsNone(): + self.controls = [] + for i in range(generationConfiguration.ControlsLength()): + if generationConfiguration.Controls(i) is None: + self.controls.append(None) + else: + control_ = ControlT.InitFromObj(generationConfiguration.Controls(i)) + self.controls.append(control_) + if not generationConfiguration.LorasIsNone(): + self.loras = [] + for i in range(generationConfiguration.LorasLength()): + if generationConfiguration.Loras(i) is None: + self.loras.append(None) + else: + loRA_ = LoRAT.InitFromObj(generationConfiguration.Loras(i)) + self.loras.append(loRA_) + self.maskBlur = generationConfiguration.MaskBlur() + self.faceRestoration = generationConfiguration.FaceRestoration() + self.clipWeight = generationConfiguration.ClipWeight() + self.negativePromptForImagePrior = generationConfiguration.NegativePromptForImagePrior() + self.imagePriorSteps = generationConfiguration.ImagePriorSteps() + self.refinerModel = generationConfiguration.RefinerModel() + self.originalImageHeight = generationConfiguration.OriginalImageHeight() + self.originalImageWidth = generationConfiguration.OriginalImageWidth() + self.cropTop = generationConfiguration.CropTop() + self.cropLeft = generationConfiguration.CropLeft() + self.targetImageHeight = generationConfiguration.TargetImageHeight() + self.targetImageWidth = generationConfiguration.TargetImageWidth() + self.aestheticScore = generationConfiguration.AestheticScore() + self.negativeAestheticScore = generationConfiguration.NegativeAestheticScore() + self.zeroNegativePrompt = generationConfiguration.ZeroNegativePrompt() + self.refinerStart = generationConfiguration.RefinerStart() + self.negativeOriginalImageHeight = generationConfiguration.NegativeOriginalImageHeight() + self.negativeOriginalImageWidth = generationConfiguration.NegativeOriginalImageWidth() + self.name = generationConfiguration.Name() + self.fpsId = generationConfiguration.FpsId() + self.motionBucketId = generationConfiguration.MotionBucketId() + self.condAug = generationConfiguration.CondAug() + self.startFrameCfg = generationConfiguration.StartFrameCfg() + self.numFrames = generationConfiguration.NumFrames() + self.maskBlurOutset = generationConfiguration.MaskBlurOutset() + self.sharpness = generationConfiguration.Sharpness() + self.shift = generationConfiguration.Shift() + self.stage2Steps = generationConfiguration.Stage2Steps() + self.stage2Cfg = generationConfiguration.Stage2Cfg() + self.stage2Shift = generationConfiguration.Stage2Shift() + self.tiledDecoding = generationConfiguration.TiledDecoding() + self.decodingTileWidth = generationConfiguration.DecodingTileWidth() + self.decodingTileHeight = generationConfiguration.DecodingTileHeight() + self.decodingTileOverlap = generationConfiguration.DecodingTileOverlap() + self.stochasticSamplingGamma = generationConfiguration.StochasticSamplingGamma() + self.preserveOriginalAfterInpaint = generationConfiguration.PreserveOriginalAfterInpaint() + self.tiledDiffusion = generationConfiguration.TiledDiffusion() + self.diffusionTileWidth = generationConfiguration.DiffusionTileWidth() + self.diffusionTileHeight = generationConfiguration.DiffusionTileHeight() + self.diffusionTileOverlap = generationConfiguration.DiffusionTileOverlap() + self.upscalerScaleFactor = generationConfiguration.UpscalerScaleFactor() + self.t5TextEncoder = generationConfiguration.T5TextEncoder() + self.separateClipL = generationConfiguration.SeparateClipL() + self.clipLText = generationConfiguration.ClipLText() + self.separateOpenClipG = generationConfiguration.SeparateOpenClipG() + self.openClipGText = generationConfiguration.OpenClipGText() + self.speedUpWithGuidanceEmbed = generationConfiguration.SpeedUpWithGuidanceEmbed() + self.guidanceEmbed = generationConfiguration.GuidanceEmbed() + self.resolutionDependentShift = generationConfiguration.ResolutionDependentShift() + self.teaCacheStart = generationConfiguration.TeaCacheStart() + self.teaCacheEnd = generationConfiguration.TeaCacheEnd() + self.teaCacheThreshold = generationConfiguration.TeaCacheThreshold() + self.teaCache = generationConfiguration.TeaCache() + self.separateT5 = generationConfiguration.SeparateT5() + self.t5Text = generationConfiguration.T5Text() + self.teaCacheMaxSkipSteps = generationConfiguration.TeaCacheMaxSkipSteps() + self.causalInferenceEnabled = generationConfiguration.CausalInferenceEnabled() + self.causalInference = generationConfiguration.CausalInference() + self.causalInferencePad = generationConfiguration.CausalInferencePad() + self.cfgZeroStar = generationConfiguration.CfgZeroStar() + self.cfgZeroInitSteps = generationConfiguration.CfgZeroInitSteps() + self.compressionArtifacts = generationConfiguration.CompressionArtifacts() + self.compressionArtifactsQuality = generationConfiguration.CompressionArtifactsQuality() + self.colorCalibration = generationConfiguration.ColorCalibration() + + # GenerationConfigurationT + def Pack(self, builder): + if self.model is not None: + model = builder.CreateString(self.model) + if self.upscaler is not None: + upscaler = builder.CreateString(self.upscaler) + if self.controls is not None: + controlslist = [] + for i in range(len(self.controls)): + controlslist.append(self.controls[i].Pack(builder)) + GenerationConfigurationStartControlsVector(builder, len(self.controls)) + for i in reversed(range(len(self.controls))): + builder.PrependUOffsetTRelative(controlslist[i]) + controls = builder.EndVector() + if self.loras is not None: + loraslist = [] + for i in range(len(self.loras)): + loraslist.append(self.loras[i].Pack(builder)) + GenerationConfigurationStartLorasVector(builder, len(self.loras)) + for i in reversed(range(len(self.loras))): + builder.PrependUOffsetTRelative(loraslist[i]) + loras = builder.EndVector() + if self.faceRestoration is not None: + faceRestoration = builder.CreateString(self.faceRestoration) + if self.refinerModel is not None: + refinerModel = builder.CreateString(self.refinerModel) + if self.name is not None: + name = builder.CreateString(self.name) + if self.clipLText is not None: + clipLText = builder.CreateString(self.clipLText) + if self.openClipGText is not None: + openClipGText = builder.CreateString(self.openClipGText) + if self.t5Text is not None: + t5Text = builder.CreateString(self.t5Text) + GenerationConfigurationStart(builder) + GenerationConfigurationAddId(builder, self.id) + GenerationConfigurationAddStartWidth(builder, self.startWidth) + GenerationConfigurationAddStartHeight(builder, self.startHeight) + GenerationConfigurationAddSeed(builder, self.seed) + GenerationConfigurationAddSteps(builder, self.steps) + GenerationConfigurationAddGuidanceScale(builder, self.guidanceScale) + GenerationConfigurationAddStrength(builder, self.strength) + if self.model is not None: + GenerationConfigurationAddModel(builder, model) + GenerationConfigurationAddSampler(builder, self.sampler) + GenerationConfigurationAddBatchCount(builder, self.batchCount) + GenerationConfigurationAddBatchSize(builder, self.batchSize) + GenerationConfigurationAddHiresFix(builder, self.hiresFix) + GenerationConfigurationAddHiresFixStartWidth(builder, self.hiresFixStartWidth) + GenerationConfigurationAddHiresFixStartHeight(builder, self.hiresFixStartHeight) + GenerationConfigurationAddHiresFixStrength(builder, self.hiresFixStrength) + if self.upscaler is not None: + GenerationConfigurationAddUpscaler(builder, upscaler) + GenerationConfigurationAddImageGuidanceScale(builder, self.imageGuidanceScale) + GenerationConfigurationAddSeedMode(builder, self.seedMode) + GenerationConfigurationAddClipSkip(builder, self.clipSkip) + if self.controls is not None: + GenerationConfigurationAddControls(builder, controls) + if self.loras is not None: + GenerationConfigurationAddLoras(builder, loras) + GenerationConfigurationAddMaskBlur(builder, self.maskBlur) + if self.faceRestoration is not None: + GenerationConfigurationAddFaceRestoration(builder, faceRestoration) + GenerationConfigurationAddClipWeight(builder, self.clipWeight) + GenerationConfigurationAddNegativePromptForImagePrior(builder, self.negativePromptForImagePrior) + GenerationConfigurationAddImagePriorSteps(builder, self.imagePriorSteps) + if self.refinerModel is not None: + GenerationConfigurationAddRefinerModel(builder, refinerModel) + GenerationConfigurationAddOriginalImageHeight(builder, self.originalImageHeight) + GenerationConfigurationAddOriginalImageWidth(builder, self.originalImageWidth) + GenerationConfigurationAddCropTop(builder, self.cropTop) + GenerationConfigurationAddCropLeft(builder, self.cropLeft) + GenerationConfigurationAddTargetImageHeight(builder, self.targetImageHeight) + GenerationConfigurationAddTargetImageWidth(builder, self.targetImageWidth) + GenerationConfigurationAddAestheticScore(builder, self.aestheticScore) + GenerationConfigurationAddNegativeAestheticScore(builder, self.negativeAestheticScore) + GenerationConfigurationAddZeroNegativePrompt(builder, self.zeroNegativePrompt) + GenerationConfigurationAddRefinerStart(builder, self.refinerStart) + GenerationConfigurationAddNegativeOriginalImageHeight(builder, self.negativeOriginalImageHeight) + GenerationConfigurationAddNegativeOriginalImageWidth(builder, self.negativeOriginalImageWidth) + if self.name is not None: + GenerationConfigurationAddName(builder, name) + GenerationConfigurationAddFpsId(builder, self.fpsId) + GenerationConfigurationAddMotionBucketId(builder, self.motionBucketId) + GenerationConfigurationAddCondAug(builder, self.condAug) + GenerationConfigurationAddStartFrameCfg(builder, self.startFrameCfg) + GenerationConfigurationAddNumFrames(builder, self.numFrames) + GenerationConfigurationAddMaskBlurOutset(builder, self.maskBlurOutset) + GenerationConfigurationAddSharpness(builder, self.sharpness) + GenerationConfigurationAddShift(builder, self.shift) + GenerationConfigurationAddStage2Steps(builder, self.stage2Steps) + GenerationConfigurationAddStage2Cfg(builder, self.stage2Cfg) + GenerationConfigurationAddStage2Shift(builder, self.stage2Shift) + GenerationConfigurationAddTiledDecoding(builder, self.tiledDecoding) + GenerationConfigurationAddDecodingTileWidth(builder, self.decodingTileWidth) + GenerationConfigurationAddDecodingTileHeight(builder, self.decodingTileHeight) + GenerationConfigurationAddDecodingTileOverlap(builder, self.decodingTileOverlap) + GenerationConfigurationAddStochasticSamplingGamma(builder, self.stochasticSamplingGamma) + GenerationConfigurationAddPreserveOriginalAfterInpaint(builder, self.preserveOriginalAfterInpaint) + GenerationConfigurationAddTiledDiffusion(builder, self.tiledDiffusion) + GenerationConfigurationAddDiffusionTileWidth(builder, self.diffusionTileWidth) + GenerationConfigurationAddDiffusionTileHeight(builder, self.diffusionTileHeight) + GenerationConfigurationAddDiffusionTileOverlap(builder, self.diffusionTileOverlap) + GenerationConfigurationAddUpscalerScaleFactor(builder, self.upscalerScaleFactor) + GenerationConfigurationAddT5TextEncoder(builder, self.t5TextEncoder) + GenerationConfigurationAddSeparateClipL(builder, self.separateClipL) + if self.clipLText is not None: + GenerationConfigurationAddClipLText(builder, clipLText) + GenerationConfigurationAddSeparateOpenClipG(builder, self.separateOpenClipG) + if self.openClipGText is not None: + GenerationConfigurationAddOpenClipGText(builder, openClipGText) + GenerationConfigurationAddSpeedUpWithGuidanceEmbed(builder, self.speedUpWithGuidanceEmbed) + GenerationConfigurationAddGuidanceEmbed(builder, self.guidanceEmbed) + GenerationConfigurationAddResolutionDependentShift(builder, self.resolutionDependentShift) + GenerationConfigurationAddTeaCacheStart(builder, self.teaCacheStart) + GenerationConfigurationAddTeaCacheEnd(builder, self.teaCacheEnd) + GenerationConfigurationAddTeaCacheThreshold(builder, self.teaCacheThreshold) + GenerationConfigurationAddTeaCache(builder, self.teaCache) + GenerationConfigurationAddSeparateT5(builder, self.separateT5) + if self.t5Text is not None: + GenerationConfigurationAddT5Text(builder, t5Text) + GenerationConfigurationAddTeaCacheMaxSkipSteps(builder, self.teaCacheMaxSkipSteps) + GenerationConfigurationAddCausalInferenceEnabled(builder, self.causalInferenceEnabled) + GenerationConfigurationAddCausalInference(builder, self.causalInference) + GenerationConfigurationAddCausalInferencePad(builder, self.causalInferencePad) + GenerationConfigurationAddCfgZeroStar(builder, self.cfgZeroStar) + GenerationConfigurationAddCfgZeroInitSteps(builder, self.cfgZeroInitSteps) + GenerationConfigurationAddCompressionArtifacts(builder, self.compressionArtifacts) + GenerationConfigurationAddCompressionArtifactsQuality(builder, self.compressionArtifactsQuality) + GenerationConfigurationAddColorCalibration(builder, self.colorCalibration) + generationConfiguration = GenerationConfigurationEnd(builder) + return generationConfiguration + + diff --git a/CLI/macos/drawthings/generated/config_generated.pyi b/CLI/macos/drawthings/generated/config_generated.pyi new file mode 100644 index 000000000..4569c15f4 --- /dev/null +++ b/CLI/macos/drawthings/generated/config_generated.pyi @@ -0,0 +1,534 @@ +from __future__ import annotations + +import flatbuffers +import numpy as np + +import typing +from typing import cast + +uoffset: typing.TypeAlias = flatbuffers.number_types.UOffsetTFlags.py_type + +class SamplerType(object): + DPMPP2MKarras = cast(int, ...) + EulerA = cast(int, ...) + DDIM = cast(int, ...) + PLMS = cast(int, ...) + DPMPPSDEKarras = cast(int, ...) + UniPC = cast(int, ...) + LCM = cast(int, ...) + EulerASubstep = cast(int, ...) + DPMPPSDESubstep = cast(int, ...) + TCD = cast(int, ...) + EulerATrailing = cast(int, ...) + DPMPPSDETrailing = cast(int, ...) + DPMPP2MAYS = cast(int, ...) + EulerAAYS = cast(int, ...) + DPMPPSDEAYS = cast(int, ...) + DPMPP2MTrailing = cast(int, ...) + DDIMTrailing = cast(int, ...) + UniPCTrailing = cast(int, ...) + UniPCAYS = cast(int, ...) + TCDTrailing = cast(int, ...) +class SeedMode(object): + Legacy = cast(int, ...) + TorchCpuCompatible = cast(int, ...) + ScaleAlike = cast(int, ...) + NvidiaGpuCompatible = cast(int, ...) +class ControlMode(object): + Balanced = cast(int, ...) + Prompt = cast(int, ...) + Control = cast(int, ...) +class ControlInputType(object): + Unspecified = cast(int, ...) + Custom = cast(int, ...) + Depth = cast(int, ...) + Canny = cast(int, ...) + Scribble = cast(int, ...) + Pose = cast(int, ...) + Normalbae = cast(int, ...) + Color = cast(int, ...) + Lineart = cast(int, ...) + Softedge = cast(int, ...) + Seg = cast(int, ...) + Inpaint = cast(int, ...) + Ip2p = cast(int, ...) + Shuffle = cast(int, ...) + Mlsd = cast(int, ...) + Tile = cast(int, ...) + Blur = cast(int, ...) + Lowquality = cast(int, ...) + Gray = cast(int, ...) +class LoRAMode(object): + All = cast(int, ...) + Base = cast(int, ...) + Refiner = cast(int, ...) +class CompressionMethod(object): + Disabled = cast(int, ...) + H264 = cast(int, ...) + H265 = cast(int, ...) + Jpeg = cast(int, ...) +class ColorCalibration(object): + Disabled = cast(int, ...) + Lab = cast(int, ...) +class Control(object): + @classmethod + def GetRootAs(cls, buf: bytes, offset: int) -> Control: ... + @classmethod + def GetRootAsControl(cls, buf: bytes, offset: int) -> Control: ... + def Init(self, buf: bytes, pos: int) -> None: ... + def File(self) -> str | None: ... + def Weight(self) -> float: ... + def GuidanceStart(self) -> float: ... + def GuidanceEnd(self) -> float: ... + def NoPrompt(self) -> bool: ... + def GlobalAveragePooling(self) -> bool: ... + def DownSamplingRate(self) -> float: ... + def ControlMode(self) -> typing.Literal[ControlMode.Balanced, ControlMode.Prompt, ControlMode.Control]: ... + def TargetBlocks(self, i: int) -> str: ... + def TargetBlocksLength(self) -> int: ... + def TargetBlocksIsNone(self) -> bool: ... + def InputOverride(self) -> typing.Literal[ControlInputType.Unspecified, ControlInputType.Custom, ControlInputType.Depth, ControlInputType.Canny, ControlInputType.Scribble, ControlInputType.Pose, ControlInputType.Normalbae, ControlInputType.Color, ControlInputType.Lineart, ControlInputType.Softedge, ControlInputType.Seg, ControlInputType.Inpaint, ControlInputType.Ip2p, ControlInputType.Shuffle, ControlInputType.Mlsd, ControlInputType.Tile, ControlInputType.Blur, ControlInputType.Lowquality, ControlInputType.Gray]: ... +class ControlT(object): + file: str | None + weight: float + guidanceStart: float + guidanceEnd: float + noPrompt: bool + globalAveragePooling: bool + downSamplingRate: float + controlMode: typing.Literal[ControlMode.Balanced, ControlMode.Prompt, ControlMode.Control] + targetBlocks: typing.List[str] + inputOverride: typing.Literal[ControlInputType.Unspecified, ControlInputType.Custom, ControlInputType.Depth, ControlInputType.Canny, ControlInputType.Scribble, ControlInputType.Pose, ControlInputType.Normalbae, ControlInputType.Color, ControlInputType.Lineart, ControlInputType.Softedge, ControlInputType.Seg, ControlInputType.Inpaint, ControlInputType.Ip2p, ControlInputType.Shuffle, ControlInputType.Mlsd, ControlInputType.Tile, ControlInputType.Blur, ControlInputType.Lowquality, ControlInputType.Gray] + def __init__( + self, + file: str | None = ..., + weight: float = ..., + guidanceStart: float = ..., + guidanceEnd: float = ..., + noPrompt: bool = ..., + globalAveragePooling: bool = ..., + downSamplingRate: float = ..., + controlMode: typing.Literal[ControlMode.Balanced, ControlMode.Prompt, ControlMode.Control] = ..., + targetBlocks: typing.List[str] | None = ..., + inputOverride: typing.Literal[ControlInputType.Unspecified, ControlInputType.Custom, ControlInputType.Depth, ControlInputType.Canny, ControlInputType.Scribble, ControlInputType.Pose, ControlInputType.Normalbae, ControlInputType.Color, ControlInputType.Lineart, ControlInputType.Softedge, ControlInputType.Seg, ControlInputType.Inpaint, ControlInputType.Ip2p, ControlInputType.Shuffle, ControlInputType.Mlsd, ControlInputType.Tile, ControlInputType.Blur, ControlInputType.Lowquality, ControlInputType.Gray] = ..., + ) -> None: ... + @classmethod + def InitFromBuf(cls, buf: bytes, pos: int) -> ControlT: ... + @classmethod + def InitFromPackedBuf(cls, buf: bytes, pos: int = 0) -> ControlT: ... + @classmethod + def InitFromObj(cls, control: Control) -> ControlT: ... + def _UnPack(self, control: Control) -> None: ... + def Pack(self, builder: flatbuffers.Builder) -> None: ... +def ControlStart(builder: flatbuffers.Builder) -> None: ... +def ControlAddFile(builder: flatbuffers.Builder, file: uoffset) -> None: ... +def ControlAddWeight(builder: flatbuffers.Builder, weight: float) -> None: ... +def ControlAddGuidanceStart(builder: flatbuffers.Builder, guidanceStart: float) -> None: ... +def ControlAddGuidanceEnd(builder: flatbuffers.Builder, guidanceEnd: float) -> None: ... +def ControlAddNoPrompt(builder: flatbuffers.Builder, noPrompt: bool) -> None: ... +def ControlAddGlobalAveragePooling(builder: flatbuffers.Builder, globalAveragePooling: bool) -> None: ... +def ControlAddDownSamplingRate(builder: flatbuffers.Builder, downSamplingRate: float) -> None: ... +def ControlAddControlMode(builder: flatbuffers.Builder, controlMode: typing.Literal[ControlMode.Balanced, ControlMode.Prompt, ControlMode.Control]) -> None: ... +def ControlAddTargetBlocks(builder: flatbuffers.Builder, targetBlocks: uoffset) -> None: ... +def ControlStartTargetBlocksVector(builder: flatbuffers.Builder, num_elems: int) -> uoffset: ... +def ControlAddInputOverride(builder: flatbuffers.Builder, inputOverride: typing.Literal[ControlInputType.Unspecified, ControlInputType.Custom, ControlInputType.Depth, ControlInputType.Canny, ControlInputType.Scribble, ControlInputType.Pose, ControlInputType.Normalbae, ControlInputType.Color, ControlInputType.Lineart, ControlInputType.Softedge, ControlInputType.Seg, ControlInputType.Inpaint, ControlInputType.Ip2p, ControlInputType.Shuffle, ControlInputType.Mlsd, ControlInputType.Tile, ControlInputType.Blur, ControlInputType.Lowquality, ControlInputType.Gray]) -> None: ... +def ControlEnd(builder: flatbuffers.Builder) -> uoffset: ... +class LoRA(object): + @classmethod + def GetRootAs(cls, buf: bytes, offset: int) -> LoRA: ... + @classmethod + def GetRootAsLoRA(cls, buf: bytes, offset: int) -> LoRA: ... + def Init(self, buf: bytes, pos: int) -> None: ... + def File(self) -> str | None: ... + def Weight(self) -> float: ... + def Mode(self) -> typing.Literal[LoRAMode.All, LoRAMode.Base, LoRAMode.Refiner]: ... +class LoRAT(object): + file: str | None + weight: float + mode: typing.Literal[LoRAMode.All, LoRAMode.Base, LoRAMode.Refiner] + def __init__( + self, + file: str | None = ..., + weight: float = ..., + mode: typing.Literal[LoRAMode.All, LoRAMode.Base, LoRAMode.Refiner] = ..., + ) -> None: ... + @classmethod + def InitFromBuf(cls, buf: bytes, pos: int) -> LoRAT: ... + @classmethod + def InitFromPackedBuf(cls, buf: bytes, pos: int = 0) -> LoRAT: ... + @classmethod + def InitFromObj(cls, loRa: LoRA) -> LoRAT: ... + def _UnPack(self, loRa: LoRA) -> None: ... + def Pack(self, builder: flatbuffers.Builder) -> None: ... +def LoRAStart(builder: flatbuffers.Builder) -> None: ... +def LoRAAddFile(builder: flatbuffers.Builder, file: uoffset) -> None: ... +def LoRAAddWeight(builder: flatbuffers.Builder, weight: float) -> None: ... +def LoRAAddMode(builder: flatbuffers.Builder, mode: typing.Literal[LoRAMode.All, LoRAMode.Base, LoRAMode.Refiner]) -> None: ... +def LoRAEnd(builder: flatbuffers.Builder) -> uoffset: ... +class GenerationConfiguration(object): + @classmethod + def GetRootAs(cls, buf: bytes, offset: int) -> GenerationConfiguration: ... + @classmethod + def GetRootAsGenerationConfiguration(cls, buf: bytes, offset: int) -> GenerationConfiguration: ... + def Init(self, buf: bytes, pos: int) -> None: ... + def Id(self) -> int: ... + def StartWidth(self) -> int: ... + def StartHeight(self) -> int: ... + def Seed(self) -> int: ... + def Steps(self) -> int: ... + def GuidanceScale(self) -> float: ... + def Strength(self) -> float: ... + def Model(self) -> str | None: ... + def Sampler(self) -> typing.Literal[SamplerType.DPMPP2MKarras, SamplerType.EulerA, SamplerType.DDIM, SamplerType.PLMS, SamplerType.DPMPPSDEKarras, SamplerType.UniPC, SamplerType.LCM, SamplerType.EulerASubstep, SamplerType.DPMPPSDESubstep, SamplerType.TCD, SamplerType.EulerATrailing, SamplerType.DPMPPSDETrailing, SamplerType.DPMPP2MAYS, SamplerType.EulerAAYS, SamplerType.DPMPPSDEAYS, SamplerType.DPMPP2MTrailing, SamplerType.DDIMTrailing, SamplerType.UniPCTrailing, SamplerType.UniPCAYS, SamplerType.TCDTrailing]: ... + def BatchCount(self) -> int: ... + def BatchSize(self) -> int: ... + def HiresFix(self) -> bool: ... + def HiresFixStartWidth(self) -> int: ... + def HiresFixStartHeight(self) -> int: ... + def HiresFixStrength(self) -> float: ... + def Upscaler(self) -> str | None: ... + def ImageGuidanceScale(self) -> float: ... + def SeedMode(self) -> typing.Literal[SeedMode.Legacy, SeedMode.TorchCpuCompatible, SeedMode.ScaleAlike, SeedMode.NvidiaGpuCompatible]: ... + def ClipSkip(self) -> int: ... + def Controls(self, i: int) -> Control | None: ... + def ControlsLength(self) -> int: ... + def ControlsIsNone(self) -> bool: ... + def Loras(self, i: int) -> LoRA | None: ... + def LorasLength(self) -> int: ... + def LorasIsNone(self) -> bool: ... + def MaskBlur(self) -> float: ... + def FaceRestoration(self) -> str | None: ... + def ClipWeight(self) -> float: ... + def NegativePromptForImagePrior(self) -> bool: ... + def ImagePriorSteps(self) -> int: ... + def RefinerModel(self) -> str | None: ... + def OriginalImageHeight(self) -> int: ... + def OriginalImageWidth(self) -> int: ... + def CropTop(self) -> int: ... + def CropLeft(self) -> int: ... + def TargetImageHeight(self) -> int: ... + def TargetImageWidth(self) -> int: ... + def AestheticScore(self) -> float: ... + def NegativeAestheticScore(self) -> float: ... + def ZeroNegativePrompt(self) -> bool: ... + def RefinerStart(self) -> float: ... + def NegativeOriginalImageHeight(self) -> int: ... + def NegativeOriginalImageWidth(self) -> int: ... + def Name(self) -> str | None: ... + def FpsId(self) -> int: ... + def MotionBucketId(self) -> int: ... + def CondAug(self) -> float: ... + def StartFrameCfg(self) -> float: ... + def NumFrames(self) -> int: ... + def MaskBlurOutset(self) -> int: ... + def Sharpness(self) -> float: ... + def Shift(self) -> float: ... + def Stage2Steps(self) -> int: ... + def Stage2Cfg(self) -> float: ... + def Stage2Shift(self) -> float: ... + def TiledDecoding(self) -> bool: ... + def DecodingTileWidth(self) -> int: ... + def DecodingTileHeight(self) -> int: ... + def DecodingTileOverlap(self) -> int: ... + def StochasticSamplingGamma(self) -> float: ... + def PreserveOriginalAfterInpaint(self) -> bool: ... + def TiledDiffusion(self) -> bool: ... + def DiffusionTileWidth(self) -> int: ... + def DiffusionTileHeight(self) -> int: ... + def DiffusionTileOverlap(self) -> int: ... + def UpscalerScaleFactor(self) -> int: ... + def T5TextEncoder(self) -> bool: ... + def SeparateClipL(self) -> bool: ... + def ClipLText(self) -> str | None: ... + def SeparateOpenClipG(self) -> bool: ... + def OpenClipGText(self) -> str | None: ... + def SpeedUpWithGuidanceEmbed(self) -> bool: ... + def GuidanceEmbed(self) -> float: ... + def ResolutionDependentShift(self) -> bool: ... + def TeaCacheStart(self) -> int: ... + def TeaCacheEnd(self) -> int: ... + def TeaCacheThreshold(self) -> float: ... + def TeaCache(self) -> bool: ... + def SeparateT5(self) -> bool: ... + def T5Text(self) -> str | None: ... + def TeaCacheMaxSkipSteps(self) -> int: ... + def CausalInferenceEnabled(self) -> bool: ... + def CausalInference(self) -> int: ... + def CausalInferencePad(self) -> int: ... + def CfgZeroStar(self) -> bool: ... + def CfgZeroInitSteps(self) -> int: ... + def CompressionArtifacts(self) -> typing.Literal[CompressionMethod.Disabled, CompressionMethod.H264, CompressionMethod.H265, CompressionMethod.Jpeg]: ... + def CompressionArtifactsQuality(self) -> float: ... + def ColorCalibration(self) -> typing.Literal[ColorCalibration.Disabled, ColorCalibration.Lab]: ... +class GenerationConfigurationT(object): + id: int + startWidth: int + startHeight: int + seed: int + steps: int + guidanceScale: float + strength: float + model: str | None + sampler: typing.Literal[SamplerType.DPMPP2MKarras, SamplerType.EulerA, SamplerType.DDIM, SamplerType.PLMS, SamplerType.DPMPPSDEKarras, SamplerType.UniPC, SamplerType.LCM, SamplerType.EulerASubstep, SamplerType.DPMPPSDESubstep, SamplerType.TCD, SamplerType.EulerATrailing, SamplerType.DPMPPSDETrailing, SamplerType.DPMPP2MAYS, SamplerType.EulerAAYS, SamplerType.DPMPPSDEAYS, SamplerType.DPMPP2MTrailing, SamplerType.DDIMTrailing, SamplerType.UniPCTrailing, SamplerType.UniPCAYS, SamplerType.TCDTrailing] + batchCount: int + batchSize: int + hiresFix: bool + hiresFixStartWidth: int + hiresFixStartHeight: int + hiresFixStrength: float + upscaler: str | None + imageGuidanceScale: float + seedMode: typing.Literal[SeedMode.Legacy, SeedMode.TorchCpuCompatible, SeedMode.ScaleAlike, SeedMode.NvidiaGpuCompatible] + clipSkip: int + controls: typing.List[ControlT] + loras: typing.List[LoRAT] + maskBlur: float + faceRestoration: str | None + clipWeight: float + negativePromptForImagePrior: bool + imagePriorSteps: int + refinerModel: str | None + originalImageHeight: int + originalImageWidth: int + cropTop: int + cropLeft: int + targetImageHeight: int + targetImageWidth: int + aestheticScore: float + negativeAestheticScore: float + zeroNegativePrompt: bool + refinerStart: float + negativeOriginalImageHeight: int + negativeOriginalImageWidth: int + name: str | None + fpsId: int + motionBucketId: int + condAug: float + startFrameCfg: float + numFrames: int + maskBlurOutset: int + sharpness: float + shift: float + stage2Steps: int + stage2Cfg: float + stage2Shift: float + tiledDecoding: bool + decodingTileWidth: int + decodingTileHeight: int + decodingTileOverlap: int + stochasticSamplingGamma: float + preserveOriginalAfterInpaint: bool + tiledDiffusion: bool + diffusionTileWidth: int + diffusionTileHeight: int + diffusionTileOverlap: int + upscalerScaleFactor: int + t5TextEncoder: bool + separateClipL: bool + clipLText: str | None + separateOpenClipG: bool + openClipGText: str | None + speedUpWithGuidanceEmbed: bool + guidanceEmbed: float + resolutionDependentShift: bool + teaCacheStart: int + teaCacheEnd: int + teaCacheThreshold: float + teaCache: bool + separateT5: bool + t5Text: str | None + teaCacheMaxSkipSteps: int + causalInferenceEnabled: bool + causalInference: int + causalInferencePad: int + cfgZeroStar: bool + cfgZeroInitSteps: int + compressionArtifacts: typing.Literal[CompressionMethod.Disabled, CompressionMethod.H264, CompressionMethod.H265, CompressionMethod.Jpeg] + compressionArtifactsQuality: float + colorCalibration: typing.Literal[ColorCalibration.Disabled, ColorCalibration.Lab] + def __init__( + self, + id: int = ..., + startWidth: int = ..., + startHeight: int = ..., + seed: int = ..., + steps: int = ..., + guidanceScale: float = ..., + strength: float = ..., + model: str | None = ..., + sampler: typing.Literal[SamplerType.DPMPP2MKarras, SamplerType.EulerA, SamplerType.DDIM, SamplerType.PLMS, SamplerType.DPMPPSDEKarras, SamplerType.UniPC, SamplerType.LCM, SamplerType.EulerASubstep, SamplerType.DPMPPSDESubstep, SamplerType.TCD, SamplerType.EulerATrailing, SamplerType.DPMPPSDETrailing, SamplerType.DPMPP2MAYS, SamplerType.EulerAAYS, SamplerType.DPMPPSDEAYS, SamplerType.DPMPP2MTrailing, SamplerType.DDIMTrailing, SamplerType.UniPCTrailing, SamplerType.UniPCAYS, SamplerType.TCDTrailing] = ..., + batchCount: int = ..., + batchSize: int = ..., + hiresFix: bool = ..., + hiresFixStartWidth: int = ..., + hiresFixStartHeight: int = ..., + hiresFixStrength: float = ..., + upscaler: str | None = ..., + imageGuidanceScale: float = ..., + seedMode: typing.Literal[SeedMode.Legacy, SeedMode.TorchCpuCompatible, SeedMode.ScaleAlike, SeedMode.NvidiaGpuCompatible] = ..., + clipSkip: int = ..., + controls: typing.List['ControlT'] | None = ..., + loras: typing.List['LoRAT'] | None = ..., + maskBlur: float = ..., + faceRestoration: str | None = ..., + clipWeight: float = ..., + negativePromptForImagePrior: bool = ..., + imagePriorSteps: int = ..., + refinerModel: str | None = ..., + originalImageHeight: int = ..., + originalImageWidth: int = ..., + cropTop: int = ..., + cropLeft: int = ..., + targetImageHeight: int = ..., + targetImageWidth: int = ..., + aestheticScore: float = ..., + negativeAestheticScore: float = ..., + zeroNegativePrompt: bool = ..., + refinerStart: float = ..., + negativeOriginalImageHeight: int = ..., + negativeOriginalImageWidth: int = ..., + name: str | None = ..., + fpsId: int = ..., + motionBucketId: int = ..., + condAug: float = ..., + startFrameCfg: float = ..., + numFrames: int = ..., + maskBlurOutset: int = ..., + sharpness: float = ..., + shift: float = ..., + stage2Steps: int = ..., + stage2Cfg: float = ..., + stage2Shift: float = ..., + tiledDecoding: bool = ..., + decodingTileWidth: int = ..., + decodingTileHeight: int = ..., + decodingTileOverlap: int = ..., + stochasticSamplingGamma: float = ..., + preserveOriginalAfterInpaint: bool = ..., + tiledDiffusion: bool = ..., + diffusionTileWidth: int = ..., + diffusionTileHeight: int = ..., + diffusionTileOverlap: int = ..., + upscalerScaleFactor: int = ..., + t5TextEncoder: bool = ..., + separateClipL: bool = ..., + clipLText: str | None = ..., + separateOpenClipG: bool = ..., + openClipGText: str | None = ..., + speedUpWithGuidanceEmbed: bool = ..., + guidanceEmbed: float = ..., + resolutionDependentShift: bool = ..., + teaCacheStart: int = ..., + teaCacheEnd: int = ..., + teaCacheThreshold: float = ..., + teaCache: bool = ..., + separateT5: bool = ..., + t5Text: str | None = ..., + teaCacheMaxSkipSteps: int = ..., + causalInferenceEnabled: bool = ..., + causalInference: int = ..., + causalInferencePad: int = ..., + cfgZeroStar: bool = ..., + cfgZeroInitSteps: int = ..., + compressionArtifacts: typing.Literal[CompressionMethod.Disabled, CompressionMethod.H264, CompressionMethod.H265, CompressionMethod.Jpeg] = ..., + compressionArtifactsQuality: float = ..., + colorCalibration: typing.Literal[ColorCalibration.Disabled, ColorCalibration.Lab] = ..., + ) -> None: ... + @classmethod + def InitFromBuf(cls, buf: bytes, pos: int) -> GenerationConfigurationT: ... + @classmethod + def InitFromPackedBuf(cls, buf: bytes, pos: int = 0) -> GenerationConfigurationT: ... + @classmethod + def InitFromObj(cls, generationConfiguration: GenerationConfiguration) -> GenerationConfigurationT: ... + def _UnPack(self, generationConfiguration: GenerationConfiguration) -> None: ... + def Pack(self, builder: flatbuffers.Builder) -> None: ... +def GenerationConfigurationStart(builder: flatbuffers.Builder) -> None: ... +def GenerationConfigurationAddId(builder: flatbuffers.Builder, id: int) -> None: ... +def GenerationConfigurationAddStartWidth(builder: flatbuffers.Builder, startWidth: int) -> None: ... +def GenerationConfigurationAddStartHeight(builder: flatbuffers.Builder, startHeight: int) -> None: ... +def GenerationConfigurationAddSeed(builder: flatbuffers.Builder, seed: int) -> None: ... +def GenerationConfigurationAddSteps(builder: flatbuffers.Builder, steps: int) -> None: ... +def GenerationConfigurationAddGuidanceScale(builder: flatbuffers.Builder, guidanceScale: float) -> None: ... +def GenerationConfigurationAddStrength(builder: flatbuffers.Builder, strength: float) -> None: ... +def GenerationConfigurationAddModel(builder: flatbuffers.Builder, model: uoffset) -> None: ... +def GenerationConfigurationAddSampler(builder: flatbuffers.Builder, sampler: typing.Literal[SamplerType.DPMPP2MKarras, SamplerType.EulerA, SamplerType.DDIM, SamplerType.PLMS, SamplerType.DPMPPSDEKarras, SamplerType.UniPC, SamplerType.LCM, SamplerType.EulerASubstep, SamplerType.DPMPPSDESubstep, SamplerType.TCD, SamplerType.EulerATrailing, SamplerType.DPMPPSDETrailing, SamplerType.DPMPP2MAYS, SamplerType.EulerAAYS, SamplerType.DPMPPSDEAYS, SamplerType.DPMPP2MTrailing, SamplerType.DDIMTrailing, SamplerType.UniPCTrailing, SamplerType.UniPCAYS, SamplerType.TCDTrailing]) -> None: ... +def GenerationConfigurationAddBatchCount(builder: flatbuffers.Builder, batchCount: int) -> None: ... +def GenerationConfigurationAddBatchSize(builder: flatbuffers.Builder, batchSize: int) -> None: ... +def GenerationConfigurationAddHiresFix(builder: flatbuffers.Builder, hiresFix: bool) -> None: ... +def GenerationConfigurationAddHiresFixStartWidth(builder: flatbuffers.Builder, hiresFixStartWidth: int) -> None: ... +def GenerationConfigurationAddHiresFixStartHeight(builder: flatbuffers.Builder, hiresFixStartHeight: int) -> None: ... +def GenerationConfigurationAddHiresFixStrength(builder: flatbuffers.Builder, hiresFixStrength: float) -> None: ... +def GenerationConfigurationAddUpscaler(builder: flatbuffers.Builder, upscaler: uoffset) -> None: ... +def GenerationConfigurationAddImageGuidanceScale(builder: flatbuffers.Builder, imageGuidanceScale: float) -> None: ... +def GenerationConfigurationAddSeedMode(builder: flatbuffers.Builder, seedMode: typing.Literal[SeedMode.Legacy, SeedMode.TorchCpuCompatible, SeedMode.ScaleAlike, SeedMode.NvidiaGpuCompatible]) -> None: ... +def GenerationConfigurationAddClipSkip(builder: flatbuffers.Builder, clipSkip: int) -> None: ... +def GenerationConfigurationAddControls(builder: flatbuffers.Builder, controls: uoffset) -> None: ... +def GenerationConfigurationStartControlsVector(builder: flatbuffers.Builder, num_elems: int) -> uoffset: ... +def GenerationConfigurationAddLoras(builder: flatbuffers.Builder, loras: uoffset) -> None: ... +def GenerationConfigurationStartLorasVector(builder: flatbuffers.Builder, num_elems: int) -> uoffset: ... +def GenerationConfigurationAddMaskBlur(builder: flatbuffers.Builder, maskBlur: float) -> None: ... +def GenerationConfigurationAddFaceRestoration(builder: flatbuffers.Builder, faceRestoration: uoffset) -> None: ... +def GenerationConfigurationAddClipWeight(builder: flatbuffers.Builder, clipWeight: float) -> None: ... +def GenerationConfigurationAddNegativePromptForImagePrior(builder: flatbuffers.Builder, negativePromptForImagePrior: bool) -> None: ... +def GenerationConfigurationAddImagePriorSteps(builder: flatbuffers.Builder, imagePriorSteps: int) -> None: ... +def GenerationConfigurationAddRefinerModel(builder: flatbuffers.Builder, refinerModel: uoffset) -> None: ... +def GenerationConfigurationAddOriginalImageHeight(builder: flatbuffers.Builder, originalImageHeight: int) -> None: ... +def GenerationConfigurationAddOriginalImageWidth(builder: flatbuffers.Builder, originalImageWidth: int) -> None: ... +def GenerationConfigurationAddCropTop(builder: flatbuffers.Builder, cropTop: int) -> None: ... +def GenerationConfigurationAddCropLeft(builder: flatbuffers.Builder, cropLeft: int) -> None: ... +def GenerationConfigurationAddTargetImageHeight(builder: flatbuffers.Builder, targetImageHeight: int) -> None: ... +def GenerationConfigurationAddTargetImageWidth(builder: flatbuffers.Builder, targetImageWidth: int) -> None: ... +def GenerationConfigurationAddAestheticScore(builder: flatbuffers.Builder, aestheticScore: float) -> None: ... +def GenerationConfigurationAddNegativeAestheticScore(builder: flatbuffers.Builder, negativeAestheticScore: float) -> None: ... +def GenerationConfigurationAddZeroNegativePrompt(builder: flatbuffers.Builder, zeroNegativePrompt: bool) -> None: ... +def GenerationConfigurationAddRefinerStart(builder: flatbuffers.Builder, refinerStart: float) -> None: ... +def GenerationConfigurationAddNegativeOriginalImageHeight(builder: flatbuffers.Builder, negativeOriginalImageHeight: int) -> None: ... +def GenerationConfigurationAddNegativeOriginalImageWidth(builder: flatbuffers.Builder, negativeOriginalImageWidth: int) -> None: ... +def GenerationConfigurationAddName(builder: flatbuffers.Builder, name: uoffset) -> None: ... +def GenerationConfigurationAddFpsId(builder: flatbuffers.Builder, fpsId: int) -> None: ... +def GenerationConfigurationAddMotionBucketId(builder: flatbuffers.Builder, motionBucketId: int) -> None: ... +def GenerationConfigurationAddCondAug(builder: flatbuffers.Builder, condAug: float) -> None: ... +def GenerationConfigurationAddStartFrameCfg(builder: flatbuffers.Builder, startFrameCfg: float) -> None: ... +def GenerationConfigurationAddNumFrames(builder: flatbuffers.Builder, numFrames: int) -> None: ... +def GenerationConfigurationAddMaskBlurOutset(builder: flatbuffers.Builder, maskBlurOutset: int) -> None: ... +def GenerationConfigurationAddSharpness(builder: flatbuffers.Builder, sharpness: float) -> None: ... +def GenerationConfigurationAddShift(builder: flatbuffers.Builder, shift: float) -> None: ... +def GenerationConfigurationAddStage2Steps(builder: flatbuffers.Builder, stage2Steps: int) -> None: ... +def GenerationConfigurationAddStage2Cfg(builder: flatbuffers.Builder, stage2Cfg: float) -> None: ... +def GenerationConfigurationAddStage2Shift(builder: flatbuffers.Builder, stage2Shift: float) -> None: ... +def GenerationConfigurationAddTiledDecoding(builder: flatbuffers.Builder, tiledDecoding: bool) -> None: ... +def GenerationConfigurationAddDecodingTileWidth(builder: flatbuffers.Builder, decodingTileWidth: int) -> None: ... +def GenerationConfigurationAddDecodingTileHeight(builder: flatbuffers.Builder, decodingTileHeight: int) -> None: ... +def GenerationConfigurationAddDecodingTileOverlap(builder: flatbuffers.Builder, decodingTileOverlap: int) -> None: ... +def GenerationConfigurationAddStochasticSamplingGamma(builder: flatbuffers.Builder, stochasticSamplingGamma: float) -> None: ... +def GenerationConfigurationAddPreserveOriginalAfterInpaint(builder: flatbuffers.Builder, preserveOriginalAfterInpaint: bool) -> None: ... +def GenerationConfigurationAddTiledDiffusion(builder: flatbuffers.Builder, tiledDiffusion: bool) -> None: ... +def GenerationConfigurationAddDiffusionTileWidth(builder: flatbuffers.Builder, diffusionTileWidth: int) -> None: ... +def GenerationConfigurationAddDiffusionTileHeight(builder: flatbuffers.Builder, diffusionTileHeight: int) -> None: ... +def GenerationConfigurationAddDiffusionTileOverlap(builder: flatbuffers.Builder, diffusionTileOverlap: int) -> None: ... +def GenerationConfigurationAddUpscalerScaleFactor(builder: flatbuffers.Builder, upscalerScaleFactor: int) -> None: ... +def GenerationConfigurationAddT5TextEncoder(builder: flatbuffers.Builder, t5TextEncoder: bool) -> None: ... +def GenerationConfigurationAddSeparateClipL(builder: flatbuffers.Builder, separateClipL: bool) -> None: ... +def GenerationConfigurationAddClipLText(builder: flatbuffers.Builder, clipLText: uoffset) -> None: ... +def GenerationConfigurationAddSeparateOpenClipG(builder: flatbuffers.Builder, separateOpenClipG: bool) -> None: ... +def GenerationConfigurationAddOpenClipGText(builder: flatbuffers.Builder, openClipGText: uoffset) -> None: ... +def GenerationConfigurationAddSpeedUpWithGuidanceEmbed(builder: flatbuffers.Builder, speedUpWithGuidanceEmbed: bool) -> None: ... +def GenerationConfigurationAddGuidanceEmbed(builder: flatbuffers.Builder, guidanceEmbed: float) -> None: ... +def GenerationConfigurationAddResolutionDependentShift(builder: flatbuffers.Builder, resolutionDependentShift: bool) -> None: ... +def GenerationConfigurationAddTeaCacheStart(builder: flatbuffers.Builder, teaCacheStart: int) -> None: ... +def GenerationConfigurationAddTeaCacheEnd(builder: flatbuffers.Builder, teaCacheEnd: int) -> None: ... +def GenerationConfigurationAddTeaCacheThreshold(builder: flatbuffers.Builder, teaCacheThreshold: float) -> None: ... +def GenerationConfigurationAddTeaCache(builder: flatbuffers.Builder, teaCache: bool) -> None: ... +def GenerationConfigurationAddSeparateT5(builder: flatbuffers.Builder, separateT5: bool) -> None: ... +def GenerationConfigurationAddT5Text(builder: flatbuffers.Builder, t5Text: uoffset) -> None: ... +def GenerationConfigurationAddTeaCacheMaxSkipSteps(builder: flatbuffers.Builder, teaCacheMaxSkipSteps: int) -> None: ... +def GenerationConfigurationAddCausalInferenceEnabled(builder: flatbuffers.Builder, causalInferenceEnabled: bool) -> None: ... +def GenerationConfigurationAddCausalInference(builder: flatbuffers.Builder, causalInference: int) -> None: ... +def GenerationConfigurationAddCausalInferencePad(builder: flatbuffers.Builder, causalInferencePad: int) -> None: ... +def GenerationConfigurationAddCfgZeroStar(builder: flatbuffers.Builder, cfgZeroStar: bool) -> None: ... +def GenerationConfigurationAddCfgZeroInitSteps(builder: flatbuffers.Builder, cfgZeroInitSteps: int) -> None: ... +def GenerationConfigurationAddCompressionArtifacts(builder: flatbuffers.Builder, compressionArtifacts: typing.Literal[CompressionMethod.Disabled, CompressionMethod.H264, CompressionMethod.H265, CompressionMethod.Jpeg]) -> None: ... +def GenerationConfigurationAddCompressionArtifactsQuality(builder: flatbuffers.Builder, compressionArtifactsQuality: float) -> None: ... +def GenerationConfigurationAddColorCalibration(builder: flatbuffers.Builder, colorCalibration: typing.Literal[ColorCalibration.Disabled, ColorCalibration.Lab]) -> None: ... +def GenerationConfigurationEnd(builder: flatbuffers.Builder) -> uoffset: ... + diff --git a/CLI/macos/drawthings/generated/imageService_pb2.py b/CLI/macos/drawthings/generated/imageService_pb2.py new file mode 100644 index 000000000..0c77dbba8 --- /dev/null +++ b/CLI/macos/drawthings/generated/imageService_pb2.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: imageService.proto +# Protobuf Python Version: 5.29.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 5, + 29, + 0, + '', + 'imageService.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12imageService.proto\"G\n\x0b\x45\x63hoRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x0csharedSecret\x18\x02 \x01(\tH\x00\x88\x01\x01\x42\x0f\n\r_sharedSecret\"I\n\x14\x43omputeUnitThreshold\x12\x11\n\tcommunity\x18\x01 \x01(\x01\x12\x0c\n\x04plus\x18\x02 \x01(\x01\x12\x10\n\x08\x65xpireAt\x18\x03 \x01(\x03\"\xd8\x01\n\tEchoReply\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\r\n\x05\x66iles\x18\x02 \x03(\t\x12(\n\x08override\x18\x03 \x01(\x0b\x32\x11.MetadataOverrideH\x00\x88\x01\x01\x12\x1b\n\x13sharedSecretMissing\x18\x04 \x01(\x08\x12.\n\nthresholds\x18\x05 \x01(\x0b\x32\x15.ComputeUnitThresholdH\x01\x88\x01\x01\x12\x18\n\x10serverIdentifier\x18\x06 \x01(\x04\x42\x0b\n\t_overrideB\r\n\x0b_thresholds\"c\n\x0f\x46ileListRequest\x12\r\n\x05\x66iles\x18\x01 \x03(\t\x12\x15\n\rfilesWithHash\x18\x02 \x03(\t\x12\x19\n\x0csharedSecret\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0f\n\r_sharedSecret\"J\n\x15\x46ileExistenceResponse\x12\r\n\x05\x66iles\x18\x01 \x03(\t\x12\x12\n\nexistences\x18\x02 \x03(\x08\x12\x0e\n\x06hashes\x18\x03 \x03(\x0c\"t\n\x10MetadataOverride\x12\x0e\n\x06models\x18\x01 \x01(\x0c\x12\r\n\x05loras\x18\x02 \x01(\x0c\x12\x13\n\x0b\x63ontrolNets\x18\x03 \x01(\x0c\x12\x19\n\x11textualInversions\x18\x04 \x01(\x0c\x12\x11\n\tupscalers\x18\x05 \x01(\x0c\"\xf2\x02\n\x16ImageGenerationRequest\x12\x12\n\x05image\x18\x01 \x01(\x0cH\x00\x88\x01\x01\x12\x13\n\x0bscaleFactor\x18\x02 \x01(\x05\x12\x11\n\x04mask\x18\x03 \x01(\x0cH\x01\x88\x01\x01\x12\x19\n\x05hints\x18\x04 \x03(\x0b\x32\n.HintProto\x12\x0e\n\x06prompt\x18\x05 \x01(\t\x12\x16\n\x0enegativePrompt\x18\x06 \x01(\t\x12\x15\n\rconfiguration\x18\x07 \x01(\x0c\x12#\n\x08override\x18\x08 \x01(\x0b\x32\x11.MetadataOverride\x12\x10\n\x08keywords\x18\t \x03(\t\x12\x0c\n\x04user\x18\n \x01(\t\x12\x1b\n\x06\x64\x65vice\x18\x0b \x01(\x0e\x32\x0b.DeviceType\x12\x10\n\x08\x63ontents\x18\x0c \x03(\x0c\x12\x19\n\x0csharedSecret\x18\r \x01(\tH\x02\x88\x01\x01\x12\x0f\n\x07\x63hunked\x18\x0e \x01(\x08\x42\x08\n\x06_imageB\x07\n\x05_maskB\x0f\n\r_sharedSecret\"@\n\tHintProto\x12\x10\n\x08hintType\x18\x01 \x01(\t\x12!\n\x07tensors\x18\x02 \x03(\x0b\x32\x10.TensorAndWeight\"1\n\x0fTensorAndWeight\x12\x0e\n\x06tensor\x18\x01 \x01(\x0c\x12\x0e\n\x06weight\x18\x02 \x01(\x02\"\xfc\x06\n\x1cImageGenerationSignpostProto\x12@\n\x0btextEncoded\x18\x01 \x01(\x0b\x32).ImageGenerationSignpostProto.TextEncodedH\x00\x12\x42\n\x0cimageEncoded\x18\x02 \x01(\x0b\x32*.ImageGenerationSignpostProto.ImageEncodedH\x00\x12:\n\x08sampling\x18\x03 \x01(\x0b\x32&.ImageGenerationSignpostProto.SamplingH\x00\x12\x42\n\x0cimageDecoded\x18\x04 \x01(\x0b\x32*.ImageGenerationSignpostProto.ImageDecodedH\x00\x12V\n\x16secondPassImageEncoded\x18\x05 \x01(\x0b\x32\x34.ImageGenerationSignpostProto.SecondPassImageEncodedH\x00\x12N\n\x12secondPassSampling\x18\x06 \x01(\x0b\x32\x30.ImageGenerationSignpostProto.SecondPassSamplingH\x00\x12V\n\x16secondPassImageDecoded\x18\x07 \x01(\x0b\x32\x34.ImageGenerationSignpostProto.SecondPassImageDecodedH\x00\x12\x42\n\x0c\x66\x61\x63\x65Restored\x18\x08 \x01(\x0b\x32*.ImageGenerationSignpostProto.FaceRestoredH\x00\x12\x44\n\rimageUpscaled\x18\t \x01(\x0b\x32+.ImageGenerationSignpostProto.ImageUpscaledH\x00\x1a\r\n\x0bTextEncoded\x1a\x0e\n\x0cImageEncoded\x1a\x18\n\x08Sampling\x12\x0c\n\x04step\x18\x01 \x01(\x05\x1a\x0e\n\x0cImageDecoded\x1a\x18\n\x16SecondPassImageEncoded\x1a\"\n\x12SecondPassSampling\x12\x0c\n\x04step\x18\x01 \x01(\x05\x1a\x18\n\x16SecondPassImageDecoded\x1a\x0e\n\x0c\x46\x61\x63\x65Restored\x1a\x0f\n\rImageUpscaledB\n\n\x08signpost\"x\n\x16RemoteDownloadResponse\x12\x15\n\rbytesReceived\x18\x01 \x01(\x03\x12\x15\n\rbytesExpected\x18\x02 \x01(\x03\x12\x0c\n\x04item\x18\x03 \x01(\x05\x12\x15\n\ritemsExpected\x18\x04 \x01(\x05\x12\x0b\n\x03tag\x18\x05 \x01(\t\"\xc7\x03\n\x17ImageGenerationResponse\x12\x17\n\x0fgeneratedImages\x18\x01 \x03(\x0c\x12;\n\x0f\x63urrentSignpost\x18\x02 \x01(\x0b\x32\x1d.ImageGenerationSignpostProtoH\x00\x88\x01\x01\x12\x30\n\tsignposts\x18\x03 \x03(\x0b\x32\x1d.ImageGenerationSignpostProto\x12\x19\n\x0cpreviewImage\x18\x04 \x01(\x0cH\x01\x88\x01\x01\x12\x18\n\x0bscaleFactor\x18\x05 \x01(\x05H\x02\x88\x01\x01\x12\x0c\n\x04tags\x18\x06 \x03(\t\x12\x19\n\x0c\x64ownloadSize\x18\x07 \x01(\x03H\x03\x88\x01\x01\x12\x1f\n\nchunkState\x18\x08 \x01(\x0e\x32\x0b.ChunkState\x12\x34\n\x0eremoteDownload\x18\t \x01(\x0b\x32\x17.RemoteDownloadResponseH\x04\x88\x01\x01\x12\x16\n\x0egeneratedAudio\x18\n \x03(\x0c\x42\x12\n\x10_currentSignpostB\x0f\n\r_previewImageB\x0e\n\x0c_scaleFactorB\x0f\n\r_downloadSizeB\x11\n\x0f_remoteDownload\">\n\tFileChunk\x12\x0f\n\x07\x63ontent\x18\x01 \x01(\x0c\x12\x10\n\x08\x66ilename\x18\x02 \x01(\t\x12\x0e\n\x06offset\x18\x03 \x01(\x03\"H\n\x11InitUploadRequest\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12\x0e\n\x06sha256\x18\x02 \x01(\x0c\x12\x11\n\ttotalSize\x18\x03 \x01(\x03\"g\n\x0eUploadResponse\x12\x1a\n\x12\x63hunkUploadSuccess\x18\x01 \x01(\x08\x12\x16\n\x0ereceivedOffset\x18\x02 \x01(\x03\x12\x0f\n\x07message\x18\x03 \x01(\t\x12\x10\n\x08\x66ilename\x18\x04 \x01(\t\"\x92\x01\n\x11\x46ileUploadRequest\x12)\n\x0binitRequest\x18\x01 \x01(\x0b\x32\x12.InitUploadRequestH\x00\x12\x1b\n\x05\x63hunk\x18\x02 \x01(\x0b\x32\n.FileChunkH\x00\x12\x19\n\x0csharedSecret\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\t\n\x07requestB\x0f\n\r_sharedSecret\"\x1d\n\rPubkeyRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\"1\n\x0ePubkeyResponse\x12\x0f\n\x07message\x18\x01 \x01(\t\x12\x0e\n\x06pubkey\x18\x02 \x01(\t\"\x0e\n\x0cHoursRequest\":\n\rHoursResponse\x12)\n\nthresholds\x18\x01 \x01(\x0b\x32\x15.ComputeUnitThreshold*/\n\nDeviceType\x12\t\n\x05PHONE\x10\x00\x12\n\n\x06TABLET\x10\x01\x12\n\n\x06LAPTOP\x10\x02*-\n\nChunkState\x12\x0e\n\nLAST_CHUNK\x10\x00\x12\x0f\n\x0bMORE_CHUNKS\x10\x01\x32\xc2\x02\n\x16ImageGenerationService\x12\x44\n\rGenerateImage\x12\x17.ImageGenerationRequest\x1a\x18.ImageGenerationResponse0\x01\x12\x36\n\nFilesExist\x12\x10.FileListRequest\x1a\x16.FileExistenceResponse\x12\x35\n\nUploadFile\x12\x12.FileUploadRequest\x1a\x0f.UploadResponse(\x01\x30\x01\x12 \n\x04\x45\x63ho\x12\x0c.EchoRequest\x1a\n.EchoReply\x12)\n\x06Pubkey\x12\x0e.PubkeyRequest\x1a\x0f.PubkeyResponse\x12&\n\x05Hours\x12\r.HoursRequest\x1a\x0e.HoursResponseb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'imageService_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + DESCRIPTOR._loaded_options = None + _globals['_DEVICETYPE']._serialized_start=3199 + _globals['_DEVICETYPE']._serialized_end=3246 + _globals['_CHUNKSTATE']._serialized_start=3248 + _globals['_CHUNKSTATE']._serialized_end=3293 + _globals['_ECHOREQUEST']._serialized_start=22 + _globals['_ECHOREQUEST']._serialized_end=93 + _globals['_COMPUTEUNITTHRESHOLD']._serialized_start=95 + _globals['_COMPUTEUNITTHRESHOLD']._serialized_end=168 + _globals['_ECHOREPLY']._serialized_start=171 + _globals['_ECHOREPLY']._serialized_end=387 + _globals['_FILELISTREQUEST']._serialized_start=389 + _globals['_FILELISTREQUEST']._serialized_end=488 + _globals['_FILEEXISTENCERESPONSE']._serialized_start=490 + _globals['_FILEEXISTENCERESPONSE']._serialized_end=564 + _globals['_METADATAOVERRIDE']._serialized_start=566 + _globals['_METADATAOVERRIDE']._serialized_end=682 + _globals['_IMAGEGENERATIONREQUEST']._serialized_start=685 + _globals['_IMAGEGENERATIONREQUEST']._serialized_end=1055 + _globals['_HINTPROTO']._serialized_start=1057 + _globals['_HINTPROTO']._serialized_end=1121 + _globals['_TENSORANDWEIGHT']._serialized_start=1123 + _globals['_TENSORANDWEIGHT']._serialized_end=1172 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO']._serialized_start=1175 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO']._serialized_end=2067 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_TEXTENCODED']._serialized_start=1863 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_TEXTENCODED']._serialized_end=1876 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_IMAGEENCODED']._serialized_start=1878 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_IMAGEENCODED']._serialized_end=1892 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SAMPLING']._serialized_start=1894 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SAMPLING']._serialized_end=1918 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_IMAGEDECODED']._serialized_start=1920 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_IMAGEDECODED']._serialized_end=1934 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SECONDPASSIMAGEENCODED']._serialized_start=1936 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SECONDPASSIMAGEENCODED']._serialized_end=1960 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SECONDPASSSAMPLING']._serialized_start=1962 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SECONDPASSSAMPLING']._serialized_end=1996 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SECONDPASSIMAGEDECODED']._serialized_start=1998 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_SECONDPASSIMAGEDECODED']._serialized_end=2022 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_FACERESTORED']._serialized_start=2024 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_FACERESTORED']._serialized_end=2038 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_IMAGEUPSCALED']._serialized_start=2040 + _globals['_IMAGEGENERATIONSIGNPOSTPROTO_IMAGEUPSCALED']._serialized_end=2055 + _globals['_REMOTEDOWNLOADRESPONSE']._serialized_start=2069 + _globals['_REMOTEDOWNLOADRESPONSE']._serialized_end=2189 + _globals['_IMAGEGENERATIONRESPONSE']._serialized_start=2192 + _globals['_IMAGEGENERATIONRESPONSE']._serialized_end=2647 + _globals['_FILECHUNK']._serialized_start=2649 + _globals['_FILECHUNK']._serialized_end=2711 + _globals['_INITUPLOADREQUEST']._serialized_start=2713 + _globals['_INITUPLOADREQUEST']._serialized_end=2785 + _globals['_UPLOADRESPONSE']._serialized_start=2787 + _globals['_UPLOADRESPONSE']._serialized_end=2890 + _globals['_FILEUPLOADREQUEST']._serialized_start=2893 + _globals['_FILEUPLOADREQUEST']._serialized_end=3039 + _globals['_PUBKEYREQUEST']._serialized_start=3041 + _globals['_PUBKEYREQUEST']._serialized_end=3070 + _globals['_PUBKEYRESPONSE']._serialized_start=3072 + _globals['_PUBKEYRESPONSE']._serialized_end=3121 + _globals['_HOURSREQUEST']._serialized_start=3123 + _globals['_HOURSREQUEST']._serialized_end=3137 + _globals['_HOURSRESPONSE']._serialized_start=3139 + _globals['_HOURSRESPONSE']._serialized_end=3197 + _globals['_IMAGEGENERATIONSERVICE']._serialized_start=3296 + _globals['_IMAGEGENERATIONSERVICE']._serialized_end=3618 +# @@protoc_insertion_point(module_scope) diff --git a/CLI/macos/drawthings/generated/imageService_pb2.pyi b/CLI/macos/drawthings/generated/imageService_pb2.pyi new file mode 100644 index 000000000..0bc53805b --- /dev/null +++ b/CLI/macos/drawthings/generated/imageService_pb2.pyi @@ -0,0 +1,296 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class DeviceType(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + PHONE: _ClassVar[DeviceType] + TABLET: _ClassVar[DeviceType] + LAPTOP: _ClassVar[DeviceType] + +class ChunkState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + LAST_CHUNK: _ClassVar[ChunkState] + MORE_CHUNKS: _ClassVar[ChunkState] +PHONE: DeviceType +TABLET: DeviceType +LAPTOP: DeviceType +LAST_CHUNK: ChunkState +MORE_CHUNKS: ChunkState + +class EchoRequest(_message.Message): + __slots__ = ("name", "sharedSecret") + NAME_FIELD_NUMBER: _ClassVar[int] + SHAREDSECRET_FIELD_NUMBER: _ClassVar[int] + name: str + sharedSecret: str + def __init__(self, name: _Optional[str] = ..., sharedSecret: _Optional[str] = ...) -> None: ... + +class ComputeUnitThreshold(_message.Message): + __slots__ = ("community", "plus", "expireAt") + COMMUNITY_FIELD_NUMBER: _ClassVar[int] + PLUS_FIELD_NUMBER: _ClassVar[int] + EXPIREAT_FIELD_NUMBER: _ClassVar[int] + community: float + plus: float + expireAt: int + def __init__(self, community: _Optional[float] = ..., plus: _Optional[float] = ..., expireAt: _Optional[int] = ...) -> None: ... + +class EchoReply(_message.Message): + __slots__ = ("message", "files", "override", "sharedSecretMissing", "thresholds", "serverIdentifier") + MESSAGE_FIELD_NUMBER: _ClassVar[int] + FILES_FIELD_NUMBER: _ClassVar[int] + OVERRIDE_FIELD_NUMBER: _ClassVar[int] + SHAREDSECRETMISSING_FIELD_NUMBER: _ClassVar[int] + THRESHOLDS_FIELD_NUMBER: _ClassVar[int] + SERVERIDENTIFIER_FIELD_NUMBER: _ClassVar[int] + message: str + files: _containers.RepeatedScalarFieldContainer[str] + override: MetadataOverride + sharedSecretMissing: bool + thresholds: ComputeUnitThreshold + serverIdentifier: int + def __init__(self, message: _Optional[str] = ..., files: _Optional[_Iterable[str]] = ..., override: _Optional[_Union[MetadataOverride, _Mapping]] = ..., sharedSecretMissing: bool = ..., thresholds: _Optional[_Union[ComputeUnitThreshold, _Mapping]] = ..., serverIdentifier: _Optional[int] = ...) -> None: ... + +class FileListRequest(_message.Message): + __slots__ = ("files", "filesWithHash", "sharedSecret") + FILES_FIELD_NUMBER: _ClassVar[int] + FILESWITHHASH_FIELD_NUMBER: _ClassVar[int] + SHAREDSECRET_FIELD_NUMBER: _ClassVar[int] + files: _containers.RepeatedScalarFieldContainer[str] + filesWithHash: _containers.RepeatedScalarFieldContainer[str] + sharedSecret: str + def __init__(self, files: _Optional[_Iterable[str]] = ..., filesWithHash: _Optional[_Iterable[str]] = ..., sharedSecret: _Optional[str] = ...) -> None: ... + +class FileExistenceResponse(_message.Message): + __slots__ = ("files", "existences", "hashes") + FILES_FIELD_NUMBER: _ClassVar[int] + EXISTENCES_FIELD_NUMBER: _ClassVar[int] + HASHES_FIELD_NUMBER: _ClassVar[int] + files: _containers.RepeatedScalarFieldContainer[str] + existences: _containers.RepeatedScalarFieldContainer[bool] + hashes: _containers.RepeatedScalarFieldContainer[bytes] + def __init__(self, files: _Optional[_Iterable[str]] = ..., existences: _Optional[_Iterable[bool]] = ..., hashes: _Optional[_Iterable[bytes]] = ...) -> None: ... + +class MetadataOverride(_message.Message): + __slots__ = ("models", "loras", "controlNets", "textualInversions", "upscalers") + MODELS_FIELD_NUMBER: _ClassVar[int] + LORAS_FIELD_NUMBER: _ClassVar[int] + CONTROLNETS_FIELD_NUMBER: _ClassVar[int] + TEXTUALINVERSIONS_FIELD_NUMBER: _ClassVar[int] + UPSCALERS_FIELD_NUMBER: _ClassVar[int] + models: bytes + loras: bytes + controlNets: bytes + textualInversions: bytes + upscalers: bytes + def __init__(self, models: _Optional[bytes] = ..., loras: _Optional[bytes] = ..., controlNets: _Optional[bytes] = ..., textualInversions: _Optional[bytes] = ..., upscalers: _Optional[bytes] = ...) -> None: ... + +class ImageGenerationRequest(_message.Message): + __slots__ = ("image", "scaleFactor", "mask", "hints", "prompt", "negativePrompt", "configuration", "override", "keywords", "user", "device", "contents", "sharedSecret", "chunked") + IMAGE_FIELD_NUMBER: _ClassVar[int] + SCALEFACTOR_FIELD_NUMBER: _ClassVar[int] + MASK_FIELD_NUMBER: _ClassVar[int] + HINTS_FIELD_NUMBER: _ClassVar[int] + PROMPT_FIELD_NUMBER: _ClassVar[int] + NEGATIVEPROMPT_FIELD_NUMBER: _ClassVar[int] + CONFIGURATION_FIELD_NUMBER: _ClassVar[int] + OVERRIDE_FIELD_NUMBER: _ClassVar[int] + KEYWORDS_FIELD_NUMBER: _ClassVar[int] + USER_FIELD_NUMBER: _ClassVar[int] + DEVICE_FIELD_NUMBER: _ClassVar[int] + CONTENTS_FIELD_NUMBER: _ClassVar[int] + SHAREDSECRET_FIELD_NUMBER: _ClassVar[int] + CHUNKED_FIELD_NUMBER: _ClassVar[int] + image: bytes + scaleFactor: int + mask: bytes + hints: _containers.RepeatedCompositeFieldContainer[HintProto] + prompt: str + negativePrompt: str + configuration: bytes + override: MetadataOverride + keywords: _containers.RepeatedScalarFieldContainer[str] + user: str + device: DeviceType + contents: _containers.RepeatedScalarFieldContainer[bytes] + sharedSecret: str + chunked: bool + def __init__(self, image: _Optional[bytes] = ..., scaleFactor: _Optional[int] = ..., mask: _Optional[bytes] = ..., hints: _Optional[_Iterable[_Union[HintProto, _Mapping]]] = ..., prompt: _Optional[str] = ..., negativePrompt: _Optional[str] = ..., configuration: _Optional[bytes] = ..., override: _Optional[_Union[MetadataOverride, _Mapping]] = ..., keywords: _Optional[_Iterable[str]] = ..., user: _Optional[str] = ..., device: _Optional[_Union[DeviceType, str]] = ..., contents: _Optional[_Iterable[bytes]] = ..., sharedSecret: _Optional[str] = ..., chunked: bool = ...) -> None: ... + +class HintProto(_message.Message): + __slots__ = ("hintType", "tensors") + HINTTYPE_FIELD_NUMBER: _ClassVar[int] + TENSORS_FIELD_NUMBER: _ClassVar[int] + hintType: str + tensors: _containers.RepeatedCompositeFieldContainer[TensorAndWeight] + def __init__(self, hintType: _Optional[str] = ..., tensors: _Optional[_Iterable[_Union[TensorAndWeight, _Mapping]]] = ...) -> None: ... + +class TensorAndWeight(_message.Message): + __slots__ = ("tensor", "weight") + TENSOR_FIELD_NUMBER: _ClassVar[int] + WEIGHT_FIELD_NUMBER: _ClassVar[int] + tensor: bytes + weight: float + def __init__(self, tensor: _Optional[bytes] = ..., weight: _Optional[float] = ...) -> None: ... + +class ImageGenerationSignpostProto(_message.Message): + __slots__ = ("textEncoded", "imageEncoded", "sampling", "imageDecoded", "secondPassImageEncoded", "secondPassSampling", "secondPassImageDecoded", "faceRestored", "imageUpscaled") + class TextEncoded(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class ImageEncoded(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class Sampling(_message.Message): + __slots__ = ("step",) + STEP_FIELD_NUMBER: _ClassVar[int] + step: int + def __init__(self, step: _Optional[int] = ...) -> None: ... + class ImageDecoded(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class SecondPassImageEncoded(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class SecondPassSampling(_message.Message): + __slots__ = ("step",) + STEP_FIELD_NUMBER: _ClassVar[int] + step: int + def __init__(self, step: _Optional[int] = ...) -> None: ... + class SecondPassImageDecoded(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class FaceRestored(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + class ImageUpscaled(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + TEXTENCODED_FIELD_NUMBER: _ClassVar[int] + IMAGEENCODED_FIELD_NUMBER: _ClassVar[int] + SAMPLING_FIELD_NUMBER: _ClassVar[int] + IMAGEDECODED_FIELD_NUMBER: _ClassVar[int] + SECONDPASSIMAGEENCODED_FIELD_NUMBER: _ClassVar[int] + SECONDPASSSAMPLING_FIELD_NUMBER: _ClassVar[int] + SECONDPASSIMAGEDECODED_FIELD_NUMBER: _ClassVar[int] + FACERESTORED_FIELD_NUMBER: _ClassVar[int] + IMAGEUPSCALED_FIELD_NUMBER: _ClassVar[int] + textEncoded: ImageGenerationSignpostProto.TextEncoded + imageEncoded: ImageGenerationSignpostProto.ImageEncoded + sampling: ImageGenerationSignpostProto.Sampling + imageDecoded: ImageGenerationSignpostProto.ImageDecoded + secondPassImageEncoded: ImageGenerationSignpostProto.SecondPassImageEncoded + secondPassSampling: ImageGenerationSignpostProto.SecondPassSampling + secondPassImageDecoded: ImageGenerationSignpostProto.SecondPassImageDecoded + faceRestored: ImageGenerationSignpostProto.FaceRestored + imageUpscaled: ImageGenerationSignpostProto.ImageUpscaled + def __init__(self, textEncoded: _Optional[_Union[ImageGenerationSignpostProto.TextEncoded, _Mapping]] = ..., imageEncoded: _Optional[_Union[ImageGenerationSignpostProto.ImageEncoded, _Mapping]] = ..., sampling: _Optional[_Union[ImageGenerationSignpostProto.Sampling, _Mapping]] = ..., imageDecoded: _Optional[_Union[ImageGenerationSignpostProto.ImageDecoded, _Mapping]] = ..., secondPassImageEncoded: _Optional[_Union[ImageGenerationSignpostProto.SecondPassImageEncoded, _Mapping]] = ..., secondPassSampling: _Optional[_Union[ImageGenerationSignpostProto.SecondPassSampling, _Mapping]] = ..., secondPassImageDecoded: _Optional[_Union[ImageGenerationSignpostProto.SecondPassImageDecoded, _Mapping]] = ..., faceRestored: _Optional[_Union[ImageGenerationSignpostProto.FaceRestored, _Mapping]] = ..., imageUpscaled: _Optional[_Union[ImageGenerationSignpostProto.ImageUpscaled, _Mapping]] = ...) -> None: ... + +class RemoteDownloadResponse(_message.Message): + __slots__ = ("bytesReceived", "bytesExpected", "item", "itemsExpected", "tag") + BYTESRECEIVED_FIELD_NUMBER: _ClassVar[int] + BYTESEXPECTED_FIELD_NUMBER: _ClassVar[int] + ITEM_FIELD_NUMBER: _ClassVar[int] + ITEMSEXPECTED_FIELD_NUMBER: _ClassVar[int] + TAG_FIELD_NUMBER: _ClassVar[int] + bytesReceived: int + bytesExpected: int + item: int + itemsExpected: int + tag: str + def __init__(self, bytesReceived: _Optional[int] = ..., bytesExpected: _Optional[int] = ..., item: _Optional[int] = ..., itemsExpected: _Optional[int] = ..., tag: _Optional[str] = ...) -> None: ... + +class ImageGenerationResponse(_message.Message): + __slots__ = ("generatedImages", "currentSignpost", "signposts", "previewImage", "scaleFactor", "tags", "downloadSize", "chunkState", "remoteDownload", "generatedAudio") + GENERATEDIMAGES_FIELD_NUMBER: _ClassVar[int] + CURRENTSIGNPOST_FIELD_NUMBER: _ClassVar[int] + SIGNPOSTS_FIELD_NUMBER: _ClassVar[int] + PREVIEWIMAGE_FIELD_NUMBER: _ClassVar[int] + SCALEFACTOR_FIELD_NUMBER: _ClassVar[int] + TAGS_FIELD_NUMBER: _ClassVar[int] + DOWNLOADSIZE_FIELD_NUMBER: _ClassVar[int] + CHUNKSTATE_FIELD_NUMBER: _ClassVar[int] + REMOTEDOWNLOAD_FIELD_NUMBER: _ClassVar[int] + GENERATEDAUDIO_FIELD_NUMBER: _ClassVar[int] + generatedImages: _containers.RepeatedScalarFieldContainer[bytes] + currentSignpost: ImageGenerationSignpostProto + signposts: _containers.RepeatedCompositeFieldContainer[ImageGenerationSignpostProto] + previewImage: bytes + scaleFactor: int + tags: _containers.RepeatedScalarFieldContainer[str] + downloadSize: int + chunkState: ChunkState + remoteDownload: RemoteDownloadResponse + generatedAudio: _containers.RepeatedScalarFieldContainer[bytes] + def __init__(self, generatedImages: _Optional[_Iterable[bytes]] = ..., currentSignpost: _Optional[_Union[ImageGenerationSignpostProto, _Mapping]] = ..., signposts: _Optional[_Iterable[_Union[ImageGenerationSignpostProto, _Mapping]]] = ..., previewImage: _Optional[bytes] = ..., scaleFactor: _Optional[int] = ..., tags: _Optional[_Iterable[str]] = ..., downloadSize: _Optional[int] = ..., chunkState: _Optional[_Union[ChunkState, str]] = ..., remoteDownload: _Optional[_Union[RemoteDownloadResponse, _Mapping]] = ..., generatedAudio: _Optional[_Iterable[bytes]] = ...) -> None: ... + +class FileChunk(_message.Message): + __slots__ = ("content", "filename", "offset") + CONTENT_FIELD_NUMBER: _ClassVar[int] + FILENAME_FIELD_NUMBER: _ClassVar[int] + OFFSET_FIELD_NUMBER: _ClassVar[int] + content: bytes + filename: str + offset: int + def __init__(self, content: _Optional[bytes] = ..., filename: _Optional[str] = ..., offset: _Optional[int] = ...) -> None: ... + +class InitUploadRequest(_message.Message): + __slots__ = ("filename", "sha256", "totalSize") + FILENAME_FIELD_NUMBER: _ClassVar[int] + SHA256_FIELD_NUMBER: _ClassVar[int] + TOTALSIZE_FIELD_NUMBER: _ClassVar[int] + filename: str + sha256: bytes + totalSize: int + def __init__(self, filename: _Optional[str] = ..., sha256: _Optional[bytes] = ..., totalSize: _Optional[int] = ...) -> None: ... + +class UploadResponse(_message.Message): + __slots__ = ("chunkUploadSuccess", "receivedOffset", "message", "filename") + CHUNKUPLOADSUCCESS_FIELD_NUMBER: _ClassVar[int] + RECEIVEDOFFSET_FIELD_NUMBER: _ClassVar[int] + MESSAGE_FIELD_NUMBER: _ClassVar[int] + FILENAME_FIELD_NUMBER: _ClassVar[int] + chunkUploadSuccess: bool + receivedOffset: int + message: str + filename: str + def __init__(self, chunkUploadSuccess: bool = ..., receivedOffset: _Optional[int] = ..., message: _Optional[str] = ..., filename: _Optional[str] = ...) -> None: ... + +class FileUploadRequest(_message.Message): + __slots__ = ("initRequest", "chunk", "sharedSecret") + INITREQUEST_FIELD_NUMBER: _ClassVar[int] + CHUNK_FIELD_NUMBER: _ClassVar[int] + SHAREDSECRET_FIELD_NUMBER: _ClassVar[int] + initRequest: InitUploadRequest + chunk: FileChunk + sharedSecret: str + def __init__(self, initRequest: _Optional[_Union[InitUploadRequest, _Mapping]] = ..., chunk: _Optional[_Union[FileChunk, _Mapping]] = ..., sharedSecret: _Optional[str] = ...) -> None: ... + +class PubkeyRequest(_message.Message): + __slots__ = ("name",) + NAME_FIELD_NUMBER: _ClassVar[int] + name: str + def __init__(self, name: _Optional[str] = ...) -> None: ... + +class PubkeyResponse(_message.Message): + __slots__ = ("message", "pubkey") + MESSAGE_FIELD_NUMBER: _ClassVar[int] + PUBKEY_FIELD_NUMBER: _ClassVar[int] + message: str + pubkey: str + def __init__(self, message: _Optional[str] = ..., pubkey: _Optional[str] = ...) -> None: ... + +class HoursRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class HoursResponse(_message.Message): + __slots__ = ("thresholds",) + THRESHOLDS_FIELD_NUMBER: _ClassVar[int] + thresholds: ComputeUnitThreshold + def __init__(self, thresholds: _Optional[_Union[ComputeUnitThreshold, _Mapping]] = ...) -> None: ... diff --git a/CLI/macos/drawthings/generated/imageService_pb2_grpc.py b/CLI/macos/drawthings/generated/imageService_pb2_grpc.py new file mode 100644 index 000000000..55d07036b --- /dev/null +++ b/CLI/macos/drawthings/generated/imageService_pb2_grpc.py @@ -0,0 +1,312 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc +import warnings + +from . import imageService_pb2 as imageService__pb2 + +GRPC_GENERATED_VERSION = '1.71.0' +GRPC_VERSION = grpc.__version__ +_version_not_supported = False + +try: + from grpc._utilities import first_version_is_lower + _version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION) +except ImportError: + _version_not_supported = True + +if _version_not_supported: + raise RuntimeError( + f'The grpc package installed is at version {GRPC_VERSION},' + + f' but the generated code in imageService_pb2_grpc.py depends on' + + f' grpcio>={GRPC_GENERATED_VERSION}.' + + f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}' + + f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.' + ) + + +class ImageGenerationServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GenerateImage = channel.unary_stream( + '/ImageGenerationService/GenerateImage', + request_serializer=imageService__pb2.ImageGenerationRequest.SerializeToString, + response_deserializer=imageService__pb2.ImageGenerationResponse.FromString, + _registered_method=True) + self.FilesExist = channel.unary_unary( + '/ImageGenerationService/FilesExist', + request_serializer=imageService__pb2.FileListRequest.SerializeToString, + response_deserializer=imageService__pb2.FileExistenceResponse.FromString, + _registered_method=True) + self.UploadFile = channel.stream_stream( + '/ImageGenerationService/UploadFile', + request_serializer=imageService__pb2.FileUploadRequest.SerializeToString, + response_deserializer=imageService__pb2.UploadResponse.FromString, + _registered_method=True) + self.Echo = channel.unary_unary( + '/ImageGenerationService/Echo', + request_serializer=imageService__pb2.EchoRequest.SerializeToString, + response_deserializer=imageService__pb2.EchoReply.FromString, + _registered_method=True) + self.Pubkey = channel.unary_unary( + '/ImageGenerationService/Pubkey', + request_serializer=imageService__pb2.PubkeyRequest.SerializeToString, + response_deserializer=imageService__pb2.PubkeyResponse.FromString, + _registered_method=True) + self.Hours = channel.unary_unary( + '/ImageGenerationService/Hours', + request_serializer=imageService__pb2.HoursRequest.SerializeToString, + response_deserializer=imageService__pb2.HoursResponse.FromString, + _registered_method=True) + + +class ImageGenerationServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def GenerateImage(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def FilesExist(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UploadFile(self, request_iterator, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Echo(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Pubkey(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Hours(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ImageGenerationServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GenerateImage': grpc.unary_stream_rpc_method_handler( + servicer.GenerateImage, + request_deserializer=imageService__pb2.ImageGenerationRequest.FromString, + response_serializer=imageService__pb2.ImageGenerationResponse.SerializeToString, + ), + 'FilesExist': grpc.unary_unary_rpc_method_handler( + servicer.FilesExist, + request_deserializer=imageService__pb2.FileListRequest.FromString, + response_serializer=imageService__pb2.FileExistenceResponse.SerializeToString, + ), + 'UploadFile': grpc.stream_stream_rpc_method_handler( + servicer.UploadFile, + request_deserializer=imageService__pb2.FileUploadRequest.FromString, + response_serializer=imageService__pb2.UploadResponse.SerializeToString, + ), + 'Echo': grpc.unary_unary_rpc_method_handler( + servicer.Echo, + request_deserializer=imageService__pb2.EchoRequest.FromString, + response_serializer=imageService__pb2.EchoReply.SerializeToString, + ), + 'Pubkey': grpc.unary_unary_rpc_method_handler( + servicer.Pubkey, + request_deserializer=imageService__pb2.PubkeyRequest.FromString, + response_serializer=imageService__pb2.PubkeyResponse.SerializeToString, + ), + 'Hours': grpc.unary_unary_rpc_method_handler( + servicer.Hours, + request_deserializer=imageService__pb2.HoursRequest.FromString, + response_serializer=imageService__pb2.HoursResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'ImageGenerationService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('ImageGenerationService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class ImageGenerationService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GenerateImage(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream( + request, + target, + '/ImageGenerationService/GenerateImage', + imageService__pb2.ImageGenerationRequest.SerializeToString, + imageService__pb2.ImageGenerationResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def FilesExist(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/ImageGenerationService/FilesExist', + imageService__pb2.FileListRequest.SerializeToString, + imageService__pb2.FileExistenceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UploadFile(request_iterator, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.stream_stream( + request_iterator, + target, + '/ImageGenerationService/UploadFile', + imageService__pb2.FileUploadRequest.SerializeToString, + imageService__pb2.UploadResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Echo(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/ImageGenerationService/Echo', + imageService__pb2.EchoRequest.SerializeToString, + imageService__pb2.EchoReply.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Pubkey(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/ImageGenerationService/Pubkey', + imageService__pb2.PubkeyRequest.SerializeToString, + imageService__pb2.PubkeyResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Hours(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/ImageGenerationService/Hours', + imageService__pb2.HoursRequest.SerializeToString, + imageService__pb2.HoursResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/CLI/macos/drawthings/requirements-Dt-gRPC.txt b/CLI/macos/drawthings/requirements-Dt-gRPC.txt new file mode 100644 index 000000000..608843632 --- /dev/null +++ b/CLI/macos/drawthings/requirements-Dt-gRPC.txt @@ -0,0 +1,6 @@ +grpcio>=1.71.0 +flatbuffers>=25.2.10 +protobuf>=5.29.0 +fpzip +numpy +Pillow diff --git "a/mac-\344\277\256\345\244\215\346\235\203\351\231\220.command" "b/mac-\344\277\256\345\244\215\346\235\203\351\231\220.command" old mode 100644 new mode 100755 diff --git "a/mac-\345\220\257\345\212\250\346\234\215\345\212\241.command" "b/mac-\345\220\257\345\212\250\346\234\215\345\212\241.command" old mode 100644 new mode 100755 diff --git a/main.py b/main.py old mode 100644 new mode 100755 index c2e9e1b48..0a803db6f --- a/main.py +++ b/main.py @@ -28,7 +28,7 @@ import html import xml.etree.ElementTree as ET from typing import List, Dict, Any, Optional, Tuple -from threading import Lock, Thread +from threading import Lock, RLock, Thread import httpx from PIL import Image, ImageOps from io import BytesIO @@ -301,7 +301,7 @@ def apply_storage_settings(dirs=None): HISTORY_LOCK = Lock() GLOBAL_CONFIG_LOCK = Lock() CONVERSATION_LOCK = Lock() -CANVAS_LOCK = Lock() +CANVAS_LOCK = RLock() LOAD_LOCK = Lock() RUNNINGHUB_WORKFLOW_LOCK = Lock() NEXT_TASK_ID = 1 @@ -314,7 +314,7 @@ def apply_storage_settings(dirs=None): } PROVIDER_ID_RE = re.compile(r"^[a-zA-Z0-9_-]{2,40}$") -SUPPORTED_PROVIDER_PROTOCOLS = {"openai", "apimart", "gemini", "gemini-cli", "volcengine", "runninghub", "jimeng", "codex"} +SUPPORTED_PROVIDER_PROTOCOLS = {"openai", "apimart", "gemini", "gemini-cli", "volcengine", "runninghub", "jimeng", "codex", "tudou", "grpc"} SUPPORTED_IMAGE_REQUEST_MODES = {"openai", "openai-json", "openai-video-proxy", "openai-responses", "tudou-async"} RUNNINGHUB_DEFAULT_BASE_URL = "https://www.runninghub.ai" RUNNINGHUB_OPENAPI_BASE_URL = "https://www.runninghub.ai/openapi/v2" @@ -593,9 +593,16 @@ def load_env_file(): IMAGE_POLL_INTERVAL = float(os.getenv("IMAGE_POLL_INTERVAL", "2")) IMAGE_TASK_TIMEOUT = float(os.getenv("IMAGE_TASK_TIMEOUT", str(AI_REQUEST_TIMEOUT))) COMFYUI_HISTORY_TIMEOUT = int(float(os.getenv("COMFYUI_HISTORY_TIMEOUT", "1800"))) -# 下载 ComfyUI 产物的 socket 超时(秒,作用于连接和每次 read)。没有它时一次网络卡顿会让 urlopen 永久挂起, -# 导致 generate() 不返回、画布卡片一直转圈拿不到结果。给得足够大以容纳大视频/大图的正常下载。 -COMFYUI_DOWNLOAD_TIMEOUT = float(os.getenv("COMFYUI_DOWNLOAD_TIMEOUT", "120")) +# Cloudflare Access TCP 转发可能比局域网后端有更高的连接延迟,所有远端请求都必须有明确的单次超时。 +COMFYUI_BACKEND_CHECK_TIMEOUT = float(os.getenv("COMFYUI_BACKEND_CHECK_TIMEOUT", "5")) +COMFYUI_HTTP_TIMEOUT = float(os.getenv("COMFYUI_HTTP_TIMEOUT", "30")) +COMFYUI_HISTORY_REQUEST_TIMEOUT = float(os.getenv("COMFYUI_HISTORY_REQUEST_TIMEOUT", "15")) +# 下载 ComfyUI 产物的 socket 超时(秒,作用于连接和每次 read)。结果通过 Cloudflare 隧道回传时, +# 连接可能会短暂重置,因此下载层会自动重试,而不是直接把远端 URL 交给前端再次下载。 +COMFYUI_DOWNLOAD_TIMEOUT = float(os.getenv("COMFYUI_DOWNLOAD_TIMEOUT", "300")) +COMFYUI_DOWNLOAD_RETRIES = max(1, int(float(os.getenv("COMFYUI_DOWNLOAD_RETRIES", "3")))) +COMFYUI_DOWNLOAD_RETRY_DELAY = max(0.0, float(os.getenv("COMFYUI_DOWNLOAD_RETRY_DELAY", "1"))) +COMFYUI_UPLOAD_TIMEOUT = float(os.getenv("COMFYUI_UPLOAD_TIMEOUT", "60")) APIMART_IMAGE_TASK_TIMEOUT = float(os.getenv("APIMART_IMAGE_TASK_TIMEOUT", "1800")) APIMART_IMAGE_POLL_INTERVAL = float(os.getenv("APIMART_IMAGE_POLL_INTERVAL", "5")) APIMART_IMAGE_INITIAL_POLL_DELAY = float(os.getenv("APIMART_IMAGE_INITIAL_POLL_DELAY", "10")) @@ -1266,13 +1273,14 @@ def normalize_provider(item): raise HTTPException(status_code=400, detail=f"API 平台 ID 不合法:{provider_id or '(empty)'}") name = re.sub(r"\s+", " ", str(item.get("name") or provider_id).strip())[:60] or provider_id base_url = str(item.get("base_url") or "").strip().rstrip("/") - if base_url and not re.match(r"^https?://", base_url): - raise HTTPException(status_code=400, detail=f"{name} 的 Base URL 需要以 http:// 或 https:// 开头") protocol = str(item.get("protocol") or "openai").strip().lower() if protocol == "tudou": protocol = "openai" if protocol not in SUPPORTED_PROVIDER_PROTOCOLS: protocol = "openai" + is_drawthings = provider_id == "drawthings" or protocol == "grpc" + if base_url and not is_drawthings and not re.match(r"^https?://", base_url): + raise HTTPException(status_code=400, detail=f"{name} 的 Base URL 需要以 http:// 或 https:// 开头") image_request_mode = detect_image_request_mode(base_url, item.get("image_models") or []) or normalize_image_request_mode(item.get("image_request_mode")) image_generation_endpoint = normalize_endpoint_override(item.get("image_generation_endpoint"), "文生图端口") image_edit_endpoint = normalize_endpoint_override(item.get("image_edit_endpoint"), "图生图/编辑端口") @@ -1288,6 +1296,10 @@ def normalize_provider(item): base_url = "" if protocol in {"codex", "gemini-cli"}: base_url = "" + if provider_id == "drawthings" or protocol == "grpc": + provider_id = "drawthings" + name = name or "Draw Things gRPCServerCLI" + protocol = "grpc" if provider_id == "runninghub": protocol = "runninghub" base_url = base_url or RUNNINGHUB_DEFAULT_BASE_URL @@ -2763,6 +2775,12 @@ class GenerateRequest(BaseModel): class DeleteHistoryRequest(BaseModel): timestamp: float +class DeleteCanvasLogRequest(BaseModel): + log_id: str + delete_unreferenced_media: bool = False + reset_referencing_nodes: bool = False + base_updated_at: int = 0 + class TokenRequest(BaseModel): token: str @@ -2800,7 +2818,11 @@ class OnlineImageRequest(BaseModel): resolution: str = "" quality: str = "auto" n: int = 1 + batch_size: int = 1 + seed: Optional[int] = None reference_images: List[AIReference] = [] + mask_images: List[AIReference] = [] + loras: Optional[List[Dict[str, Any]]] = None operation: str = "" resolution_type: str = "" @@ -3208,7 +3230,7 @@ def check_images_exist(backend_addr, images): for img in images: try: url = f"http://{backend_addr}/view?filename={urllib.parse.quote(img)}&type=input" - r = requests.get(url, stream=True, timeout=0.5) + r = requests.get(url, stream=True, timeout=COMFYUI_BACKEND_CHECK_TIMEOUT) r.close() if r.status_code != 200: return False except: return False @@ -3281,7 +3303,11 @@ def get_best_backend(required_images: List[str] = None): for addr in COMFYUI_INSTANCES: try: - with urllib.request.urlopen(f"http://{addr}/queue", timeout=1) as response: + request = urllib.request.Request( + f"http://{addr}/queue", + headers={"Connection": "close", "Cache-Control": "no-cache"}, + ) + with urllib.request.urlopen(request, timeout=COMFYUI_BACKEND_CHECK_TIMEOUT) as response: data = json.loads(response.read()) remote_load = len(data.get('queue_running', [])) + len(data.get('queue_pending', [])) with LOAD_LOCK: @@ -3308,7 +3334,11 @@ def reserve_best_backend(required_images: List[str] = None): backend_stats = {} for addr in COMFYUI_INSTANCES: try: - with urllib.request.urlopen(f"http://{addr}/queue", timeout=1) as response: + request = urllib.request.Request( + f"http://{addr}/queue", + headers={"Connection": "close", "Cache-Control": "no-cache"}, + ) + with urllib.request.urlopen(request, timeout=COMFYUI_BACKEND_CHECK_TIMEOUT) as response: data = json.loads(response.read()) remote_load = len(data.get('queue_running', [])) + len(data.get('queue_pending', [])) has_images = check_images_exist(addr, required_images) @@ -3330,13 +3360,78 @@ def reserve_best_backend(required_images: List[str] = None): # --- 辅助工具 --- +def _download_comfy_file_atomic(full_url, local_path, validate_image=False, alternate_urls=None): + """Download a ComfyUI file atomically with retries for tunnel resets.""" + directory = os.path.dirname(local_path) or "." + urls = [str(full_url)] + for alternate_url in alternate_urls or []: + if alternate_url and str(alternate_url) not in urls: + urls.append(str(alternate_url)) + last_error = None + + for attempt in range(COMFYUI_DOWNLOAD_RETRIES): + for url in urls: + temp_path = "" + try: + prefix = f".{os.path.basename(local_path)}." + fd, temp_path = tempfile.mkstemp(prefix=prefix, suffix=".part", dir=directory) + total_bytes = 0 + request = urllib.request.Request( + url, + headers={"Connection": "close", "Cache-Control": "no-cache"}, + ) + with os.fdopen(fd, "wb") as out_file: + with urllib.request.urlopen(request, timeout=COMFYUI_DOWNLOAD_TIMEOUT) as response: + expected_length = response.headers.get("Content-Length") + try: + expected_length = int(expected_length) if expected_length else None + except (TypeError, ValueError): + expected_length = None + while True: + chunk = response.read(1024 * 1024) + if not chunk: + break + out_file.write(chunk) + total_bytes += len(chunk) + out_file.flush() + os.fsync(out_file.fileno()) + if expected_length is not None and total_bytes != expected_length: + raise IOError( + f"ComfyUI 文件下载不完整:收到 {total_bytes} 字节,期望 {expected_length} 字节" + ) + if total_bytes <= 0: + raise IOError("ComfyUI 返回了空文件") + if validate_image: + with Image.open(temp_path) as image: + image.verify() + os.replace(temp_path, local_path) + temp_path = "" + return + except Exception as exc: + last_error = exc + finally: + if temp_path: + try: + os.remove(temp_path) + except OSError: + pass + if attempt + 1 < COMFYUI_DOWNLOAD_RETRIES: + time.sleep(COMFYUI_DOWNLOAD_RETRY_DELAY) + + raise last_error or IOError("ComfyUI 文件下载失败") + def download_image(comfy_address, comfy_url_path, prefix="studio_"): filename = f"{prefix}{uuid.uuid4().hex[:10]}.png" local_path = output_path_for(filename, "output") full_url = f"http://{comfy_address}{comfy_url_path}" try: - with urllib.request.urlopen(full_url, timeout=COMFYUI_DOWNLOAD_TIMEOUT) as response, open(local_path, 'wb') as out_file: - shutil.copyfileobj(response, out_file) + alternate_url = full_url.replace("/view?", "/api/view?", 1) + _download_comfy_file_atomic( + full_url, + local_path, + validate_image=True, + alternate_urls=[alternate_url], + ) return output_url_for(filename, "output") except Exception as e: print(f"下载图片失败: {e}") @@ -3401,15 +3496,18 @@ def download_comfy_output(comfy_address, item, prefix="studio_"): file_type = urllib.parse.quote(str(item.get("type") or "output")) comfy_url_path = f"/view?filename={urllib.parse.quote(str(item['filename']))}&subfolder={subfolder}&type={file_type}" full_url = f"http://{comfy_address}{comfy_url_path}" + alternate_url = full_url.replace("/view?", "/api/view?", 1) try: - with urllib.request.urlopen(full_url, timeout=COMFYUI_DOWNLOAD_TIMEOUT) as response, open(local_path, 'wb') as out_file: - shutil.copyfileobj(response, out_file) + _download_comfy_file_atomic( + full_url, + local_path, + validate_image=ext in {".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tif", ".tiff"}, + alternate_urls=[alternate_url], + ) return output_url_for(filename, "output") except Exception as e: print(f"下载 ComfyUI 输出失败: {e}") - if comfy_url_path.startswith("/view"): - return comfy_url_path.replace("/view", "/api/view", 1) - return full_url + raise RuntimeError(f"ComfyUI 输出回传失败:{e}") from e def save_comfy_text_output(value, prefix="studio_", name=""): text = value if isinstance(value, str) else json.dumps(value, ensure_ascii=False, indent=2) @@ -3492,7 +3590,11 @@ def save_to_history(record): def get_comfy_history(comfy_address, prompt_id): try: - with urllib.request.urlopen(f"http://{comfy_address}/history/{prompt_id}") as response: + request = urllib.request.Request( + f"http://{comfy_address}/history/{prompt_id}", + headers={"Connection": "close", "Cache-Control": "no-cache"}, + ) + with urllib.request.urlopen(request, timeout=COMFYUI_HISTORY_REQUEST_TIMEOUT) as response: return json.loads(response.read()) except Exception as e: return {} @@ -4888,6 +4990,9 @@ def is_codex_provider(provider): def is_gemini_cli_provider(provider): return provider_protocol(provider) == "gemini-cli" +def is_draw_things_provider(provider): + return provider_protocol(provider) == "grpc" or str((provider or {}).get("id") or "").strip().lower() == "drawthings" + def codex_env_value(key): return os.getenv(key, "") or read_api_env_value(key) @@ -6940,6 +7045,205 @@ def output_file_from_url(url): return path return None +def collect_local_media_urls(value: Any) -> List[str]: + urls = [] + if isinstance(value, str): + text = value.strip() + if text.startswith(("/assets/", "/output/", "/api/storage-files/")): + urls.append(text) + elif isinstance(value, dict): + for item in value.values(): + urls.extend(collect_local_media_urls(item)) + elif isinstance(value, (list, tuple)): + for item in value: + urls.extend(collect_local_media_urls(item)) + return urls + +def local_media_path_from_url(url: str) -> Optional[str]: + try: + return output_file_from_url(url) + except (HTTPException, OSError, ValueError): + return None + +def generated_media_path_from_url(url: str) -> Optional[str]: + path = local_media_path_from_url(url) + if not path or not os.path.isfile(path): + return None + path = os.path.realpath(path) + for root in (OUTPUT_OUTPUT_DIR, OUTPUT_DIR): + root = os.path.realpath(root) + try: + if os.path.commonpath([root, path]) == root: + return path + except ValueError: + continue + return None + +def json_references_media_path(value: Any, target_path: str) -> bool: + target = os.path.normcase(os.path.realpath(target_path)) + if isinstance(value, str): + resolved = local_media_path_from_url(value.strip()) + return bool(resolved and os.path.normcase(os.path.realpath(resolved)) == target) + if isinstance(value, dict): + return any(json_references_media_path(item, target) for item in value.values()) + if isinstance(value, (list, tuple)): + return any(json_references_media_path(item, target) for item in value) + return False + +def persisted_json_references_media_path(target_path: str) -> bool: + candidates = [ASSET_LIBRARY_PATH] + for root in (CANVAS_DIR, CONVERSATION_DIR): + if os.path.isdir(root): + for current, _, files in os.walk(root): + candidates.extend( + os.path.join(current, name) + for name in files + if name.lower().endswith(".json") + ) + seen = set() + for path in candidates: + path = os.path.abspath(path) + if path in seen or not os.path.isfile(path): + continue + seen.add(path) + try: + with open(path, "r", encoding="utf-8-sig") as handle: + value = json.load(handle) + except (OSError, UnicodeError, json.JSONDecodeError): + return True + if json_references_media_path(value, target_path): + return True + return False + +def prune_generation_history_for_media(paths: List[str]) -> int: + if not paths or not os.path.isfile(HISTORY_FILE): + return 0 + try: + with HISTORY_LOCK: + with open(HISTORY_FILE, "r", encoding="utf-8-sig") as handle: + history = json.load(handle) + if not isinstance(history, list): + return 0 + kept = [ + record for record in history + if not any(json_references_media_path(record, path) for path in paths) + ] + removed = len(history) - len(kept) + if removed: + with open(HISTORY_FILE, "w", encoding="utf-8") as handle: + json.dump(kept, handle, ensure_ascii=False, indent=4) + return removed + except (OSError, UnicodeError, json.JSONDecodeError): + return 0 + +def smart_owned_result_items(images: List[Any], paths: List[str]) -> List[Any]: + return [ + item for item in images + if isinstance(item, dict) + and item.get("loopInputPreview") is not True + and item.get("generatedResult") is True + ] + +def expand_canvas_generated_media_paths(canvas: Dict[str, Any], paths: List[str]) -> List[str]: + expanded = list(paths) + for node in list(canvas.get("nodes") or []): + node_type = str(node.get("type") or "").strip().lower() + images = list(node.get("images") or []) + if node_type == "output": + owned_items = images + elif node_type == "smart-image": + owned_items = smart_owned_result_items(images, paths) + else: + owned_items = [] + if not any(json_references_media_path(item, path) for item in owned_items for path in paths): + continue + for item in owned_items: + for url in collect_local_media_urls(item): + candidate = generated_media_path_from_url(url) + if candidate and candidate not in expanded: + expanded.append(candidate) + return expanded + +def reset_canvas_result_nodes_for_media(canvas: Dict[str, Any], paths: List[str]) -> List[str]: + reset_ids = [] + updated_nodes = [] + for node in list(canvas.get("nodes") or []): + node = dict(node) + node_type = str(node.get("type") or "").strip().lower() + changed = False + if isinstance(node.get("generatedOutputs"), list): + outputs = list(node.get("generatedOutputs") or []) + kept_outputs = [ + item for item in outputs + if not any(json_references_media_path(item, path) for path in paths) + ] + if len(kept_outputs) != len(outputs): + node["generatedOutputs"] = kept_outputs + changed = True + if node_type in {"smart-image", "output"} and isinstance(node.get("images"), list): + images = list(node.get("images") or []) + owned_items = smart_owned_result_items(images, paths) if node_type == "smart-image" else images + owns_target = any( + json_references_media_path(item, path) for item in owned_items for path in paths + ) + kept_images = [ + item for item in images + if not ( + owns_target + and item in owned_items + and any(json_references_media_path(item, path) for path in paths) + ) + ] + if len(kept_images) != len(images): + node["images"] = kept_images + changed = True + if node_type == "output": + node["_pending"] = [] + node["imageComparisons"] = {} + if node_type == "smart-image": + node["pending"] = 0 + node["running"] = False + node["queued"] = False + for key in ( + "jimengPending", "pendingTasks", "runStartedAt", "runFinishedAt", + "runElapsedMs", "runTimerHidden", "outputKind", "w", "h", + ): + node.pop(key, None) + elif node_type == "image" and any(json_references_media_path(node.get("url"), path) for path in paths): + node["url"] = "" + node["mediaKind"] = "image" + node["name"] = "空白图片" + changed = True + if changed and node.get("id"): + reset_ids.append(str(node["id"])) + updated_nodes.append(node) + + if reset_ids: + canvas["nodes"] = updated_nodes + return reset_ids + +def delete_media_preview_cache(path: str) -> int: + try: + stat = os.stat(path) + except OSError: + return 0 + source = os.path.realpath(path) + removed = 0 + for width in range(0, 4097): + keys = [hashlib.sha1(f"{source}|{stat.st_mtime_ns}|{stat.st_size}|{width}|jpg".encode("utf-8", "ignore")).hexdigest() + ".jpg"] + if 64 <= width <= 2048: + preview_key = hashlib.sha1(f"{source}|{stat.st_mtime_ns}|{stat.st_size}|{width}".encode("utf-8", "ignore")).hexdigest() + keys.extend((preview_key + ".webp", preview_key + ".png")) + for name in keys: + cache_path = os.path.join(MEDIA_PREVIEW_DIR, name) + try: + if os.path.isfile(cache_path): + os.remove(cache_path) + removed += 1 + except OSError: + pass + return removed + def image_has_alpha(img: Image.Image) -> bool: if img.mode in ("RGBA", "LA"): return True @@ -8721,7 +9025,7 @@ def public_media_url_suffix() -> str: def local_asset_public_url(value: str) -> str: text = str(value or "").strip() - if not text.startswith(("/output/", "/assets/")): + if not text.startswith(("/output/", "/assets/", "/api/storage-files/")): return "" if not output_file_from_url(text): return "" @@ -8766,6 +9070,36 @@ async def openai_video_proxy_public_reference_url(ref) -> str: ) raise HTTPException(status_code=400, detail=f"参考图不是公网 URL,无法传给上游:{text[:160]}") +def local_media_reference_path(ref_url: str) -> str: + """将画布引用中的本机 URL 还原为后端可读取的本地媒体 URL。""" + text = str(ref_url or "").strip() + if not text: + return "" + parsed = urllib.parse.urlsplit(text) + if parsed.scheme in {"http", "https"}: + host = (parsed.hostname or "").lower() + is_local_host = ( + host in {"127.0.0.1", "localhost", "::1"} + or re.match(r"^(192\.168\.|10\.|172\.(1[6-9]|2\d|3[01])\.)", host) + or host.endswith(".local") + ) + if not is_local_host: + return "" + text = urllib.parse.unquote(parsed.path or "") + if parsed.path == "/api/media-preview": + nested = urllib.parse.parse_qs(parsed.query).get("url", [""])[0] + text = urllib.parse.unquote(nested or "") + elif parsed.scheme == "file": + text = urllib.parse.unquote(parsed.path or "") + elif parsed.scheme: + return "" + if parsed.path == "/api/media-preview": + nested = urllib.parse.parse_qs(parsed.query).get("url", [""])[0] + text = urllib.parse.unquote(nested or "") + if text.startswith(("/output/", "/assets/", "/api/storage-files/")): + return text + return "" + def openai_video_proxy_local_image_path(ref) -> str: raw = ref.get("url", "") if isinstance(ref, dict) else ref text = str(raw or "").strip() @@ -9357,11 +9691,12 @@ def local_media_path_for_cloud_upload(ref_url: str, allowed_prefixes=("image/", ref_url = str(ref_url or "").strip() if not ref_url: raise HTTPException(status_code=400, detail="没有可上传的媒体文件") - if ref_url.startswith("http://") or ref_url.startswith("https://"): - return "" - if not (ref_url.startswith("/output/") or ref_url.startswith("/assets/")): + local_ref = local_media_reference_path(ref_url) or ref_url + if not local_ref.startswith(("/output/", "/assets/", "/api/storage-files/")): + if ref_url.startswith(("http://", "https://")): + return "" raise HTTPException(status_code=400, detail="云端上传只支持画布里的本地图片或视频文件") - path = output_file_from_url(ref_url) + path = output_file_from_url(local_ref) if not path: raise HTTPException(status_code=404, detail="本地媒体文件不存在或已被删除") ct = content_type_for_path(path) @@ -9416,20 +9751,92 @@ async def upload_video_to_temp_sh(path: str, source_url: str) -> Dict[str, str]: except Exception as exc: raise HTTPException(status_code=502, detail=f"Temp.sh 上传异常:{exc}") from exc +async def upload_media_to_uguu(path: str, source_url: str) -> Dict[str, str]: + upload_url = os.getenv("UGUU_UPLOAD_URL", "https://uguu.se/upload.php").strip() or "https://uguu.se/upload.php" + ct = content_type_for_path(path) + try: + async with httpx.AsyncClient(timeout=httpx.Timeout(connect=20.0, read=600.0, write=600.0, pool=20.0), follow_redirects=True) as client: + with open(path, "rb") as fh: + files = {"files[]": (os.path.basename(path), fh, ct)} + response = await client.post(upload_url, files=files) + if not response.is_success: + raise HTTPException(status_code=response.status_code, detail=f"Uguu 上传失败:{response.text[:300]}") + try: + payload = response.json() + except Exception as exc: + raise HTTPException(status_code=502, detail=f"Uguu 返回了非 JSON 响应:{response.text[:300]}") from exc + items = payload.get("files") if isinstance(payload, dict) else None + direct_url = str((items[0] if isinstance(items, list) and items else {}).get("url") or "").strip() + if not re.match(r"^https?://", direct_url, re.I): + raise HTTPException(status_code=502, detail=f"Uguu 返回了无法识别的链接:{str(payload)[:300]}") + return {"url": direct_url, "source": source_url, "name": os.path.basename(path), "expires": "temporary", "service": "uguu"} + except HTTPException: + raise + except Exception as exc: + raise HTTPException(status_code=502, detail=f"Uguu 上传异常:{exc}") from exc + +async def verify_public_media_url(url: str, expected_content_type: str = "") -> Dict[str, str]: + parsed = urllib.parse.urlsplit(str(url or "").strip()) + if parsed.scheme not in {"http", "https"} or not parsed.netloc: + raise HTTPException(status_code=502, detail="上传服务返回的不是有效公网 URL") + expected = str(expected_content_type or "").lower().split(";", 1)[0].strip() + try: + async with httpx.AsyncClient( + timeout=httpx.Timeout(connect=20.0, read=60.0, write=30.0, pool=20.0), + follow_redirects=True, + headers={"User-Agent": "Infinite-Canvas/1.0"}, + ) as client: + async with client.stream("GET", str(url).strip()) as response: + if not response.is_success: + raise HTTPException(status_code=502, detail=f"公网媒体返回 HTTP {response.status_code}") + content_type = str(response.headers.get("content-type") or "").lower().split(";", 1)[0].strip() + if expected.startswith("image/"): + supported = {"image/png", "image/jpeg", "image/jpg", "image/webp"} + if content_type not in supported: + raise HTTPException(status_code=502, detail=f"公网地址返回的不是可用图片(Content-Type: {content_type or '未知'})") + elif expected.startswith("video/") and not content_type.startswith("video/"): + raise HTTPException(status_code=502, detail=f"公网地址返回的不是可用视频(Content-Type: {content_type or '未知'})") + first_chunk = b"" + async for chunk in response.aiter_bytes(): + first_chunk += chunk + if len(first_chunk) >= 32: + break + if expected.startswith("image/") and not first_chunk: + raise HTTPException(status_code=502, detail="公网地址返回了空图片") + return {"content_type": content_type, "status": str(response.status_code), "host": parsed.hostname or ""} + except HTTPException: + raise + except Exception as exc: + raise HTTPException(status_code=502, detail=f"公网媒体地址无法访问:{exc}") from exc + async def upload_local_video_to_cloud(ref_url: str, service: str = "auto") -> Dict[str, str]: ref_url = str(ref_url or "").strip() - if ref_url.startswith("http://") or ref_url.startswith("https://"): + local_ref = local_media_reference_path(ref_url) + if local_ref: + ref_url = local_ref + elif ref_url.startswith(("http://", "https://")): return {"url": ref_url, "source": ref_url, "service": "existing"} path = local_media_path_for_cloud_upload(ref_url) - service = str(service or os.getenv("CLOUD_VIDEO_UPLOAD_SERVICE", "auto") or "auto").strip().lower() - if service in {"litterbox", "catbox"}: - return await upload_video_to_litterbox(path, ref_url) - if service in {"temp", "temp.sh", "tempsh"}: - return await upload_video_to_temp_sh(path, ref_url) + requested_service = str(service or "").strip().lower() + service = requested_service if requested_service not in {"", "auto"} else str(os.getenv("CLOUD_VIDEO_UPLOAD_SERVICE", "auto") or "auto").strip().lower() + uploaders = { + "uguu": upload_media_to_uguu, + "litterbox": upload_video_to_litterbox, + "catbox": upload_video_to_litterbox, + "temp": upload_video_to_temp_sh, + "temp.sh": upload_video_to_temp_sh, + "tempsh": upload_video_to_temp_sh, + } + if service in uploaders: + result = await uploaders[service](path, ref_url) + result.update(await verify_public_media_url(result.get("url", ""), content_type_for_path(path))) + return result errors = [] - for name, func in (("litterbox", upload_video_to_litterbox), ("temp.sh", upload_video_to_temp_sh)): + for name, func in (("uguu", upload_media_to_uguu), ("litterbox", upload_video_to_litterbox), ("temp.sh", upload_video_to_temp_sh)): try: - return await func(path, ref_url) + result = await func(path, ref_url) + result.update(await verify_public_media_url(result.get("url", ""), content_type_for_path(path))) + return result except HTTPException as exc: errors.append(f"{name}: {exc.detail}") raise HTTPException(status_code=502, detail="云端上传失败:" + ";".join(errors)) @@ -11169,10 +11576,110 @@ async def generate_runninghub_video(payload, provider): local_urls = [await save_remote_video_to_output(url, prefix="rh_video_") for url in urls] return {"videos": local_urls, "task_id": task_id, "raw": result} -async def generate_ai_image(prompt, size, quality, model, reference_images=None, provider_id="comfly", aspect_ratio="", resolution=""): +async def generate_ai_image(prompt, size, quality, model, reference_images=None, provider_id="comfly", aspect_ratio="", resolution="", seed=None, batch_size=1, loras=None, mask_images=None): provider = get_api_provider(provider_id) if is_tudou_provider(provider): model = tudou_image_model_for_request(model) + if is_draw_things_provider(provider): + from CLI.macos.drawthings.draw_things_grpc import draw_things_model_supports_editing + + drawthings_references = [] + drawthings_masks = [] + seen_sources = set() + for reference in (reference_images or []): + item = dict(reference) if isinstance(reference, dict) else {"url": reference} + url = str(item.get("url") or "").strip() + is_mask = ( + str(item.get("role") or "").strip().lower() == "mask" + or bool(re.search(r"(?:^|_)mask\.(?:png|jpe?g|webp)$", str(item.get("name") or "").strip(), re.IGNORECASE)) + ) + # Canvas references normally use /output or /assets URLs. Resolve + # those to local files before passing them to the gRPC client; + # other providers keep their existing reference handling. + local_path = local_media_path_from_url(url) + target = drawthings_masks if is_mask else drawthings_references + if local_path: + target.append({ + "path": local_path, + "name": item.get("name") or os.path.basename(local_path), + "weight": item.get("weight", 1.0), + }) + elif url: + target.append(item) + if url: + seen_sources.add(url) + for reference in (mask_images or []): + item = dict(reference) if isinstance(reference, dict) else {"url": reference} + url = str(item.get("url") or "").strip() + if url and url in seen_sources: + continue + local_path = local_media_path_from_url(url) + if local_path: + drawthings_masks.append({ + "path": local_path, + "name": item.get("name") or os.path.basename(local_path), + "weight": item.get("weight", 1.0), + }) + elif url: + drawthings_masks.append(item) + editing_model = draw_things_model_supports_editing(model) + if not editing_model and len(drawthings_references) > 1: + raise HTTPException( + status_code=400, + detail="当前 Draw Things 模型只支持文生图或单图图生图,不能输入多张参考图。", + ) + if len(drawthings_masks) > 1: + raise HTTPException( + status_code=400, + detail="当前 Draw Things 请求只支持一张遮罩图。", + ) + if drawthings_masks and not drawthings_references: + raise HTTPException( + status_code=400, + detail="遮罩需要与一张输入图一起使用。", + ) + try: + from CLI.macos.drawthings.draw_things_grpc import generate_draw_things_image + # The provider's saved host:port overrides environment defaults. + request_options = { + "endpoint": provider.get("base_url") or "", + "seed": seed, + "batch_size": batch_size, + "loras": loras or [], + } + if drawthings_masks: + # Draw Things masks belong to ImageGenerationRequest.mask and + # must not be counted as a second ordinary reference image. + # For an editing model, the first image is the masked base + # image and the remaining references stay in the HintProto + # stack, matching the official Draw Things ComfyUI node. + if editing_model and len(drawthings_references) > 1: + request_options.update( + reference_images=drawthings_references[:1], + hint_images=drawthings_references[1:], + hint_type="shuffle", + mask_images=drawthings_masks, + ) + else: + request_options.update( + reference_images=drawthings_references, + mask_images=drawthings_masks, + ) + elif editing_model: + request_options.update( + hint_images=drawthings_references, + hint_type="shuffle" if drawthings_references else "", + ) + else: + request_options.update( + reference_images=drawthings_references, + ) + image_item, raw = await generate_draw_things_image( + prompt, size, model, **request_options + ) + except Exception as exc: + raise HTTPException(status_code=502, detail=str(exc)) from exc + return image_item, raw if provider["id"] == "modelscope": return await generate_modelscope_provider_image(prompt, size, model, reference_images, provider) if is_codex_provider(provider): @@ -11232,13 +11739,16 @@ async def post_openai_edits(edit_files=None): local_image_paths = [openai_video_proxy_local_image_path(ref) for ref in refs_for_proxy] has_local_images = any(local_image_paths) if has_local_images: - form_data = [(key, value) for key, value in body.items()] + form_data = dict(body) + remote_image_urls = [] for ref, local_path in zip(refs_for_proxy, local_image_paths): if local_path: continue url = await openai_video_proxy_public_reference_url(ref) if url: - form_data.append(("images", url)) + remote_image_urls.append(url) + if remote_image_urls: + form_data["images"] = remote_image_urls files = [] opened = [] try: @@ -11836,20 +12346,31 @@ async def upload_image(files: List[UploadFile] = File(...)): for file, content in files_content: success_count = 0 last_result = None + upload_errors = [] for addr in COMFYUI_INSTANCES: try: files_data = {'image': (file.filename, content, file.content_type)} - response = requests.post(f"http://{addr}/upload/image", files=files_data, timeout=5) + response = requests.post( + f"http://{addr}/upload/image", + files=files_data, + timeout=(10, COMFYUI_UPLOAD_TIMEOUT), + ) if response.status_code == 200: last_result = response.json() success_count += 1 + else: + upload_errors.append(f"{addr}: HTTP {response.status_code}") except Exception as e: print(f"Upload error for {addr}: {e}") + upload_errors.append(f"{addr}: {e}") if success_count > 0 and last_result: uploaded_files.append({"comfy_name": last_result.get("name", file.filename)}) else: - raise HTTPException(status_code=500, detail="Failed to upload to any backend") + detail = "Failed to upload to any backend" + if upload_errors: + detail += f": {'; '.join(upload_errors[:3])}" + raise HTTPException(status_code=502, detail=detail) return {"files": uploaded_files} @@ -11892,8 +12413,7 @@ async def upload_ai_reference(files: List[UploadFile] = File(...)): ext = ".bin" filename = f"ai_ref_{uuid.uuid4().hex[:12]}{ext}" path = output_path_for(filename, "input") - with open(path, "wb") as f: - f.write(content) + write_uploaded_content_atomic(path, content) uploaded.append({"url": output_url_for(filename, "input"), "name": file.filename or filename, "kind": kind, "mime": content_type}) return {"files": uploaded} @@ -11925,8 +12445,7 @@ async def upload_ai_base64(payload: Base64UploadRequest): kind, ext = "image", ".png" filename = f"ai_ref_{uuid.uuid4().hex[:12]}{ext}" path = output_path_for(filename, "input") - with open(path, "wb") as f: - f.write(content) + write_uploaded_content_atomic(path, content) return {"files": [{"url": output_url_for(filename, "input"), "name": payload.name or filename, "kind": kind}]} @app.post("/api/comfyui/upload-base64") @@ -11979,6 +12498,25 @@ def _local_upload_kind_ext(filename, content_type): return "image", ext return None, ext +def write_uploaded_content_atomic(path: str, content: bytes): + directory = os.path.dirname(path) or "." + prefix = f".{os.path.basename(path)}." + temp_path = "" + try: + fd, temp_path = tempfile.mkstemp(prefix=prefix, suffix=".part", dir=directory) + with os.fdopen(fd, "wb") as handle: + handle.write(content) + handle.flush() + os.fsync(handle.fileno()) + os.replace(temp_path, path) + temp_path = "" + finally: + if temp_path: + try: + os.remove(temp_path) + except OSError: + pass + def _local_upload_display_name(filename): # 文件名形如 up__<原始名>;去掉前缀还原展示名 base = os.path.basename(str(filename or "")) @@ -13225,6 +13763,26 @@ async def ai_models(): async def api_providers(): return {"providers": public_api_providers()} +@app.get("/api/drawthings/models") +async def drawthings_models(): + provider = next((item for item in load_api_providers() if item.get("id") == "drawthings"), None) + if not provider: + return { + "connected": False, + "models": [], + "model_metadata": [], + "loras": [], + "message": "Draw Things provider 尚未添加", + } + result = await fetch_models_from_upstream(provider.get("base_url") or "", "", "grpc", "openai") + return { + "connected": bool(result.get("ok")), + "models": result.get("image_models") or [], + "model_metadata": result.get("model_metadata") or [], + "loras": result.get("loras") or [], + "message": result.get("message") or "", + } + @app.put("/api/providers") async def save_providers(payload: List[ApiProviderPayload]): providers = [] @@ -13323,6 +13881,8 @@ def protocol_from_payload(payload): return "runninghub" if provider_id == "jimeng": return "jimeng" + if provider_id == "drawthings": + return "grpc" base_url = str(getattr(payload, "base_url", "") or "").strip().lower() if "runninghub.cn" in base_url or "runninghub.ai" in base_url: return "runninghub" @@ -13333,6 +13893,8 @@ def api_key_from_payload(payload, protocol: str = ""): explicit = str(getattr(payload, "api_key", "") or "").strip() provider_id = str(getattr(payload, "provider_id", "") or "").strip().lower() protocol = str(protocol or protocol_from_payload(payload) or "").strip().lower() + if protocol == "grpc": + return "" if explicit: return explicit if provider_id: @@ -13585,6 +14147,27 @@ async def test_provider_connection(payload: TestConnectionPayload): "protocol": "runninghub", "raw": payload_models.get("raw"), } + if protocol == "grpc": + try: + from CLI.macos.drawthings.draw_things_grpc import list_draw_things_models + result = await list_draw_things_models(payload.base_url) + except Exception as exc: + result = {"connected": False, "models": [], "error": str(exc)} + models = result.get("models") or [] + return { + "ok": bool(result.get("connected")), + "protocol": "grpc", + "status": 200 if result.get("connected") else 0, + "message": "Draw Things gRPCServerCLI 已连接" if result.get("connected") else (result.get("error") or "Draw Things gRPCServerCLI 未连接"), + "model_count": len(models), + "image_models": models, + "chat_models": [], + "video_models": [], + "all": models, + "model_metadata": result.get("model_metadata") or [], + "loras": result.get("loras") or [], + "raw": result, + } base_url = (payload.base_url or "").strip().rstrip("/") if not base_url: raise HTTPException(status_code=400, detail="请先填写请求地址") @@ -13829,6 +14412,27 @@ async def fetch_models_from_upstream(base_url: str, api_key: str, protocol: str payload = gemini_cli_models_payload(raw={"status": status}) payload["message"] = status.get("message") or payload["message"] return payload + if protocol == "grpc": + try: + from CLI.macos.drawthings.draw_things_grpc import list_draw_things_models + result = await list_draw_things_models(base_url) + except Exception as exc: + result = {"connected": False, "models": [], "error": str(exc)} + models = result.get("models") or [] + return { + "ok": bool(result.get("connected")), + "protocol": "grpc", + "status": 200 if result.get("connected") else 0, + "message": "Draw Things gRPCServerCLI 已连接" if result.get("connected") else (result.get("error") or "Draw Things gRPCServerCLI 未连接"), + "total": len(models), + "image_models": models, + "chat_models": [], + "video_models": [], + "all": models, + "model_metadata": result.get("model_metadata") or [], + "loras": result.get("loras") or [], + "raw": result, + } if protocol == "jimeng": return { "total": len(JIMENG_DEFAULT_IMAGE_MODELS) + len(JIMENG_DEFAULT_VIDEO_MODELS), @@ -13961,6 +14565,8 @@ async def fetch_upstream_models(provider_id: str): return await fetch_models_from_upstream("", "", "codex", provider.get("image_request_mode") or "openai") if is_gemini_cli_provider(provider): return await fetch_models_from_upstream("", "", "gemini-cli", provider.get("image_request_mode") or "openai") + if is_draw_things_provider(provider): + return await fetch_models_from_upstream(provider.get("base_url") or "", "", "grpc", provider.get("image_request_mode") or "openai") api_key = os.getenv(runninghub_wallet_key_env(), "") if provider["id"] == "runninghub" else "" if not api_key: api_key = provider_env_key_value(provider["id"]) @@ -13974,9 +14580,13 @@ async def build_online_image_result(payload: OnlineImageRequest): model = selected_model(payload.model, default_model) request_size = snap_size_to_multiple(payload.size, 16) refs = [ref.dict() for ref in payload.reference_images if ref.url] + mask_refs = [ref.dict() for ref in payload.mask_images if ref.url] image_refs = image_references(refs) count = max(1, min(8, int(payload.n or 1))) - operation = str(payload.operation or "").strip().lower() + batch_size = max(1, min(8, int(payload.batch_size or 1))) if is_draw_things_provider(provider) else 1 + if batch_size > 1: + count = 1 + operation = str(getattr(payload, "operation", "") or "").strip().lower() if operation == "upscale": if not is_jimeng_provider(provider): raise HTTPException(status_code=400, detail="图片放大目前仅支持即梦(Dreamina)平台") @@ -13989,7 +14599,8 @@ async def generate_one(): else: image_data, raw_item = await generate_ai_image( payload.prompt, request_size, payload.quality, model, image_refs, provider["id"], - payload.aspect_ratio, payload.resolution, + payload.aspect_ratio, payload.resolution, payload.seed, batch_size=batch_size, + loras=payload.loras, mask_images=mask_refs, ) try: image_items = extract_images(raw_item) if isinstance(raw_item, dict) else [image_data] @@ -14033,7 +14644,7 @@ async def generate_one(): "provider_name": provider.get("name") or provider["id"], "task_id": extract_task_id(raw) if isinstance(raw, dict) else None, "request_id": raw.get("id") if isinstance(raw, dict) else None, - "params": {"provider_id": provider["id"], "model": model, "size": request_size, "requested_size": payload.size, "quality": payload.quality, "n": count, "reference_images": refs}, + "params": {"provider_id": provider["id"], "model": model, "size": request_size, "requested_size": payload.size, "aspect_ratio": payload.aspect_ratio, "resolution": payload.resolution, "quality": payload.quality, "n": count, "batch_size": batch_size, "reference_images": refs, "mask_images": mask_refs}, "raw_usage": raw.get("usage") if isinstance(raw, dict) else None, } save_to_history(result) @@ -14914,13 +15525,55 @@ def agnes_video_frame_count(duration, fps=24): return min(441, max(9, 8 * n + 1)), frame_rate async def agnes_video_image_url(ref): - url = str(getattr(ref, "url", "") or "").strip() + if isinstance(ref, dict): + url = str(ref.get("url", "") or "").strip() + original_urls = [ref.get("originalLocalUrl", ""), ref.get("original_url", ""), ref.get("source_url", "")] + else: + url = str(getattr(ref, "url", "") or "").strip() + original_urls = [getattr(ref, "originalLocalUrl", ""), getattr(ref, "original_url", ""), getattr(ref, "source_url", "")] if not url: return "" - if url.startswith("http://") or url.startswith("https://"): - return url - uploaded = await upload_local_video_to_cloud(url, "auto") - return uploaded.get("url") or "" + + local_ref = "" + for value in original_urls: + local_ref = local_media_reference_path(value) + if local_ref: + break + local_ref = local_ref or local_media_reference_path(url) + remote_error = "" + if url.startswith(("http://", "https://")): + try: + await verify_public_media_url(url, "image/png") + return url + except HTTPException as exc: + remote_error = str(exc.detail) + + if local_ref: + upload_error = "" + try: + uploaded = await upload_local_video_to_cloud(local_ref, "auto") + uploaded_url = str((uploaded or {}).get("url") or "").strip() + if uploaded_url.startswith(("http://", "https://")): + return uploaded_url + except HTTPException as exc: + upload_error = str(exc.detail) + public_url = local_asset_public_url(local_ref) + if public_url: + try: + await verify_public_media_url(public_url, "image/png") + return public_url + except HTTPException as exc: + upload_error = f"{upload_error or '云端上传失败'};公网回源地址无效:{exc.detail}" + raise HTTPException( + status_code=400, + detail=f"Agnes 参考图无法转成可下载的公网图片:{upload_error[:200] or remote_error[:200] or '云端上传失败'}。请检查网络后重试。" + ) + + if url.startswith(("http://", "https://")): + raise HTTPException(status_code=400, detail=f"Agnes 参考图公网地址无效:{remote_error[:240] or '无法下载图片'}") + if url.startswith("data:image/"): + raise HTTPException(status_code=400, detail="Agnes 图生视频暂不接受 data:image;请使用画布中的本地图片或公网图片 URL") + raise HTTPException(status_code=400, detail=f"Agnes 参考图地址无法识别:{url[:160]}") async def wait_for_agnes_video_task(client, provider, video_id, model): base_url = video_api_root(provider) @@ -17309,6 +17962,83 @@ async def update_canvas(canvas_id: str, payload: CanvasSaveRequest): await manager.broadcast_canvas_updated(canvas_id, int(canvas.get("updated_at") or now_ms()), payload.client_id) return {"canvas": canvas} +@app.post("/api/canvases/{canvas_id}/logs/delete") +async def delete_canvas_log(canvas_id: str, payload: DeleteCanvasLogRequest): + log_id = str(payload.log_id or "").strip() + if not log_id: + raise HTTPException(status_code=400, detail="缺少日志 ID") + + def remove_log_record(): + with CANVAS_LOCK: + canvas = load_canvas(canvas_id) + current_updated_at = int(canvas.get("updated_at") or 0) + if payload.base_updated_at and current_updated_at and int(payload.base_updated_at) < current_updated_at: + raise HTTPException(status_code=409, detail={ + "message": "画布已被其他页面更新,请刷新后重试。", + "canvas": canvas, + "updated_at": current_updated_at, + }) + logs = list(canvas.get("logs") or []) + target = next((item for item in logs if str(item.get("id") or "") == log_id), None) + if not target: + raise HTTPException(status_code=404, detail="生成日志不存在") + + candidate_paths = [] + if payload.delete_unreferenced_media: + for url in collect_local_media_urls(target.get("outputs") or []): + path = generated_media_path_from_url(url) + if path and path not in candidate_paths: + candidate_paths.append(path) + + reset_node_ids = [] + if payload.reset_referencing_nodes and candidate_paths: + reset_node_ids = reset_canvas_result_nodes_for_media(canvas, candidate_paths) + + canvas["logs"] = [item for item in logs if str(item.get("id") or "") != log_id] + save_canvas(canvas) + return canvas, candidate_paths, reset_node_ids + + canvas, candidate_paths, reset_node_ids = await asyncio.to_thread(remove_log_record) + + def cleanup_unreferenced_media(): + removed_files = [] + skipped_referenced = [] + removed_previews = 0 + deletable_paths = [] + for path in candidate_paths: + if persisted_json_references_media_path(path): + skipped_referenced.append(os.path.basename(path)) + continue + deletable_paths.append(path) + prune_generation_history_for_media(deletable_paths) + for path in deletable_paths: + try: + removed_previews += delete_media_preview_cache(path) + os.remove(path) + removed_files.append(os.path.basename(path)) + except OSError: + skipped_referenced.append(os.path.basename(path)) + return removed_files, skipped_referenced, removed_previews + + removed_files = [] + skipped_referenced = [] + removed_previews = 0 + if payload.delete_unreferenced_media: + def locked_cleanup(): + with CANVAS_LOCK: + return cleanup_unreferenced_media() + removed_files, skipped_referenced, removed_previews = await asyncio.to_thread(locked_cleanup) + + await manager.broadcast_canvas_updated(canvas_id, int(canvas.get("updated_at") or now_ms())) + return { + "ok": True, + "canvas": canvas, + "removed_files": removed_files, + "removed_previews": removed_previews, + "reset_node_ids": reset_node_ids, + "skipped_referenced": skipped_referenced, + } + @app.delete("/api/canvases/{canvas_id}") async def delete_canvas(canvas_id: str): canvas = load_canvas_any(canvas_id) @@ -18246,17 +18976,46 @@ def generate(req: GenerateRequest): "_meta": node_inputs.get("_meta") if isinstance(node_inputs.get("_meta"), dict) else {"title": str(node_inputs.get("class_type"))}, } + # Custom workflows can put their seed on any node, so the generic + # random value above is not necessarily the value sent to ComfyUI. + # Collect the numeric seed inputs after applying all frontend params. + seed_values = {} + for node_id, node_data in workflow.items(): + inputs = node_data.get("inputs") if isinstance(node_data, dict) else None + if not isinstance(inputs, dict): + continue + for input_name, value in inputs.items(): + normalized_name = str(input_name or "").strip().lower().replace("-", "_").replace(" ", "_") + if normalized_name not in {"seed", "noise_seed", "random_seed"}: + continue + try: + seed_values[f"{node_id}:{input_name}"] = int(float(value)) + except (TypeError, ValueError): + continue + if seed_values: + seed = next(iter(seed_values.values())) + p = {"prompt": workflow, "client_id": CLIENT_ID} data = json.dumps(p).encode('utf-8') try: - post_req = urllib.request.Request(f"http://{target_backend}/prompt", data=data) - prompt_id = json.loads(urllib.request.urlopen(post_req, timeout=10).read())['prompt_id'] + post_req = urllib.request.Request( + f"http://{target_backend}/prompt", + data=data, + headers={ + "Content-Type": "application/json", + "Connection": "close", + }, + ) + prompt_id = json.loads( + urllib.request.urlopen(post_req, timeout=COMFYUI_HTTP_TIMEOUT).read() + )['prompt_id'] except urllib.error.HTTPError as e: error_body = e.read().decode('utf-8') raise Exception(comfy_prompt_error_message(e.code, error_body)) history_data = None - for i in range(COMFYUI_HISTORY_TIMEOUT): + history_deadline = time.monotonic() + COMFYUI_HISTORY_TIMEOUT + while time.monotonic() < history_deadline: try: res = get_comfy_history(target_backend, prompt_id) if prompt_id in res: @@ -18264,7 +19023,7 @@ def generate(req: GenerateRequest): break except Exception: pass - time.sleep(1) + time.sleep(min(1, max(0, history_deadline - time.monotonic()))) if not history_data: raise Exception("ComfyUI 渲染超时") @@ -18356,6 +19115,7 @@ def _class_type_of(nid): "items": local_items, "outputs": local_urls, "seed": seed, + "seed_values": seed_values, "timestamp": current_timestamp, "type": req.type, "workflow_json": req.workflow_json, @@ -18400,6 +19160,8 @@ class WorkflowField(BaseModel): step: Optional[float] = None options: List[str] = [] random_enabled: bool = False + auto: str = "" + image_count_min: Optional[int] = None class WorkflowConfig(BaseModel): title: str = "" diff --git a/static/angle.html b/static/angle.html index 03ab292f5..64c379dab 100644 --- a/static/angle.html +++ b/static/angle.html @@ -17,21 +17,21 @@ } catch(e) {} })(); - - - - - - + + + + + + - + diff --git a/static/api-settings.html b/static/api-settings.html index e95813e1a..f56fdf0ae 100644 --- a/static/api-settings.html +++ b/static/api-settings.html @@ -16,12 +16,12 @@ } catch(e) {} })(); - - - - - - + + + + + + @@ -46,6 +46,7 @@ 即梦 CLI GPT CLI Antigravity CLI + Draw Things gRPC 需要先安装 CLI文件夹中的依赖。 @@ -80,7 +81,7 @@ - 请求地址 + 请求地址 国内默认请求地址:https://api-inference.modelscope.cn/v1 @@ -272,6 +273,7 @@ 即梦 CLI OpenAI Codex CLI Antigravity CLI + Draw Things gRPCServerCLI @@ -332,7 +334,7 @@ - + 生图模型 @@ -426,7 +428,7 @@ 全部 0 生图 0 - LLM 0 + LLM 0 视频 0 @@ -566,6 +568,6 @@ - +