Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,6 +1222,12 @@ async def on_batch_complete(batch_idx, total, batch_previews):
)

_usage_duration = _time.monotonic() - _usage_t0
result["duration_seconds"] = _usage_duration
_preview_count = len(result.get("preview_images", []))
print(
f"[Independent] ✅ 完成: {model} | {_preview_count} 张 | 耗时 {_usage_duration:.1f}s",
flush=True,
)

# --- Usage Tracking ---
try:
Expand Down
11 changes: 10 additions & 1 deletion adapters/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,13 @@ def _build_openai_request(self, params: Dict, mode: str = "text2img") -> Dict:
# Frontend handles api_name mapping, so params already have correct keys
# When payload has "messages" (Chat API), skip prompt/seed — they're inside messages
chat_mode = "messages" in payload
excluded_params = set(self.endpoint.get("exclude_params") or [])
excluded_params.update(self.mode_config.get("exclude_params") or [])
for param_name, value in params.items():
if param_name.startswith("_"):
continue
if param_name in excluded_params:
continue
if chat_mode and param_name in ("prompt", "seed"):
continue
if param_name in ("endpoint_override",):
Expand Down Expand Up @@ -1115,6 +1119,11 @@ def _poll_for_result(self, task_id: str, timeout: int = 600) -> APIResponse:
status_path = self.mode_config.get("status_path", "data.status")
success_value = self.mode_config.get("success_value", "SUCCESS")
response_path = self.mode_config.get("response_path", "data.data.data[*].url")
poll_interval = self.mode_config.get("poll_interval", 2)
try:
poll_interval = max(float(poll_interval), 0)
except (TypeError, ValueError):
poll_interval = 2

poll_url = f"{self.base_url}{polling_endpoint}"

Expand All @@ -1130,7 +1139,7 @@ def _poll_for_result(self, task_id: str, timeout: int = 600) -> APIResponse:
start_time = time.time()

while time.time() - start_time < timeout:
time.sleep(2)
time.sleep(poll_interval)

