【z-image】白嫖modal的30$自部署z-image 增加宋玉Lora版(额度用完了)

各位佬友白嫖之前先去我的github 项目点个星星吧,到100个我就可以领免费域名了。

ok,下面是正题。打开下面这个链接
https://fantastic-bubblegum-fbc36a.netlify.app/
就可以直接用了。一天十次(清空缓存可以刷新次数,防君子不防小人)给更多佬友机会用。
有动手能力的可以跟着下面的教程自己部署一个。

  1. 注册modal
    https://modal.com/signup?next=%2Fapps 无 AFF
  2. 找个文件夹 python3 -m venv modal (python 应该都有吧,没有的话先安装python)
  3. source modal/bin/activate
  4. pip install modal
  5. modal setup 就会跳转到浏览器,让你认证一下
  6. 新建一个 z_image_workflow.json 写入如下代码
{
  "6": {
    "inputs": {
      "text": "cute anime style girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron",
      "clip": ["18", 0]
    },
    "class_type": "CLIPTextEncode",
    "_meta": {
      "title": "CLIP Text Encode (Positive Prompt)"
    }
  },
  "7": {
    "inputs": {
      "text": "blurry ugly bad",
      "clip": ["18", 0]
    },
    "class_type": "CLIPTextEncode",
    "_meta": {
      "title": "CLIP Text Encode (Negative Prompt)"
    }
  },
  "8": {
    "inputs": {
      "samples": ["3", 0],
      "vae": ["17", 0]
    },
    "class_type": "VAEDecode",
    "_meta": {
      "title": "VAE Decode"
    }
  },
  "9": {
    "inputs": {
      "filename_prefix": "ComfyUI",
      "images": ["8", 0]
    },
    "class_type": "SaveImage",
    "_meta": {
      "title": "Save Image"
    }
  },
  "13": {
    "inputs": {
      "width": 1024,
      "height": 1024,
      "batch_size": 1
    },
    "class_type": "EmptySD3LatentImage",
    "_meta": {
      "title": "EmptySD3LatentImage"
    }
  },
  "16": {
    "inputs": {
      "unet_name": "z_image_turbo_bf16.safetensors",
      "weight_dtype": "default"
    },
    "class_type": "UNETLoader",
    "_meta": {
      "title": "Load Diffusion Model"
    }
  },
  "17": {
    "inputs": {
      "vae_name": "ae.safetensors"
    },
    "class_type": "VAELoader",
    "_meta": {
      "title": "Load VAE"
    }
  },
  "18": {
    "inputs": {
      "clip_name": "qwen_3_4b.safetensors",
      "type": "lumina2",
      "device": "default"
    },
    "class_type": "CLIPLoader",
    "_meta": {
      "title": "Load CLIP"
    }
  },
  "3": {
    "inputs": {
      "seed": 47447417949230,
      "steps": 9,
      "cfg": 1,
      "sampler_name": "euler",
      "scheduler": "simple",
      "denoise": 1,
      "model": ["16", 0],
      "positive": ["6", 0],
      "negative": ["7", 0],
      "latent_image": ["13", 0]
    },
    "class_type": "KSampler",
    "_meta": {
      "title": "KSampler"
    }
  }
}

  1. 新建一个 main.py 写入如下代码
# ---
# deploy: true
# cmd: ["modal", "serve", "06_gpu_and_ml/comfyui/comfyapp.py"]
# ---

# # Run Flux on ComfyUI as an API

# In this example, we show you how to turn a [ComfyUI](https://github.com/comfyanonymous/ComfyUI) workflow into a scalable API endpoint.

# ## Quickstart

# To run this simple text-to-image [Flux Schnell workflow](https://github.com/modal-labs/modal-examples/blob/main/06_gpu_and_ml/comfyui/workflow_api.json) as an API:

# 1. Deploy ComfyUI behind a web endpoint:

# ```bash
# modal deploy 06_gpu_and_ml/comfyui/comfyapp.py
# ```

# 2. In another terminal, run inference:

# ```bash
# python 06_gpu_and_ml/comfyui/comfyclient.py --modal-workspace $(modal profile current) --prompt "Surreal dreamscape with floating islands, upside-down waterfalls, and impossible geometric structures, all bathed in a soft, ethereal light"
# ```

# ![example comfyui image](https://modal-cdn.com/cdnbot/flux_gen_imagesenr_0w3_209b7170.webp)

# The first inference will take ~1m since the container needs to launch the ComfyUI server and load Flux into memory. Successive calls on a warm container should take a few seconds.

# ## Installing ComfyUI

# We use [comfy-cli](https://github.com/Comfy-Org/comfy-cli) to install ComfyUI and its dependencies.

import json
import subprocess
import uuid
from pathlib import Path
from typing import Dict

import modal
import modal.experimental

image = (  # build up a Modal Image to run ComfyUI, step by step
    modal.Image.debian_slim(  # start from basic Linux with Python
        python_version="3.11"
    )
    .apt_install("git")  # install git to clone ComfyUI
    .uv_pip_install("fastapi[standard]==0.115.4")  # install web dependencies
    .uv_pip_install("comfy-cli")  # install comfy-cli
    .run_commands(  # use comfy-cli to install ComfyUI and its dependencies
        "comfy --skip-prompt install --fast-deps --nvidia"
    )
    .run_commands(  # download the WAS Node Suite custom node pack
        "comfy node install was-node-suite-comfyui"
    )
)

# ## Downloading custom nodes

# We'll also use `comfy-cli` to download custom nodes, in this case the popular [WAS Node Suite](https://github.com/WASasquatch/was-node-suite-comfyui).

# Use the [ComfyUI Registry](https://registry.comfy.org/) to find the specific custom node name to use with this command.

# See [this post](https://modal.com/blog/comfyui-custom-nodes) for more examples
# on how to install popular custom nodes like ComfyUI Impact Pack and ComfyUI IPAdapter Plus.

# ## Downloading models

# `comfy-cli` also supports downloading models, but we've found it's faster to use
# [`hf_hub_download`](https://huggingface.co/docs/huggingface_hub/en/guides/download#download-a-single-file)
# directly by:

# 1. Enabling [faster downloads](https://huggingface.co/docs/huggingface_hub/en/guides/download#faster-downloads)
# 2. Mounting the cache directory to a [Volume](https://modal.com/docs/guide/volumes)

# By persisting the cache to a Volume, you avoid re-downloading the models every time you rebuild your image.
# For more on storing model weights on Modal, see
# [this guide](https://modal.com/docs/guide/model-weights).


def hf_download():
    from huggingface_hub import hf_hub_download

    diffusion_model = hf_hub_download(
        repo_id="Comfy-Org/z_image_turbo",
        filename="split_files/diffusion_models/z_image_turbo_bf16.safetensors",
        cache_dir="/cache",
    )
    ae = hf_hub_download(
        repo_id="Comfy-Org/z_image_turbo",
        filename="split_files/vae/ae.safetensors",
        cache_dir="/cache",
    )
    text_encoder = hf_hub_download(
        repo_id="Comfy-Org/z_image_turbo",
        filename="split_files/text_encoders/qwen_3_4b.safetensors",
        cache_dir="/cache",
    )

    # symlink the model to the right ComfyUI directory
    subprocess.run(
        f"ln -s {diffusion_model} /root/comfy/ComfyUI/models/diffusion_models/z_image_turbo_bf16.safetensors",
        shell=True,
        check=True,
    )
    subprocess.run(
        f"ln -s {ae} /root/comfy/ComfyUI/models/vae/ae.safetensors",
        shell=True,
        check=True,
    )
    subprocess.run(
        f"ln -s {text_encoder} /root/comfy/ComfyUI/models/text_encoders/qwen_3_4b.safetensors",
        shell=True,
        check=True,
    )


vol = modal.Volume.from_name("hf-hub-cache", create_if_missing=True)

image = (
    # install huggingface_hub with hf_xet support to speed up downloads
    image.uv_pip_install("huggingface-hub==0.36.0")
    .env({"HF_XET_HIGH_PERFORMANCE": "1"})
    .run_function(
        hf_download,
        # persist the HF cache to a Modal Volume so future runs don't re-download models
        volumes={"/cache": vol},
    )
)

# Lastly, copy the ComfyUI workflow JSON to the container.
image = image.add_local_file(
    Path(__file__).parent / "z_image_workflow.json", "/root/z_image_workflow.json"
)