try:
resp = requests.get(
Expand Down
179 changes: 157 additions & 22 deletions api_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ models:
display_name: 🖼️ GPT-Image-2 (旗舰·图像生成)
category: image
description: OpenAI 最新旗舰图像生成模型,文字渲染能力强(最大长边 3840px,2K以上为实验性功能)
show_seed_widget: true
show_seed_widget: false
dynamic_inputs:
image:
max: 16
Expand All @@ -117,45 +117,116 @@ models:
prompt:
type: string
default: ''
resolution:
type: select
default: 1k
options:
- value: 1k
label: 1K (1024 基准)
- value: 2k
label: 2K (高清海报)
- value: 4k
label: 4K (仅限部分比例)
size:
type: select
default: auto
options:
- value: auto
label: 自动 (模型决定)
- value: 1024x1024
label: 1024×1024 (1:1)
- value: 1536x1024
label: 1536×1024 (3:2 横版)
- value: 1024x1536
label: 1024×1536 (2:3 竖版)
- value: 1792x1024
label: 1792×1024 (16:9 横版)
- value: 1024x1792
label: 1024×1792 (9:16 竖版)
- value: 2048x2048
label: 2048×2048 (2K 1:1)
- value: 2048x1152
label: 2048×1152 (2K 横版)
- value: 3840x2160
label: 3840×2160 (4K 横版)
- value: 2160x3840
label: 2160×3840 (4K 竖版)
label: 自动 (服务端选择比例)
- value: '1:1'
label: 1:1 正方形
- value: '3:2'
label: 3:2 横构图
- value: '2:3'
label: 2:3 竖构图
- value: '4:3'
label: 4:3 横构图
- value: '3:4'
label: 3:4 竖构图
- value: '5:4'
label: 5:4 横构图
- value: '4:5'
label: 4:5 竖构图
- value: '16:9'
label: 16:9 宽屏横版
- value: '9:16'
label: 9:16 手机竖版
- value: '2:1'
label: 2:1 横向 Banner
- value: '1:2'
label: 1:2 竖向长图
- value: '21:9'
label: 21:9 电影超宽屏
- value: '9:21'
label: 9:21 竖向超长图
quality:
type: select
default: medium
default: auto
options:
- value: auto
label: 自动
- value: low
label: 低质量 (快速)
- value: medium
label: 中等
- value: high
label: 高质量
advanced: {}
output_format:
type: select
default: png
options:
- value: png
label: PNG
- value: jpeg
label: JPEG
- value: webp
label: WebP
n:
type: number
default: 1
min: 1
max: 4
step: 1
advanced:
background:
type: select
default: auto
options:
- value: auto
label: 自动
- value: opaque
label: 不透明
- value: transparent
label: 透明 (官方会降级为 auto)
moderation:
type: select
default: auto
options:
- value: auto
label: 默认审核
- value: low
label: 宽松审核
output_compression:
type: string
default: ''
mask_url:
type: string
default: ''
api_endpoints:
- provider: openrouter
priority: 2
model_name: openai/gpt-5.4-image-2
exclude_params:
- seed
- size
- quality
- resolution
- background
- moderation
- output_format
- output_compression
- n
- mask_url
extra_params:
modalities:
- image
Expand Down Expand Up @@ -187,6 +258,17 @@ models:
- provider: 柏拉图
priority: 1
model_name: gpt-image-2
exclude_params:
- seed
- size
- quality
- resolution
- background
- moderation
- output_format
- output_compression
- n
- mask_url
modes:
text2img:
endpoint: /v1/images/generations
Expand All @@ -204,6 +286,59 @@ models:
model: gpt-image-2
prompt: '{{prompt}}'
image: '{{_images_b64}}'
- provider: apimart
display_name: APIMart 官方
priority: 3
model_name: gpt-image-2-official
use_oss_cache: true
modes:
text2img:
endpoint: /v1/images/generations
method: POST
content_type: application/json
response_type: async
task_id_path: data.0.task_id
polling_endpoint: /v1/tasks/{{task_id}}
poll_interval: 5
status_path: data.status
success_value: completed
response_path: data.result.images[*].url[0]
exclude_params:
- seed
payload_template:
model: gpt-image-2-official
prompt: '{{prompt}}'
size: '{{size}}'
resolution: '{{resolution}}'
quality: '{{quality}}'
background: '{{background}}'
moderation: '{{moderation}}'
output_format: '{{output_format}}'
n: '{{n}}'
img2img:
endpoint: /v1/images/generations
oss_endpoint: /v1/images/generations
method: POST
content_type: multipart/form-data
response_type: async
task_id_path: data.0.task_id
polling_endpoint: /v1/tasks/{{task_id}}
poll_interval: 5
status_path: data.status
success_value: completed
response_path: data.result.images[*].url[0]
exclude_params:
- seed
payload_template:
model: gpt-image-2-official
prompt: '{{prompt}}'
size: '{{size}}'
resolution: '{{resolution}}'
quality: '{{quality}}'
background: '{{background}}'
moderation: '{{moderation}}'
output_format: '{{output_format}}'
n: '{{n}}'
sora_image(文生图):
display_name: 🖼️ Sora-Image (视觉先锋·文生图)
category: image
Expand Down
76 changes: 76 additions & 0 deletions deployment/Deploy_Install.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
@echo off

echo ======================================
echo ComfyUI Portable - Deployment
echo ======================================
echo.

rem ===== Auto-detect NAS Drive =====
echo Searching for NAS drive...

set NAS_FOLDER=ComfyUI_Master\ComfyUI_windows_portable
set NAS_DRIVE=

for %%d in (Z P N M L K J I H G F E D X W) do (
if exist "%%d:\%NAS_FOLDER%\" (
set NAS_DRIVE=%%d:
goto :found
)
)