# ## Running ComfyUI interactively

# Spin up an interactive ComfyUI server by wrapping the `comfy launch` command in a Modal Function
# and serving it as a [web server](https://modal.com/docs/guide/webhooks#non-asgi-web-servers).

app = modal.App(name="comfyui-online", image=image)


@app.function(
    max_containers=1,  # limit interactive session to 1 container
    gpu="L40S",  # good starter GPU for inference
    volumes={"/cache": vol},  # mounts our cached models
)
@modal.concurrent(
    max_inputs=10
)  # required for UI startup process which runs several API calls concurrently
@modal.web_server(8000, startup_timeout=60)
def ui():
    subprocess.Popen("comfy launch -- --listen 0.0.0.0 --port 8000", shell=True)


# At this point you can run `modal serve 06_gpu_and_ml/comfyui/comfyapp.py` and open the UI in your browser for the classic ComfyUI experience.

# Remember to **close your UI tab** when you are done developing.
# This will close the connection with the container serving ComfyUI and you will stop being charged.

# ## Running ComfyUI as an API

# To run a workflow as an API:

# 1. Stand up a "headless" ComfyUI server in the background when the app starts.

# 2. Define an `infer` method that takes in a workflow path and runs the workflow on the ComfyUI server.

# 3. Create a web handler `api` as a web endpoint, so that we can run our workflow as a service and accept inputs from clients.

# We group all these steps into a single Modal `cls` object, which we'll call `ComfyUI`.


@app.cls(
    scaledown_window=60,  # 1 minute container keep alive after it processes an input
    gpu="L40S",
    volumes={"/cache": vol},
)
@modal.concurrent(max_inputs=5)  # run 5 inputs per container
class ComfyUI:
    port: int = 8000

    @modal.enter()
    def launch_comfy_background(self):
        # launch the ComfyUI server exactly once when the container starts
        cmd = f"comfy launch --background -- --port {self.port}"
        subprocess.run(cmd, shell=True, check=True)

    @modal.method()
    def infer(self, workflow_path: str = "/root/z_image_workflow.json"):
        # sometimes the ComfyUI server stops responding (we think because of memory leaks), so this makes sure it's still up
        self.poll_server_health()

        # runs the comfy run --workflow command as a subprocess
        cmd = f"comfy run --workflow {workflow_path} --wait --timeout 1200 --verbose"
        subprocess.run(cmd, shell=True, check=True)

        # completed workflows write output images to this directory
        output_dir = "/root/comfy/ComfyUI/output"

        # looks up the name of the output image file based on the workflow
        workflow = json.loads(Path(workflow_path).read_text())
        file_prefix = [
            node.get("inputs", {}).get("filename_prefix")
            for node in workflow.values()
            if node.get("class_type") == "SaveImage"
        ][0]

        # returns the image as bytes
        for f in Path(output_dir).iterdir():
            if f.name.startswith(file_prefix):
                return f.read_bytes()

    @modal.fastapi_endpoint(method="POST")
    def api(self, item: Dict):
        from fastapi import Response

        # load the workflow template
        workflow_data = json.loads(
            Path("/root/z_image_workflow.json").read_text()
        )

        # insert the user's prompt into node 6 (positive prompt)
        workflow_data["6"]["inputs"]["text"] = item["prompt"]

        # give the output image a unique id per client request
        client_id = uuid.uuid4().hex
        workflow_data["9"]["inputs"]["filename_prefix"] = client_id

        # save this updated workflow to a new file
        new_workflow_file = f"/root/{client_id}.json"
        with open(new_workflow_file, "w") as f:
            json.dump(workflow_data, f)

        # run inference on the currently running container
        img_bytes = self.infer.local(new_workflow_file)

        return Response(img_bytes, media_type="image/jpeg")

    def poll_server_health(self) -> Dict:
        import socket
        import urllib

        try:
            # check if the server is up (response should be immediate)
            req = urllib.request.Request(f"http://127.0.0.1:{self.port}/system_stats")
            urllib.request.urlopen(req, timeout=5)
            print("ComfyUI server is healthy")
        except (socket.timeout, urllib.error.URLError) as e:
            # if no response in 5 seconds, stop the container
            print(f"Server health check failed: {str(e)}")
            modal.experimental.stop_fetching_inputs()

            # all queued inputs will be marked "Failed", so you need to catch these errors in your client and then retry
            raise Exception("ComfyUI server is not healthy, stopping container")

  1. 执行 modal deploy main.py 会输出如下类似内容, 注意输出的API endpoint,下面会用到。
(modal) mac@MacBook-Pro deploy % modal deploy main.py
✓ Created objects.
├── 🔨 Created mount /Users/qcy/proj/modal-examples/deploy/main.py
├── 🔨 Created mount z_image_workflow.json
├── 🔨 Created function hf_download.
├── 🔨 Created web function ui => https://mbb-61875--comfyui-online-ui.modal.run
├── 🔨 Created function ComfyUI.*.
└── 🔨 Created web endpoint for ComfyUI.api => https://mbb-61875--comfyui-online-comfyui-api.modal.run (**注意这里,这里就是API endpoint**)
✓ App deployed in 8.910s! 🎉

View Deployment: https://modal.com/apps/mbb-61875/main/deployed/comfyui-online
  1. 创建一个 index.html 写入如下内容(记得添加endpoint, 看到在这里填入就是了)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ComfyUI Image Generator</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .container {
            background: white;
            border-radius: 20px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            max-width: 800px;
            width: 100%;
            padding: 40px;
        }

        h1 {
            color: #333;
            margin-bottom: 10px;
            font-size: 2em;
            text-align: center;
        }

        .subtitle {
            color: #666;
            text-align: center;
            margin-bottom: 30px;
            font-size: 0.9em;
        }

        .form-group {
            margin-bottom: 25px;
        }

        label {
            display: block;
            margin-bottom: 8px;
            color: #333;
            font-weight: 600;
            font-size: 0.95em;
        }

        textarea {
            width: 100%;
            padding: 12px 15px;
            border: 2px solid #e0e0e0;
            border-radius: 10px;
            font-size: 1em;
            font-family: inherit;
            resize: vertical;
            min-height: 100px;
            transition: border-color 0.3s;
        }

        textarea:focus {
            outline: none;
            border-color: #667eea;
        }

        .button-group {
            display: flex;
            gap: 15px;
            margin-bottom: 30px;
        }

        button {
            flex: 1;
            padding: 15px 30px;
            border: none;
            border-radius: 10px;
            font-size: 1em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
        }

        .btn-generate {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }

        .btn-generate:hover:not(:disabled) {
            transform: translateY(-2px);
            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
        }

        .btn-generate:disabled {
            opacity: 0.6;
            cursor: not-allowed;
        }

        .btn-clear {
            background: #f5f5f5;
            color: #333;
        }

        .btn-clear:hover {
            background: #e0e0e0;
        }

        .loading {
            display: none;
            text-align: center;
            margin: 20px 0;
        }

        .loading.active {
            display: block;
        }

        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #667eea;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            animation: spin 1s linear infinite;
            margin: 0 auto 15px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .loading-text {
            color: #666;
            font-size: 0.95em;
        }

        .result {
            display: none;
            margin-top: 30px;
        }

        .result.active {
            display: block;
        }

        .result-title {
            color: #333;
            margin-bottom: 15px;
            font-size: 1.2em;
            font-weight: 600;
        }

        .image-container {
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
            margin-bottom: 15px;
        }

        .result-image {
            width: 100%;
            height: auto;
            display: block;
        }

        .download-btn {
            width: 100%;
            background: #4CAF50;
            color: white;
            padding: 12px;
            border: none;
            border-radius: 10px;
            font-size: 1em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
        }

        .download-btn:hover {
            background: #45a049;
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(76, 175, 80, 0.3);
        }

        .error {
            display: none;
            background: #ffebee;
            color: #c62828;
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
            border-left: 4px solid #c62828;
        }

        .error.active {
            display: block;
        }

        .info {
            background: #e3f2fd;
            color: #1565c0;
            padding: 12px 15px;
            border-radius: 8px;
            margin-bottom: 20px;
            font-size: 0.9em;
            border-left: 4px solid #1565c0;
        }

        @media (max-width: 600px) {
            .container {
                padding: 25px;
            }

            h1 {
                font-size: 1.5em;
            }

            .button-group {
                flex-direction: column;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>ComfyUI Image Generator</h1>
        <p class="subtitle">使用 AI 生成精美图片</p>

        <div class="info">
            提示:输入详细的图片描述,AI 将为您生成对应的图片
        </div>

        <div class="form-group">
            <label for="prompt">图片提示词</label>
            <textarea
                id="prompt"
                placeholder="例如:Surreal dreamscape with floating islands, upside-down waterfalls, and impossible geometric structures, all bathed in a soft, ethereal light"
            ></textarea>
        </div>

        <div class="button-group">
            <button class="btn-generate" id="generateBtn">生成图片</button>
            <button class="btn-clear" id="clearBtn">清空</button>
        </div>

        <div class="loading" id="loading">
            <div class="spinner"></div>
            <p class="loading-text">正在生成图片,请稍候...</p>
        </div>

        <div class="error" id="error"></div>

        <div class="result" id="result">
            <h3 class="result-title">生成结果</h3>
            <div class="image-container">
                <img id="resultImage" class="result-image" alt="Generated Image">
            </div>
            <button class="download-btn" id="downloadBtn">下载图片</button>
        </div>
    </div>

    <script>
        const API_ENDPOINT = '在这里填入endpoint';
        const DAILY_LIMIT = 10;
        const STORAGE_KEY = 'comfyui_usage';

        const promptInput = document.getElementById('prompt');
        const generateBtn = document.getElementById('generateBtn');
        const clearBtn = document.getElementById('clearBtn');
        const loading = document.getElementById('loading');
        const error = document.getElementById('error');
        const result = document.getElementById('result');
        const resultImage = document.getElementById('resultImage');
        const downloadBtn = document.getElementById('downloadBtn');

        let currentImageBlob = null;
        let currentFilename = null;

        // 获取今天的日期字符串 (YYYY-MM-DD)
        function getTodayString() {
            const today = new Date();
            return today.toISOString().split('T')[0];
        }

        // 获取使用次数
        function getUsageData() {
            try {
                const data = localStorage.getItem(STORAGE_KEY);
                if (!data) {
                    return { date: getTodayString(), count: 0 };
                }
                return JSON.parse(data);
            } catch (e) {
                return { date: getTodayString(), count: 0 };
            }
        }

        // 保存使用次数
        function saveUsageData(data) {
            try {
                localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
            } catch (e) {
                console.error('无法保存使用记录:', e);
            }
        }

        // 检查是否还有使用次数
        function checkUsageLimit() {
            const usage = getUsageData();
            const today = getTodayString();

            // 如果是新的一天,重置计数
            if (usage.date !== today) {
                return { allowed: true, remaining: DAILY_LIMIT };
            }

            // 检查是否超过限制
            if (usage.count >= DAILY_LIMIT) {
                return { allowed: false, remaining: 0 };
            }

            return { allowed: true, remaining: DAILY_LIMIT - usage.count };
        }

        // 增加使用次数
        function incrementUsage() {
            const usage = getUsageData();
            const today = getTodayString();

            if (usage.date !== today) {
                // 新的一天,重置计数
                saveUsageData({ date: today, count: 1 });
            } else {
                // 增加计数
                saveUsageData({ date: today, count: usage.count + 1 });
            }
        }

        // 更新剩余次数显示
        function updateRemainingDisplay() {
            const { remaining } = checkUsageLimit();
            const infoDiv = document.querySelector('.info');
            if (remaining <= 3 && remaining > 0) {
                infoDiv.innerHTML = `提示:输入详细的图片描述,AI 将为您生成对应的图片<br><span style="color: #f57c00; font-weight: 600;">今日剩余次数:${remaining}</span>`;
            } else if (remaining === 0) {
                infoDiv.innerHTML = `<span style="color: #c62828; font-weight: 600;">今日使用次数已用完,请明天再来!</span>`;
            } else {
                infoDiv.innerHTML = `提示:输入详细的图片描述,AI 将为您生成对应的图片<br><span style="color: #1565c0;">今日剩余次数:${remaining}</span>`;
            }
        }

        // 生成唯一文件名
        function generateFilename() {
            const timestamp = Date.now();
            const random = Math.random().toString(36).substring(2, 8);
            return `comfyui_${timestamp}_${random}`;
        }

        // 显示错误信息
        function showError(message) {
            error.textContent = message;
            error.classList.add('active');
            setTimeout(() => {
                error.classList.remove('active');
            }, 5000);
        }

        // 隐藏错误信息
        function hideError() {
            error.classList.remove('active');
        }

        // 生成图片
        async function generateImage() {
            const prompt = promptInput.value.trim();

            if (!prompt) {
                showError('请输入图片提示词');
                return;
            }

            // 检查使用次数限制
            const { allowed, remaining } = checkUsageLimit();
            if (!allowed) {
                showError('今日使用次数已用完,请明天再来!');
                return;
            }

            // 重置状态
            hideError();
            result.classList.remove('active');
            loading.classList.add('active');
            generateBtn.disabled = true;

            try {
                currentFilename = generateFilename();

                const response = await fetch(API_ENDPOINT, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        prompt: prompt
                    })
                });

                if (!response.ok) {
                    throw new Error(`API 请求失败: ${response.status} ${response.statusText}`);
                }

                // 获取图片 blob
                currentImageBlob = await response.blob();

                // 创建图片 URL 并显示
                const imageUrl = URL.createObjectURL(currentImageBlob);
                resultImage.src = imageUrl;

                // 成功生成后增加使用次数
                incrementUsage();
                updateRemainingDisplay();

                // 显示结果
                loading.classList.remove('active');
                result.classList.add('active');

            } catch (err) {
                console.error('生成图片失败:', err);
                showError(`生成图片失败: ${err.message}`);
                loading.classList.remove('active');
            } finally {
                generateBtn.disabled = false;
            }
        }

        // 下载图片
        function downloadImage() {
            if (!currentImageBlob) {
                showError('没有可下载的图片');
                return;
            }

            const url = URL.createObjectURL(currentImageBlob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `${currentFilename}.jpg`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }

        // 清空表单
        function clearForm() {
            promptInput.value = '';
            result.classList.remove('active');
            hideError();
            currentImageBlob = null;
            currentFilename = null;
        }

        // 事件监听
        generateBtn.addEventListener('click', generateImage);
        clearBtn.addEventListener('click', clearForm);
        downloadBtn.addEventListener('click', downloadImage);

        // 支持 Enter 键提交(Ctrl/Cmd + Enter)
        promptInput.addEventListener('keydown', (e) => {
            if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                generateImage();
            }
        });

        // 页面加载时初始化
        window.addEventListener('DOMContentLoaded', () => {
            updateRemainingDisplay();

            // 如果已经用完次数,禁用生成按钮
            const { allowed } = checkUsageLimit();
            if (!allowed) {
                generateBtn.disabled = true;
                generateBtn.style.opacity = '0.5';
                generateBtn.style.cursor = 'not-allowed';
            }
        });
    </script>
</body>
</html>

  1. 在本地打开这个html 就可以了。 或者部署到 vercel、Netlify、Cloudflare。让更多佬友白嫖吧!

  2. 看到这里了,别忘了给我的github 项目点star!!!

49 个赞

太强了,大佬

3 个赞

感谢大佬

1 个赞

感谢大佬

1 个赞

太强了!

1 个赞

感谢大佬分享!

1 个赞

卧槽牛逼 很早就薅了modal的教育额度一直没怎么用
要是能加入更多参数设置就好了

2 个赞

感谢分享

1 个赞

感谢分享

感謝大佬,如果能早點看到:melting_face:

不错不错,就是能结合comfyui创建工作流就更好了

1 个赞

大佬 666

部署成功了 就是只有5刀

star

1 个赞

佬友威武

绑卡就有30刀了,站内佬友有代绑卡的。或者弄一张 Yonder、N26、Safepal 的卡都可以

可以的,看步骤8 启动输出了两个endpoint,找到带ui的那个,就是comfyui 的web 界面了

1 个赞

可以的,在我这个基础上改改就可以了。另 佬友的教育额度有多少哇,我看看能不能也薅一个,回馈社区

强大无需多言

1 个赞

太强了,支持了

1 个赞