echo ERROR: Cannot find NAS!
echo Searched for: %NAS_FOLDER%
echo Please ensure NAS is mapped and contains the ComfyUI_Master folder.
pause
goto :eof

:found
echo Found NAS at: %NAS_DRIVE%

rem ===== Path Configuration =====
set NAS_SOURCE=%NAS_DRIVE%\ComfyUI_Master\ComfyUI_windows_portable
set NAS_SCRIPTS=%NAS_DRIVE%\ComfyUI_Master
set LOCAL_DIR=C:\ComfyUI_Portable

rem ===== Copy Files =====
echo.
echo Copying ComfyUI to local drive...
echo This may take a few minutes...
echo.

robocopy "%NAS_SOURCE%" "%LOCAL_DIR%" /E /XO /FFT /R:3 /W:1 /NJH /NJS /XD "output" "temp"

if %ERRORLEVEL% GEQ 8 (
echo ERROR: Copy failed!
pause
goto :eof
)
echo Copy complete!

rem ===== Copy Start Script =====
echo.
echo Setting up launcher script...
copy /Y "%NAS_SCRIPTS%\Start_Client.bat" "%LOCAL_DIR%\Start_Client.bat" >nul

rem ===== Create Desktop Shortcut =====
echo Creating desktop shortcut...

set DESKTOP_LNK=%USERPROFILE%\Desktop\ComfyUI.lnk
set TARGET_BAT=%LOCAL_DIR%\Start_Client.bat
set ICON_EXE=%LOCAL_DIR%\ComfyUI.exe

if exist "%ICON_EXE%" (
powershell -Command "$s=(New-Object -COM WScript.Shell).CreateShortcut('%DESKTOP_LNK%');$s.TargetPath='%TARGET_BAT%';$s.WorkingDirectory='%LOCAL_DIR%';$s.IconLocation='%ICON_EXE%,0';$s.Save()"
) else (
powershell -Command "$s=(New-Object -COM WScript.Shell).CreateShortcut('%DESKTOP_LNK%');$s.TargetPath='%TARGET_BAT%';$s.WorkingDirectory='%LOCAL_DIR%';$s.Save()"
)

echo.
echo ======================================
echo Deployment Complete!
echo ======================================
echo.
echo NAS Drive: %NAS_DRIVE%
echo Local folder: %LOCAL_DIR%
echo.
pause
31 changes: 31 additions & 0 deletions deployment/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Student Deployment Scripts

This directory stores the student-side deployment scripts that are copied to the NAS root:

- `Start_Client.bat`: launcher used by student desktop shortcuts. It checks the NAS, self-updates from the NAS root, verifies `ComfyUI-Custom-Batchbox` against the NAS copy, mirrors the plugin when needed, then starts ComfyUI.
- `Deploy_Install.bat`: first-time or repair install script. It copies the portable ComfyUI bundle from NAS to `C:\ComfyUI_Portable`, installs the local launcher, and creates the desktop shortcut.
- `Uninstall.bat`: local cleanup script for removing `C:\ComfyUI_Portable` and the desktop shortcut before a reinstall.

## NAS Placement

Copy these files to:

```text
Z:\ComfyUI_Master\Start_Client.bat
Z:\ComfyUI_Master\Deploy_Install.bat
Z:\ComfyUI_Master\Uninstall.bat
```

Student machines may map the same NAS as another drive letter, such as `P:`. Both scripts auto-detect mapped drives by checking for:

```text
ComfyUI_Master\ComfyUI_windows_portable
```

## BatchBox Sync Policy

`Start_Client.bat` does not try to kill running Python or ComfyUI processes. It compares the local BatchBox plugin against the NAS copy first:

- If every file matches by relative path, size, and modified time, it starts ComfyUI immediately.
- If anything differs, it runs `robocopy /MIR` for `ComfyUI-Custom-Batchbox`, which also removes local extra files.
- If `.pyd` files are locked and cannot be mirrored, startup stops and the user should reboot, then run the launcher again.
Loading
Loading