From 62f1c8dac471505032a375f0d8a57014c962bc7f Mon Sep 17 00:00:00 2001
From: wjluoxiao <331030818@qq.com>
Date: Fri, 15 May 2026 21:05:45 +0800
Subject: [PATCH] Add files via upload
---
README.md | 146 +++++++++---------
__init__.py | 58 ++++---
js/folder_picker.js | 16 +-
js/xb_dashboard.js | 62 +++-----
js/xb_master_param.js | 28 +---
js/xb_smart_ui.js | 14 +-
js/xb_video.js | 21 +--
js/xb_wiring.js | 27 ++--
locales/zh/main.json | 49 ++++++
locales/zh/nodeDefs.json | 321 +++++++++++++++++++++++++++++++++++++++
nodes_batch.py | 23 +--
nodes_blockswap.py | 38 ++---
nodes_dashboard.py | 3 +-
nodes_pipeline.py | 108 ++++++-------
nodes_sageatt.py | 81 ++++++++++
nodes_tile.py | 13 +-
nodes_video.py | 40 +++--
nodes_vis.py | 154 ++++++++-----------
nodes_vram.py | 66 ++++----
nodes_wan_vae.py | 80 ++++------
nodes_wiring.py | 29 +---
21 files changed, 806 insertions(+), 571 deletions(-)
create mode 100644 locales/zh/main.json
create mode 100644 locales/zh/nodeDefs.json
create mode 100644 nodes_sageatt.py
diff --git a/README.md b/README.md
index 5d2d728..5e63c61 100644
--- a/README.md
+++ b/README.md
@@ -1,74 +1,74 @@
-
-
-
-# 🧰 ComfyUI XB-BOX (XB_ToolBox)
-
----
-
-## 🌟 Core Philosophy
-
-The primary goal of **XB_ToolBox** is to help AI beginners new to ComfyUI quickly master workflows, making local deployment and execution simpler and more convenient. It is a comprehensive ComfyUI extension suite encompassing both front-end interaction and low-level memory scheduling.
-
-- **Unified Parameters:** Integrates frequently used but scattered parameter nodes, allowing you to set essential image and video parameters from a single hub.
-- **Visual Learning:** Provides visual chunking and parameter preview nodes, enabling beginners to intuitively understand the concepts of VAE/Encoder chunks, temporal slicing, and overlap effects.
-- **UX Enhancements:** Introduces a user-friendly "Mirror Clone Console" (Dashboard) that clones major operation nodes from complex workflows into a unified, clean, and highly efficient control panel.
-- **Extreme VRAM Optimization:** Through "Spatiotemporal Chunk Hijacking", "Dynamic VRAM Offloading for UNet/Checkpoints", and "Deep LiteGraph Customization", it allows consumer-grade GPUs to smoothly run massive 14B~22B 3D-DiT video models that would normally cause instant OOM (Out of Memory).
-
----
-
-
-
-
-
-
-
-
-
-
----
-
-### ✨ Core Nodes
-
-#### 1. 🎬 Media Parameters & Spatiotemporal Visualization
-
-- **🖼️ Image Parameters Master**: Controls core image generation parameters (Width, Height, Batch Size, Strength) in one place. Enforces safe steps to prevent invalid inputs and automatically aligns dimensions when a fixed aspect ratio is selected.
-- **🎬 Video Parameters Master**: Manages Width, Height, Frames, and FPS (Integer/Float). Features a geek-level "Auto-Manual Transmission Engine" that smoothly snaps to official pre-trained "Golden Buckets" (e.g., 480x832, 544x960) and strictly locks the physical frame count to the safe `1+8N` rule. Dynamically displays video duration based on frame rate.
-- **🧊 Spatiotemporal Chunk Visualization**: An exclusive dual-zone radar! The 2D left panel visualizes spatial image chunks and overlap values, while the 3D right panel uses a painter's algorithm to render temporal cylinder stacks. Saves beginners from the exhaustion of blind parameter tuning.
-- 🧊 **Storyboard Image Slicer:** Offers multiple modes to slice common 4-grid, 6-grid, 9-grid... images all at once and output multiple individual images. This makes image-to-video or start/end frame video generation much more convenient, and is highly effective for creating short dramas.
-- **📟 VRAM Calculator & Data Radar**: Predicts VRAM footprint for WAN/LTX models under different quantizations (FP8/GGUF) and weighs tensor volumes with MB-level precision. Significantly reduces trial-and-error costs by helping users choose models fitting their VRAM capacity.
-
-#### 2. 🧊 VRAM Optimization
-
-- **🧊 Sampler Chunk Master**: Tailored for WAN/LTX. Performs spatiotemporal dual-axis slicing (Spatial Tiles & Temporal Chunks) in the Latent space. Hijacks model parameters in-place with an built-in `rocm_optimized` extreme memory reclamation strategy, breaking the large-tensor OOM curse.
-- **✂️ Model Block Swap**: Supports both UNet and Checkpoint modes. Forcibly unloads the first N Transformer Blocks and Text/Image embedding layers to system RAM, trading time for space.
-- **🧹 VRAM Cleaner**: Essential for pre-generation clearing. Delivers "nuclear-level cleanup" by deeply invoking `gc.collect()` and `torch.cuda.empty_cache()` to crush PyTorch cache fragmentation.
-
-#### 3. 🎛️ Workflow Deployment Assistants
-
-- **🪄 XB Dashboard Zen**: A "Mirror Clone Console" developed using low-level LiteGraph features. Batch-clones any scattered components in your workflow into a fixed panel. Features two-way data synchronization and direct arterial connections to create a clean, customized monitoring dashboard.
-- **🎛️ Dynamic Bus**: An ultra-compact N-in/N-out `AnyType` universal bus node. Provides custom UI type labels and one-click channel addition/removal to save your workflow from "noodle wire" chaos.
-
----
-
-### 📦 Installation
-
-**Method 1: ComfyUI Manager (Recommended)**
-Search for `XB_ToolBox` in the ComfyUI Manager and click install.
-
-**Method 2: Manual Git Clone**
-Navigate to your ComfyUI `custom_nodes` directory and run:
-
-```bash
-git clone https://github.com/wjluoxiao/XB_ToolBox.git
-```
-
-(Note: This extension relies purely on the native ComfyUI ecosystem through elegant Python/JS architecture. NO extra pip dependencies required! Plug and play.)
-
----
-
-📝 License
-This project is open-sourced under the Apache-2.0 License. It guarantees the freedom to share while protecting the patent defense rights of the core architecture code.
-
+
+
+
+# 🧰 ComfyUI XB-BOX (XB_ToolBox)
+
+---
+
+## 🌟 Core Philosophy
+
+The primary goal of **XB_ToolBox** is to help AI beginners new to ComfyUI quickly master workflows, making local deployment and execution simpler and more convenient. It is a comprehensive ComfyUI extension suite encompassing both front-end interaction and low-level memory scheduling.
+
+- **Unified Parameters:** Integrates frequently used but scattered parameter nodes, allowing you to set essential image and video parameters from a single hub.
+- **Visual Learning:** Provides visual chunking and parameter preview nodes, enabling beginners to intuitively understand the concepts of VAE/Encoder chunks, temporal slicing, and overlap effects.
+- **UX Enhancements:** Introduces a user-friendly "Mirror Clone Console" (Dashboard) that clones major operation nodes from complex workflows into a unified, clean, and highly efficient control panel.
+- **Extreme VRAM Optimization:** Through "Spatiotemporal Chunk Hijacking", "Dynamic VRAM Offloading for UNet/Checkpoints", and "Deep LiteGraph Customization", it allows consumer-grade GPUs to smoothly run massive 14B~22B 3D-DiT video models that would normally cause instant OOM (Out of Memory).
+
+---
+
+
+
+
+
+
+
+
+
+
+---
+
+### ✨ Core Nodes
+
+#### 1. 🎬 Media Parameters & Spatiotemporal Visualization
+
+- **🖼️ Image Parameters Master**: Controls core image generation parameters (Width, Height, Batch Size, Strength) in one place. Enforces safe steps to prevent invalid inputs and automatically aligns dimensions when a fixed aspect ratio is selected.
+- **🎬 Video Parameters Master**: Manages Width, Height, Frames, and FPS (Integer/Float). Features a geek-level "Auto-Manual Transmission Engine" that smoothly snaps to official pre-trained "Golden Buckets" (e.g., 480x832, 544x960) and strictly locks the physical frame count to the safe `1+8N` rule. Dynamically displays video duration based on frame rate.
+- **🧊 Spatiotemporal Chunk Visualization**: An exclusive dual-zone radar! The 2D left panel visualizes spatial image chunks and overlap values, while the 3D right panel uses a painter's algorithm to render temporal cylinder stacks. Saves beginners from the exhaustion of blind parameter tuning.
+- 🧊 **Storyboard Image Slicer:** Offers multiple modes to slice common 4-grid, 6-grid, 9-grid... images all at once and output multiple individual images. This makes image-to-video or start/end frame video generation much more convenient, and is highly effective for creating short dramas.
+- **📟 VRAM Calculator & Data Radar**: Predicts VRAM footprint for WAN/LTX models under different quantizations (FP8/GGUF) and weighs tensor volumes with MB-level precision. Significantly reduces trial-and-error costs by helping users choose models fitting their VRAM capacity.
+
+#### 2. 🧊 VRAM Optimization
+
+- **🧊 Sampler Chunk Master**: Tailored for WAN/LTX. Performs spatiotemporal dual-axis slicing (Spatial Tiles & Temporal Chunks) in the Latent space. Hijacks model parameters in-place with an built-in `rocm_optimized` extreme memory reclamation strategy, breaking the large-tensor OOM curse.
+- **✂️ Model Block Swap**: Supports both UNet and Checkpoint modes. Forcibly unloads the first N Transformer Blocks and Text/Image embedding layers to system RAM, trading time for space.
+- **🧹 VRAM Cleaner**: Essential for pre-generation clearing. Delivers "nuclear-level cleanup" by deeply invoking `gc.collect()` and `torch.cuda.empty_cache()` to crush PyTorch cache fragmentation.
+
+#### 3. 🎛️ Workflow Deployment Assistants
+
+- **🪄 XB Dashboard Zen**: A "Mirror Clone Console" developed using low-level LiteGraph features. Batch-clones any scattered components in your workflow into a fixed panel. Features two-way data synchronization and direct arterial connections to create a clean, customized monitoring dashboard.
+- **🎛️ Dynamic Bus**: An ultra-compact N-in/N-out `AnyType` universal bus node. Provides custom UI type labels and one-click channel addition/removal to save your workflow from "noodle wire" chaos.
+
+---
+
+### 📦 Installation
+
+**Method 1: ComfyUI Manager (Recommended)**
+Search for `XB_ToolBox` in the ComfyUI Manager and click install.
+
+**Method 2: Manual Git Clone**
+Navigate to your ComfyUI `custom_nodes` directory and run:
+
+```bash
+git clone https://github.com/wjluoxiao/XB_ToolBox.git
+```
+
+(Note: This extension relies purely on the native ComfyUI ecosystem through elegant Python/JS architecture. NO extra pip dependencies required! Plug and play.)
+
+---
+
+📝 License
+This project is open-sourced under the Apache-2.0 License. It guarantees the freedom to share while protecting the patent defense rights of the core architecture code.
+
---
\ No newline at end of file
diff --git a/__init__.py b/__init__.py
index 1bfb12c..1c84e37 100644
--- a/__init__.py
+++ b/__init__.py
@@ -6,13 +6,11 @@ from aiohttp import web
@PromptServer.instance.routes.post("/xb_toolbox/choose_folder")
async def choose_folder(request):
-
root = tk.Tk()
root.withdraw()
root.attributes('-topmost', True)
folder_path = filedialog.askdirectory()
root.destroy()
-
return web.json_response({"path": folder_path})
def print_success(msg):
@@ -29,7 +27,6 @@ NODE_DISPLAY_NAME_MAPPINGS = {}
WEB_DIRECTORY = "./js"
try:
-
from .nodes_vis import XB_VRAM_Calculator, XB_ChunkVisualization
from .nodes_vram import XTX_VRAM_Cleaner, XTX_Data_Radar
from .nodes_video import XB_VideoParamsMaster, XB_ImageParamsMaster, XB_MasterParameter
@@ -40,6 +37,7 @@ try:
from .nodes_wan_vae import XB_WanImageToVideo, XB_WanFirstLastFrameToVideo
from .nodes_batch import XB_BatchFolderLoader
from .nodes_pipeline import XB_Wan_ParamBus, XB_Wan_RelayNode, XB_Video_Merger, XB_StoryboardSlicer
+ from .nodes_sageatt import XB_SageAttentionAccelerator
NODE_CLASS_MAPPINGS = {
"XB_VRAM_Calculator": XB_VRAM_Calculator,
@@ -62,43 +60,43 @@ try:
"XB_Wan_ParamBus": XB_Wan_ParamBus,
"XB_Wan_RelayNode": XB_Wan_RelayNode,
"XB_Video_Merger": XB_Video_Merger,
- "XB_StoryboardSlicer": XB_StoryboardSlicer
+ "XB_StoryboardSlicer": XB_StoryboardSlicer,
+ "XB_SageAttentionAccelerator": XB_SageAttentionAccelerator
}
NODE_DISPLAY_NAME_MAPPINGS = {
- "XB_VRAM_Calculator": "XB-BOX - 📟 可用显存计算",
- "XB_ChunkVisualization": "XB-BOX - 🧊 时空分块预览",
- "XTX_VRAM_Cleaner": "XB-BOX- 🧹 显存清理大师",
- "XTX_Data_Radar": "XB-BOX - 🪞 生成数据预览",
- "XB_VideoParamsMaster": "XB-BOX - 🎬 视频参数大全",
- "XB_ImageParamsMaster": "XB-BOX - 🖼️ 图片参数大全",
- "XB_MasterParameter": "XB-BOX - 🎛️ 全能参数控制",
- "XB_UNetBlockSwap": "XB-BOX - ✂️ 模型分块交换(UNet)",
- "XB_CheckpointBlockSwap": "XB-BOX - ✂️ 模型分块交换(checkpoints)",
- "XB_DynamicBus": "XB-BOX - 🎛️ 动态总线插排",
- "XB_UNetNameBroadcaster": "XB-BOX - 🗂️ UNet 名称分发",
- "XB_CLIPNameBroadcaster": "XB-BOX - 🗂️ CLIP 名称分发",
- "XB_Dashboard_Zen": "XB-BOX - 🪄 远程控制中心",
- "XB_SamplerChunkMaster": "XB-BOX - 🧊 采样分块大师",
- "XB_WanImageToVideo": "XB-BOX - 🖼️ 单图转视频分块 (Wan)",
- "XB_WanFirstLastFrameToVideo": "XB-BOX - 🎞️ 首尾帧视频分块(Wan)",
- "XB_BatchFolderLoader": "XB-BOX - 📂 图片批量加载",
- "XB_Wan_ParamBus": "XB-BOX - 📦 视频参数总线",
- "XB_Wan_RelayNode": "XB-BOX - 🏃 首尾帧接力点",
- "XB_Video_Merger": "XB-BOX - 🧩 视频无缝拼接",
- "XB_StoryboardSlicer": "XB-BOX - 🧊 分镜图片切割"
+ "XB_VRAM_Calculator": "XB-BOX - VRAM Calculator",
+ "XB_ChunkVisualization": "XB-BOX - Chunk Visualization",
+ "XTX_VRAM_Cleaner": "XB-BOX - VRAM Cleaner",
+ "XTX_Data_Radar": "XB-BOX - Data Radar",
+ "XB_VideoParamsMaster": "XB-BOX - Video Params Master",
+ "XB_ImageParamsMaster": "XB-BOX - Image Params Master",
+ "XB_MasterParameter": "XB-BOX - Master Parameter",
+ "XB_UNetBlockSwap": "XB-BOX - UNet Block Swap",
+ "XB_CheckpointBlockSwap": "XB-BOX - Checkpoint Block Swap",
+ "XB_DynamicBus": "XB-BOX - Dynamic Bus",
+ "XB_UNetNameBroadcaster": "XB-BOX - UNet Name Broadcaster",
+ "XB_CLIPNameBroadcaster": "XB-BOX - CLIP Name Broadcaster",
+ "XB_Dashboard_Zen": "XB-BOX - Dashboard Zen",
+ "XB_SamplerChunkMaster": "XB-BOX - Sampler Chunk Master",
+ "XB_WanImageToVideo": "XB-BOX - Wan Image2Video",
+ "XB_WanFirstLastFrameToVideo": "XB-BOX - Wan First/Last Frame2Video",
+ "XB_BatchFolderLoader": "XB-BOX - Batch Folder Loader",
+ "XB_Wan_ParamBus": "XB-BOX - Wan Param Bus",
+ "XB_Wan_RelayNode": "XB-BOX - Wan Relay Node",
+ "XB_Video_Merger": "XB-BOX - Video Merger",
+ "XB_StoryboardSlicer": "XB-BOX - Storyboard Slicer",
+ "XB_SageAttentionAccelerator": "XB-BOX - SageAttention Accelerator"
}
- # 打印超级醒目的成功提示
print_success("\n" + "="*50)
- print_success("🚀 [XB-BOX] 小白工具箱 核心模块加载成功!")
+ print_success("🚀 [XB-BOX] XB_ToolBox Core Modules Loaded Successfully!")
print_success("="*50 + "\n")
except Exception as e:
print_error("\n" + "="*50)
- print_error("🚨 [XB-BOX] 致命错误:小白工具箱 加载失败!")
- print_error(f"❌ 错误简述: {str(e)}")
- print_warning("🔍 详细追踪信息如下 (请根据此处排查 Bug):")
+ print_error("🚨 [XB-BOX] FATAL ERROR: XB_ToolBox Loading Failed!")
+ print_error(f"❌ Error Detail: {str(e)}")
traceback.print_exc()
print_error("="*50 + "\n")
diff --git a/js/folder_picker.js b/js/folder_picker.js
index 6974b9e..96be380 100644
--- a/js/folder_picker.js
+++ b/js/folder_picker.js
@@ -1,34 +1,28 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
+const isZH = navigator.language.startsWith("zh");
+
app.registerExtension({
name: "XB_ToolBox.FolderPicker",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
- // 精准狙击我们的批量加载节点
if (nodeData.name === "XB_BatchFolderLoader") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
-
- // 找到那个蠢笨的 directory 文本输入框
const dirWidget = this.widgets.find(w => w.name === "directory");
- // 在节点上凭空捏造一个高大上的“选择文件夹”按钮
- const btnWidget = this.addWidget("button", "📁 浏览文件夹...", "browse", () => {
-
- // 呼叫后端 Python 弹出 Windows 窗口
+ const btnLabel = isZH ? "📁 浏览文件夹..." : "📁 Browse Folder...";
+ const btnWidget = this.addWidget("button", btnLabel, "browse", () => {
api.fetchApi("/xb_toolbox/choose_folder", { method: "POST" })
.then(res => res.json())
.then(data => {
- // 如果用户选了路径,直接塞进文本框里
if (data.path) {
dirWidget.value = data.path;
}
- }).catch(err => console.error("文件夹选择器唤醒失败:", err));
-
+ }).catch(err => {});
});
-
return r;
};
}
diff --git a/js/xb_dashboard.js b/js/xb_dashboard.js
index f642028..1c80fce 100644
--- a/js/xb_dashboard.js
+++ b/js/xb_dashboard.js
@@ -1,8 +1,5 @@
import { app } from "../../scripts/app.js";
-// ==============================================================================
-// 🛠️ 核心工具:深度比较,防死循环
-// ==============================================================================
function isValEqual(v1, v2) {
if (v1 === v2) return true;
if (typeof v1 === 'object' && typeof v2 === 'object') {
@@ -12,27 +9,20 @@ function isValEqual(v1, v2) {
return false;
}
-// ==============================================================================
-// 🚨 存档清洗协议:清除上一版的魔改残留,恢复 100% 原生 Group 功能
-// ==============================================================================
function cleanCorruptedGroups() {
if (!app.graph) return;
- const group = app.graph._groups.find(g => g.title === "🎮 XB 远程控制中心");
+ const group = app.graph._groups.find(g => g.title === "🎮 XB Dashboard Zen");
if (group) {
- // 删除我们之前强加的自定义绘画和属性,让引擎接管
if (group._xb_patched) {
delete group.draw;
delete group._xb_patched;
}
if (group.hasOwnProperty('recomputeInsideNodes')) {
- delete group.recomputeInsideNodes; // 恢复原生拖拽抓取能力
+ delete group.recomputeInsideNodes;
}
}
}
-// ==============================================================================
-// 🛠️ 终极同步引擎:大动脉直连 + 强制继承
-// ==============================================================================
function applyMirrorSyncLogic(proxyNode) {
if (proxyNode._xb_sync_interval) return;
@@ -41,7 +31,6 @@ function applyMirrorSyncLogic(proxyNode) {
if (!realTarget) return;
- // 强制继承初始值
if (proxyNode.widgets && realTarget.widgets) {
for (let i = 0; i < proxyNode.widgets.length; i++) {
let pw = proxyNode.widgets[i];
@@ -63,13 +52,13 @@ function applyMirrorSyncLogic(proxyNode) {
const currentTarget = app.graph.getNodeById(targetId);
if (!currentTarget) {
- if (proxyNode.title !== "⚠️ 母节点已断开") {
- proxyNode.title = "⚠️ 母节点已断开";
+ if (proxyNode.title !== "⚠️ Mother Node Disconnected") {
+ proxyNode.title = "⚠️ Mother Node Disconnected";
proxyNode.color = "#880000";
proxyNode.setDirtyCanvas(true, true);
}
return;
- } else if (proxyNode.title === "⚠️ 母节点已断开") {
+ } else if (proxyNode.title === "⚠️ Mother Node Disconnected") {
proxyNode.title = "🪞 " + (currentTarget.title || currentTarget.type);
if (currentTarget.color) proxyNode.color = currentTarget.color;
else delete proxyNode.color;
@@ -80,7 +69,6 @@ function applyMirrorSyncLogic(proxyNode) {
let needRedraw = false;
- // 大动脉直连出图
if (app.nodeOutputs && app.nodeOutputs[targetId]) {
const motherOutput = app.nodeOutputs[targetId];
const proxyOutput = app.nodeOutputs[proxyNode.id];
@@ -93,7 +81,6 @@ function applyMirrorSyncLogic(proxyNode) {
}
}
- // 图像指纹雷达
const getImgFingerprint = (imgs) => imgs ? imgs.map(img => img.src || "").join('|') + '|' + imgs.length : "null";
const currentImgState = getImgFingerprint(currentTarget.imgs);
if (proxyNode._xb_last_img_state !== currentImgState) {
@@ -105,7 +92,6 @@ function applyMirrorSyncLogic(proxyNode) {
if (proxyNode.imageIndex !== currentTarget.imageIndex) { proxyNode.imageIndex = currentTarget.imageIndex; needRedraw = true; }
if (proxyNode.animatedImages !== currentTarget.animatedImages) { proxyNode.animatedImages = currentTarget.animatedImages; needRedraw = true; }
- // 参数双向绑定
if (proxyNode.widgets && currentTarget.widgets) {
for (let i = 0; i < proxyNode.widgets.length; i++) {
let pw = proxyNode.widgets[i];
@@ -167,7 +153,6 @@ function applyMirrorSyncLogic(proxyNode) {
};
}
-// 读档拦截恢复影子
const origOnNodeAdded = LGraph.prototype.add;
LGraph.prototype.add = function(node, skip_compute_order) {
origOnNodeAdded.apply(this, arguments);
@@ -179,9 +164,6 @@ LGraph.prototype.add = function(node, skip_compute_order) {
}
};
-// ==============================================================================
-// 📦 原生组件选择弹窗
-// ==============================================================================
function showMultiPickerModal(validNodes, onConfirmCallback) {
const existing = document.getElementById("xb-picker-modal");
if (existing) existing.remove();
@@ -208,7 +190,7 @@ function showMultiPickerModal(validNodes, onConfirmCallback) {
const header = document.createElement("div");
header.style.cssText = "padding: 15px 20px; background: #1c232d; border-bottom: 1px solid #334; display: flex; justify-content: space-between; align-items: center;";
- header.innerHTML = `📦 批量选择要克隆的组件`;
+ header.innerHTML = `📦 Batch Select Components to Clone`;
const closeBtn = document.createElement("button");
closeBtn.innerText = "✖";
@@ -222,7 +204,7 @@ function showMultiPickerModal(validNodes, onConfirmCallback) {
const checkboxes = [];
if (validNodes.length === 0) {
- listContainer.innerHTML = `当前工作流无可用节点。
`;
+ listContainer.innerHTML = `No available nodes in current workflow.
`;
} else {
validNodes.forEach(node => {
const label = document.createElement("label");
@@ -241,7 +223,7 @@ function showMultiPickerModal(validNodes, onConfirmCallback) {
textDiv.style.cssText = "display: flex; flex-direction: column;";
textDiv.innerHTML = `
${node.title || node.type}
- ID: ${node.id} | 类型: ${node.type}
+ ID: ${node.id} | Type: ${node.type}
`;
label.appendChild(cb);
@@ -257,7 +239,7 @@ function showMultiPickerModal(validNodes, onConfirmCallback) {
footer.style.cssText = "padding: 15px 20px; background: #1c232d; border-top: 1px solid #334; display: flex; justify-content: space-between; align-items: center;";
const selectAllBtn = document.createElement("button");
- selectAllBtn.innerText = "☑ 全选 / 反选";
+ selectAllBtn.innerText = "☑ Select All / Invert";
selectAllBtn.style.cssText = "padding: 8px 15px; background: #2a3441; color: white; border: none; border-radius: 4px; cursor: pointer;";
selectAllBtn.onclick = (e) => {
e.preventDefault();
@@ -266,7 +248,7 @@ function showMultiPickerModal(validNodes, onConfirmCallback) {
};
const confirmBtn = document.createElement("button");
- confirmBtn.innerText = "🚀 执行克隆";
+ confirmBtn.innerText = "🚀 Execute Clone";
confirmBtn.style.cssText = "padding: 8px 20px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;";
confirmBtn.onclick = (e) => {
@@ -290,9 +272,6 @@ function showMultiPickerModal(validNodes, onConfirmCallback) {
document.body.appendChild(overlay);
}
-// ==============================================================================
-// 🚀 核心控制台:100% 拥抱原生 API,拒绝任何底层魔改
-// ==============================================================================
app.registerExtension({
name: "XB_ToolBox.Dashboard",
@@ -313,7 +292,6 @@ app.registerExtension({
if (info && info.widgets_values) info.widgets_values = [];
if (origConfigure) origConfigure.apply(this, arguments);
- // 启动清洗协议:把旧存档里被污染的组洗白
setTimeout(() => cleanCorruptedGroups(), 200);
};
@@ -321,14 +299,14 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
if (onNodeCreated) onNodeCreated.apply(this, arguments);
- this.title = "🪄 XB 远程控制中心";
+ this.title = "🪄 XB Dashboard Zen";
this.properties = this.properties || {};
- this.addWidget("button", "➕ 克隆组件", "extract", () => {
+ this.addWidget("button", "➕ Clone Component", "extract", () => {
setTimeout(() => this.openExtractModal(), 15);
});
- this.addWidget("button", "🧹 清除组件", "clear", () => {
+ this.addWidget("button", "🧹 Clear Components", "clear", () => {
setTimeout(() => this.destroyAllMirrors(), 15);
});
@@ -349,15 +327,13 @@ app.registerExtension({
};
nodeType.prototype.createMirrorsInPlace = function(nodesToClone) {
- // 1. 完全调用官方 API 创建原生组
- let group = app.graph._groups.find(g => g.title === "🎮 XB 远程控制中心");
+ let group = app.graph._groups.find(g => g.title === "🎮 XB Dashboard Zen");
if (!group) {
group = new LiteGraph.LGraphGroup();
- group.title = "🎮 XB 远程控制中心";
+ group.title = "🎮 XB Dashboard Zen";
app.graph.add(group);
}
- // 2. 根据中枢节点计算坐标
let newGroupX = this.pos[0] - 40;
let newGroupY = this.pos[1] - 100;
@@ -378,7 +354,6 @@ app.registerExtension({
});
}
- // 3. 克隆节点
nodesToClone.forEach(target => {
const existingProxy = existingProxies.find(n => n.properties.xb_mirror_target_id === target.id);
if (existingProxy) return;
@@ -392,7 +367,6 @@ app.registerExtension({
proxy.isVirtualNode = true;
proxy.title = "🪞 " + (target.title || target.type);
- // 完全继承原生颜色
if (target.color) proxy.color = target.color;
if (target.bgcolor) proxy.bgcolor = target.bgcolor;
@@ -412,11 +386,9 @@ app.registerExtension({
applyMirrorSyncLogic(proxy);
});
- // 4. 给原生组赋值尺寸
group.pos = [newGroupX, newGroupY];
group.size = [maxRightEdge - newGroupX + 40, maxBottomEdge - newGroupY + 40];
- // 5. 🚨 终极原生奥义:直接调用 LiteGraph 原生的重算方法,让引擎自己去抓节点、写内存!
if (group.recomputeInsideNodes) {
group.recomputeInsideNodes();
}
@@ -426,11 +398,11 @@ app.registerExtension({
};
nodeType.prototype.destroyAllMirrors = function() {
- if (confirm("确定要销毁所有克隆组件吗?(原工作流完全不受影响)")) {
+ if (confirm("Are you sure you want to destroy all cloned components? (Original workflow remains unaffected)")) {
const proxies = app.graph._nodes.filter(n => n.properties && n.properties.xb_mirror_target_id);
proxies.forEach(p => app.graph.remove(p));
- const group = app.graph._groups.find(g => g.title === "🎮 XB 远程控制中心");
+ const group = app.graph._groups.find(g => g.title === "🎮 XB Dashboard Zen");
if (group) app.graph.remove(group);
app.graph.change();
diff --git a/js/xb_master_param.js b/js/xb_master_param.js
index 55d87d8..784f2cd 100644
--- a/js/xb_master_param.js
+++ b/js/xb_master_param.js
@@ -1,15 +1,12 @@
import { app } from "../../scripts/app.js";
-// 📝 架构规则表:彻底分离,绝不互相污染
-// dragStep: 控制拖拽速度和箭头增量 (原生会乘0.1)
-// snapRound: 控制吸附刻度
const MODE_CONFIGS = {
- "自由模式": { min: 0, max: 9999, dragStep: 10, snapRound: 1, precision: 0, def: 0 },
- "模型模式": { min: 0, max: 50, dragStep: 10, snapRound: 1, precision: 0, def: 0 },
- "编码模式": { min: 64, max: 3840, dragStep: 320, snapRound: 32, precision: 0, def: 64 },
- "解码模式": { min: 64, max: 3840, dragStep: 320, snapRound: 32, precision: 0, def: 64 },
- "比例模式": { min: 0.00, max: 1.00, dragStep: 0.1, snapRound: 0.01, precision: 2, def: 0.50 },
- "其他模式": { min: -99999.00, max: 99999.00, dragStep: 0.1, snapRound: 0.01, precision: 2, def: 1.00 }
+ "Free Mode": { min: 0, max: 9999, dragStep: 10, snapRound: 1, precision: 0, def: 0 },
+ "Model Mode": { min: 0, max: 50, dragStep: 10, snapRound: 1, precision: 0, def: 0 },
+ "Encode Mode": { min: 64, max: 3840, dragStep: 320, snapRound: 32, precision: 0, def: 64 },
+ "Decode Mode": { min: 64, max: 3840, dragStep: 320, snapRound: 32, precision: 0, def: 64 },
+ "Ratio Mode": { min: 0.00, max: 1.00, dragStep: 0.1, snapRound: 0.01, precision: 2, def: 0.50 },
+ "Other Mode": { min: -99999.00, max: 99999.00, dragStep: 0.1, snapRound: 0.01, precision: 2, def: 1.00 }
};
app.registerExtension({
@@ -20,11 +17,8 @@ app.registerExtension({
const wValueOrig = node.widgets.find(w => w.name === "value");
if (wMode && wValueOrig) {
- // 1. 记录原位置,确保替换时不会跑到最下面
const valueWidgetIndex = node.widgets.indexOf(wValueOrig);
- // 2. 核心架构:预先制造 6 个纯正的原生 Widget (影子分身)
- // 它们在创建的一瞬间就拥有了最完美的 step 和 round,再也不会被篡改
node._xb_shadow_widgets = {};
for (const [mName, cfg] of Object.entries(MODE_CONFIGS)) {
const w = node.addWidget("number", "value", cfg.def, function(v){}, {
@@ -34,38 +28,31 @@ app.registerExtension({
round: cfg.snapRound,
precision: cfg.precision
});
- // 创建完立刻从面板上摘下来,放进后台仓库
node._xb_shadow_widgets[mName] = node.widgets.pop();
}
- // 3. 定义无缝换挡 (组件热替换) 核心逻辑
node.swapWidget = function(modeName) {
- // 卸载当前面板上的 value 组件
const currIdx = node.widgets.findIndex(w => w.name === "value");
if (currIdx > -1) {
node.widgets.splice(currIdx, 1);
}
- // 从仓库中取出对应的组件,精准安插回原来的位置
const targetW = node._xb_shadow_widgets[modeName];
if (targetW) {
- targetW.name = "value"; // 必须叫 value,否则 Python 后端读不到
+ targetW.name = "value";
node.widgets.splice(valueWidgetIndex, 0, targetW);
}
app.graph.setDirtyCanvas(true, true);
};
- // 4. 初次加载:立刻换上当前模式的专属组件
node.swapWidget(wMode.value);
- // 5. 监听下拉菜单切换:不再修改数字,而是直接“换人”!
const origModeCb = wMode.callback;
wMode.callback = function(v) {
node.swapWidget(v);
if (origModeCb) return origModeCb.apply(this, arguments);
};
- // 6. 完美兼容 ComfyUI 的工作流加载 (F5 刷新或拖入图片时恢复数据)
const onConfigure = node.onConfigure;
node.onConfigure = function(info) {
if (onConfigure) onConfigure.apply(this, arguments);
@@ -73,7 +60,6 @@ app.registerExtension({
if (wMode.value) {
node.swapWidget(wMode.value);
- // 强制将加载的数据覆盖到换上来的组件里
if (info && info.widgets_values && info.widgets_values.length > valueWidgetIndex) {
const savedVal = info.widgets_values[valueWidgetIndex];
const activeW = node.widgets.find(w => w.name === "value");
diff --git a/js/xb_smart_ui.js b/js/xb_smart_ui.js
index 86a7dc6..79034da 100644
--- a/js/xb_smart_ui.js
+++ b/js/xb_smart_ui.js
@@ -1,5 +1,7 @@
import { app } from "../../scripts/app.js";
+const isZH = navigator.language.startsWith("zh");
+
app.registerExtension({
name: "XB_ToolBox.SmartUI",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
@@ -7,7 +9,6 @@ app.registerExtension({
nodeType.prototype.updateWidgetState = function () {
const optInput = this.inputs?.find(i => i.name === "opt_end_image");
- // 🔴 核心修复:只认原始名字,绝不乱改名
const imgCombo = this.widgets?.find(w => w.name === "end_image_file");
let uploadBtn = this.widgets?.find(w => w.type === "button" && !w.name.includes("randomize"));
@@ -16,21 +17,18 @@ app.registerExtension({
const isConnected = !!optInput.link;
if (isConnected) {
- // 🔒 连线状态:只变灰,绝对不准改 imgCombo.name !(否则会导致后端报错)
imgCombo.disabled = true;
- // 按钮可以随意改名,因为它不负责往后端发数据
if (!uploadBtn._orig_callback) uploadBtn._orig_callback = uploadBtn.callback;
- uploadBtn.name = "🔒 端口已被连线接管";
- uploadBtn.label = "🔒 端口已被连线接管";
+ uploadBtn.name = isZH ? "🔒 端口已被连线接管" : "Locked by connection";
+ uploadBtn.label = isZH ? "🔒 端口已被连线接管" : "Locked by connection";
uploadBtn.callback = null;
} else {
- // 🔓 断线状态:恢复使用
imgCombo.disabled = false;
- uploadBtn.name = "选择上传尾帧图片";
- uploadBtn.label = "选择上传尾帧图片";
+ uploadBtn.name = isZH ? "选择上传尾帧图片" : "Select/Upload End Frame";
+ uploadBtn.label = isZH ? "选择上传尾帧图片" : "Select/Upload End Frame";
if (uploadBtn._orig_callback) uploadBtn.callback = uploadBtn._orig_callback;
}
this.setDirtyCanvas(true, true);
diff --git a/js/xb_video.js b/js/xb_video.js
index 2bbf449..cb07166 100644
--- a/js/xb_video.js
+++ b/js/xb_video.js
@@ -1,8 +1,7 @@
import { app } from "../../scripts/app.js";
-console.log("🚀 XB-Tools: 媒体参数节点 JS (原版交互逻辑 100% 保留 + 0.20.X 兼容版) 已加载!");
+const isZH = navigator.language.startsWith("zh");
-// 🟢 0.20.X 兼容层:新版 UI 渲染引擎改动了 DOM 结构,这里做静默兼容,绝不干预任何逻辑!
const xb_dispatch = (w, val) => {
if (w.inputEl) {
w.inputEl.value = val;
@@ -22,10 +21,6 @@ app.registerExtension({
if (!app.graph || !app.graph._nodes) return;
for (const node of app.graph._nodes) {
-
- // ==========================================
- // 🎬 核心 1:视频参数大全 (XB_VideoParamsMaster)
- // ==========================================
if (node.comfyClass === "XB_VideoParamsMaster" && node.widgets) {
const wRatio = node.widgets.find(w => w.name === "aspect_ratio");
const wDisp = node.widgets.find(w => w.name === "duration_display");
@@ -36,8 +31,6 @@ app.registerExtension({
const wHeight = node.widgets.find(w => w.name === "height");
if (wDisp && wLen && wFps && wFpsF && wWidth && wHeight && wRatio) {
-
- // 1. 样式与初始化 (加入 .element 兼容)
let dispEl = wDisp.inputEl || wDisp.element;
if (dispEl && dispEl.style && dispEl.style.backgroundColor !== "rgb(34, 34, 34)") {
dispEl.readOnly = true;
@@ -59,11 +52,10 @@ app.registerExtension({
let needsUpdate = false;
let valRatio = wRatio.value;
- let isFree = valRatio.includes("自由");
+ let isFree = valRatio.includes("Free");
let rChanged = valRatio !== node._xb_last_ratio;
let isGoldenZone = (valRatio === "16:9" || valRatio === "9:16");
- // 2. 视频档位与步长吸附
let currentStep = isGoldenZone ? 1 : (isFree ? 1 : 16);
wWidth.options.step = currentStep; wHeight.options.step = currentStep;
if (wWidth.inputEl) wWidth.inputEl.step = currentStep; else if (wWidth.element) wWidth.element.step = currentStep;
@@ -144,7 +136,6 @@ app.registerExtension({
needsUpdate = true;
}
- // 3. FPS 与视频时长联动
if (wFps.value !== node._xb_last_fps) {
let val = Math.round(Number(wFps.value)); wFps.value = val; wFpsF.value = val;
node._xb_last_fps = val; node._xb_last_fps_float = val;
@@ -179,7 +170,7 @@ app.registerExtension({
let fps = parseInt(wFps.value, 10) || 1;
let seconds = ((safeLen - 1) / fps).toFixed(2);
- let expectedText = `视频时长: ${seconds} 秒`;
+ let expectedText = isZH ? `视频时长: ${seconds} 秒` : `Video Duration: ${seconds} s`;
if (wDisp.value !== expectedText) {
wDisp.value = expectedText;
xb_dispatch(wDisp, expectedText);
@@ -192,9 +183,6 @@ app.registerExtension({
}
}
- // ==========================================
- // 🖼️ 核心 2:图片参数大全 (XB_ImageParamsMaster)
- // ==========================================
if (node.comfyClass === "XB_ImageParamsMaster" && node.widgets) {
const wRatio = node.widgets.find(w => w.name === "aspect_ratio");
const wWidth = node.widgets.find(w => w.name === "width");
@@ -210,10 +198,9 @@ app.registerExtension({
let needsUpdate = false;
let valRatio = wRatio.value;
- let isFree = valRatio.includes("自由");
+ let isFree = valRatio.includes("Free");
let rChanged = valRatio !== node._xb_last_ratio;
- // 1. 宽高步长安全引擎
let currentStep = isFree ? 1 : 16;
wWidth.options.step = currentStep; wHeight.options.step = currentStep;
if (wWidth.inputEl) wWidth.inputEl.step = currentStep; else if (wWidth.element) wWidth.element.step = currentStep;
diff --git a/js/xb_wiring.js b/js/xb_wiring.js
index d2ad82c..c08de4e 100644
--- a/js/xb_wiring.js
+++ b/js/xb_wiring.js
@@ -1,8 +1,7 @@
import { app } from "../../scripts/app.js";
-// ==============================================================================
-// 独立模块:深色工业风自定义弹窗
-// ==============================================================================
+const isZH = navigator.language.startsWith("zh");
+
function showCustomPrompt(title, defaultValue, callback) {
const existing = document.getElementById("xb-custom-prompt");
if (existing) existing.remove();
@@ -40,13 +39,13 @@ function showCustomPrompt(title, defaultValue, callback) {
btnContainer.style.cssText = "display: flex; justify-content: flex-end; gap: 12px;";
const btnCancel = document.createElement("button");
- btnCancel.innerText = "取消";
+ btnCancel.innerText = isZH ? "取消" : "Cancel";
btnCancel.style.cssText = "padding: 8px 16px; background: transparent; border: 1px solid #666; color: #ccc; border-radius: 4px; cursor: pointer; font-size: 13px; transition: 0.2s;";
btnCancel.onmouseover = () => { btnCancel.style.background = "#333"; };
btnCancel.onmouseout = () => { btnCancel.style.background = "transparent"; };
const btnOk = document.createElement("button");
- btnOk.innerText = "确定";
+ btnOk.innerText = isZH ? "确定" : "OK";
btnOk.style.cssText = "padding: 8px 16px; background: #4CAF50; border: none; color: #fff; border-radius: 4px; cursor: pointer; font-size: 13px; transition: 0.2s;";
btnOk.onmouseover = () => { btnOk.style.background = "#45a049"; };
btnOk.onmouseout = () => { btnOk.style.background = "#4CAF50"; };
@@ -95,16 +94,11 @@ app.registerExtension({
this.title = "";
this.color = "#151e29";
this.bgcolor = "#0B1116";
- // 初始化极度压缩的尺寸
this.size = [90, 46];
};
- // ==========================================
- // 【究极榨干法】:高度死锁公式极限压缩
- // ==========================================
const getExactHeight = (node) => {
const rows = Math.max(node.inputs ? node.inputs.length : 0, node.outputs ? node.outputs.length : 0);
- // 30(标题高) + 孔位总高 + 16(极限底部留白,原先是28)
return 30 + rows * LiteGraph.NODE_SLOT_HEIGHT + 16;
};
@@ -113,8 +107,8 @@ app.registerExtension({
};
nodeType.prototype.onResize = function(size) {
- size[1] = getExactHeight(this); // 高度绝对死锁
- if (size[0] < 90) size[0] = 90; // 宽度防崩
+ size[1] = getExactHeight(this);
+ if (size[0] < 90) size[0] = 90;
};
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
@@ -165,11 +159,9 @@ app.registerExtension({
ctx.textBaseline = "middle";
const centerX = this.size[0] / 2;
- // 按钮核心位置极限上提!距底部仅 10 像素
const btnY = this.size[1] - 10;
const btnInterval = 28;
- // 深色护盾也同步压缩高度 (18像素高,完美包裹字体)
const baseW = btnInterval * 2 + 28;
ctx.fillStyle = "#0B1116";
ctx.beginPath();
@@ -177,7 +169,6 @@ app.registerExtension({
ctx.fill();
ctx.font = "16px Arial";
- // 调整视觉重心,让 emoji 看起来在护盾的正中央
ctx.fillStyle = "#4CAF50"; ctx.fillText("➕", centerX - btnInterval, btnY);
ctx.fillStyle = "#FFC107"; ctx.fillText("⭕", centerX, btnY);
ctx.fillStyle = "#F44336"; ctx.fillText("➖", centerX + btnInterval, btnY);
@@ -188,7 +179,6 @@ app.registerExtension({
const onMouseDown = nodeType.prototype.onMouseDown;
nodeType.prototype.onMouseDown = function(e, pos, canvas) {
const centerX = this.size[0] / 2;
- // Hitbox 同步极限上提
const btnY = this.size[1] - 10;
const btnInterval = 28;
@@ -216,7 +206,7 @@ app.registerExtension({
const idx = this.portCount - 1;
if ((this.inputs[idx] && this.inputs[idx].link !== null) ||
(this.outputs[idx] && this.outputs[idx].links && this.outputs[idx].links.length > 0)) {
- alert("【防呆设计】该通道上还有连线,请拔除线缆后再移除通道!");
+ alert(isZH ? "【防呆设计】该通道上还有连线,请拔除线缆后再移除通道!" : "[Safety] Wires are still connected to this channel. Please disconnect before removing!");
} else {
this.removeInput(idx);
this.removeOutput(idx);
@@ -250,8 +240,9 @@ app.registerExtension({
if (clickedRow >= 0) {
const currentType = this.inputs[clickedRow].customType || "*";
+ const promptTitle = isZH ? `📌 修改通道 ${clickedRow + 1} 的自定义类型:\n(留空则恢复为通配符 ANY)` : `[Edit Type] Channel ${clickedRow + 1}:\n(Leave blank to restore ANY wildcard)`;
showCustomPrompt(
- `📌 修改通道 ${clickedRow + 1} 的自定义类型:\n(留空则恢复为通配符 ANY)`,
+ promptTitle,
currentType === "*" ? "" : currentType,
(newType) => {
const t = newType.trim() === "" ? "*" : newType.toUpperCase();
diff --git a/locales/zh/main.json b/locales/zh/main.json
new file mode 100644
index 0000000..7f74839
--- /dev/null
+++ b/locales/zh/main.json
@@ -0,0 +1,49 @@
+{
+ "nodeCategories": {
+ "XB_ToolBox": "小白工具箱",
+ "Pipeline": "流水线架构",
+ "Wiring": "布线整理",
+ "VRAM_Hacks": "显存特攻",
+ "Dashboard": "总控中心",
+ "Image_Params": "图像参数",
+ "Params_Control": "参数控制",
+ "Tile_Tools": "分块工具",
+ "Batch_Process": "批量处理"
+ },
+ "Mode A (128x128x32)": "模式A (128x128x32)",
+ "Mode B (128x64x96)": "模式 B (128x64x96)",
+ "Mode C (128x16x16)": "模式 C (128x16x16)",
+ "Mode D (64x64x16)": "模式 D (64x64x16)",
+ "Full Preview": "完整预览",
+ "Chunks Only": "仅显示分块",
+ "Spatial Only": "仅画面",
+ "Temporal Only": "仅时间",
+ "Spatial & Temporal": "画面与时间",
+ "Encode Stage": "编码阶段",
+ "Decode Stage": "解码阶段",
+ "Free": "自由 (Free)",
+ "Free Mode": "自由模式",
+ "Model Mode": "模型模式",
+ "Encode Mode": "编码模式",
+ "Decode Mode": "解码模式",
+ "Ratio Mode": "比例模式",
+ "Other Mode": "其他模式",
+ "3-Grid (1x3 Horizontal)": "三宫格 (1行3列 · 横向)",
+ "3-Grid (3x1 Vertical)": "三宫格 (3行1列 · 竖向)",
+ "4-Grid (2x2)": "四宫格 (2行2列)",
+ "6-Grid (2x3 Horizontal)": "六宫格 (2行3列 · 横向)",
+ "6-Grid (3x2 Vertical)": "六宫格 (3行2列 · 竖向)",
+ "9-Grid (3x3)": "九宫格 (3行3列)",
+ "Browse Folder...": "📁 浏览文件夹...",
+ "Mother Node Disconnected": "⚠️ 母节点已断开",
+ "Mirror: ": "🪞 ",
+ "Batch Select Components": "📦 批量选择要克隆的组件",
+ "Select All / Invert": "☑ 全选 / 反选",
+ "Execute Clone": "🚀 执行克隆",
+ "Are you sure you want to destroy all cloned components?": "确定要销毁所有克隆组件吗?",
+ "No available nodes.": "当前工作流无可用节点。",
+ "Locked by connection": "🔒 端口已被连线接管",
+ "Select/Upload End Frame": "选择上传尾帧图片",
+ "Cancel": "取消",
+ "OK": "确定"
+}
\ No newline at end of file
diff --git a/locales/zh/nodeDefs.json b/locales/zh/nodeDefs.json
new file mode 100644
index 0000000..c1224d8
--- /dev/null
+++ b/locales/zh/nodeDefs.json
@@ -0,0 +1,321 @@
+{
+ "XB_VRAM_Calculator": {
+ "display_name": "XB-BOX - 📟 可用显存计算",
+ "inputs": {
+ "Total_VRAM_GB": { "name": "显卡总显存_GB" },
+ "System_Overhead_GB": { "name": "系统开销_GB" },
+ "Main_Video_Model": { "name": "视频主模型" },
+ "Model_Quantization": { "name": "模型量化" },
+ "Total_Model_Layers": { "name": "主模型总层数" },
+ "Layers_to_Swap": { "name": "下放层数" },
+ "LoRA_Plugin_VRAM_GB": { "name": "LoRA及插件总占用_GB" },
+ "Context_Length_Frames": { "name": "上下文总帧数" },
+ "Spatial_Resolution": { "name": "空间分辨率" }
+ },
+ "outputs": {
+ "0": { "name": "可用显存输出" }
+ }
+ },
+ "XB_ChunkVisualization": {
+ "display_name": "XB-BOX - 🧊 时空分块预览",
+ "inputs": {
+ "Stat_Mode": { "name": "统计模式" },
+ "Available_VRAM_GB": { "name": "可用显存_GB" },
+ "Image_Width": { "name": "图片宽度" },
+ "Image_Height": { "name": "图片高度" },
+ "Image_Frames": { "name": "图像帧数" },
+ "Current_Stage": { "name": "当前阶段" },
+ "Spatial_Tile_Size": { "name": "分块大小" },
+ "Spatial_Tile_Overlap": { "name": "分块重叠" },
+ "Temporal_Chunk_Size": { "name": "切片大小" },
+ "Temporal_Chunk_Overlap": { "name": "切片重叠" },
+ "Spatial_Tiling": { "name": "空间分块" },
+ "Temporal_Chunking": { "name": "时间切片" },
+ "Image_Input": { "name": "图像输入" }
+ },
+ "outputs": {
+ "0": { "name": "预览图" },
+ "1": { "name": "管道数量" },
+ "2": { "name": "单管像素量" },
+ "3": { "name": "上下文峰值" }
+ }
+ },
+ "XTX_VRAM_Cleaner": {
+ "display_name": "XB-BOX - 🧹 显存清理大师",
+ "inputs": {
+ "Nuclear_Cleanup": { "name": "核爆级清理" },
+ "model": { "name": "模型" },
+ "positive": { "name": "正面提示词" },
+ "negative": { "name": "负面提示词" },
+ "latent_image": { "name": "潜变量" }
+ },
+ "outputs": {
+ "0": { "name": "模型" },
+ "1": { "name": "正面提示词" },
+ "2": { "name": "负面提示词" },
+ "3": { "name": "潜变量" }
+ }
+ },
+ "XTX_Data_Radar": {
+ "display_name": "XB-BOX - 🪞 生成数据预览",
+ "inputs": {
+ "model": { "name": "模型" },
+ "latent": { "name": "潜变量" },
+ "positive_cond": { "name": "正面条件" },
+ "negative_cond": { "name": "负面条件" }
+ },
+ "outputs": {
+ "0": { "name": "模型" },
+ "1": { "name": "潜变量" }
+ }
+ },
+ "XB_VideoParamsMaster": {
+ "display_name": "XB-BOX - 🎬 视频参数大全",
+ "inputs": {
+ "aspect_ratio": { "name": "画幅比例" },
+ "duration_display": { "name": "时长估算" },
+ "width": { "name": "宽度" },
+ "height": { "name": "高度" },
+ "length": { "name": "帧数" },
+ "fps": { "name": "帧率" },
+ "fps_float": { "name": "帧率(小数)" }
+ },
+ "outputs": {
+ "0": { "name": "宽度" },
+ "1": { "name": "高度" },
+ "2": { "name": "帧数" },
+ "3": { "name": "帧率" },
+ "4": { "name": "帧率(小数)" },
+ "5": { "name": "缩放尺寸" }
+ }
+ },
+ "XB_ImageParamsMaster": {
+ "display_name": "XB-BOX - 🖼️ 图片参数大全",
+ "inputs": {
+ "aspect_ratio": { "name": "画幅比例" },
+ "width": { "name": "宽度" },
+ "height": { "name": "高度" },
+ "batch_size": { "name": "生成数量" },
+ "strength_float": { "name": "强度(小数)" },
+ "strength_int": { "name": "强度(整数)" }
+ },
+ "outputs": {
+ "0": { "name": "图片宽度" },
+ "1": { "name": "图片高度" },
+ "2": { "name": "生成数量" },
+ "3": { "name": "浮点控制" },
+ "4": { "name": "整数控制" },
+ "5": { "name": "缩放尺寸" }
+ }
+ },
+ "XB_MasterParameter": {
+ "display_name": "XB-BOX - 🎛️ 全能参数控制",
+ "inputs": {
+ "mode": { "name": "控制模式" },
+ "value": { "name": "数值" }
+ },
+ "outputs": {
+ "0": { "name": "整数(INT)" },
+ "1": { "name": "浮点(FLOAT)" }
+ }
+ },
+ "XB_UNetBlockSwap": {
+ "display_name": "XB-BOX - ✂️ 模型分块交换(UNet)",
+ "inputs": {
+ "unet_model": { "name": "UNet模型" },
+ "blocks_to_swap": { "name": "下放块数量" }
+ },
+ "outputs": {
+ "0": { "name": "UNet模型" }
+ }
+ },
+ "XB_CheckpointBlockSwap": {
+ "display_name": "XB-BOX - ✂️ 模型分块交换(Checkpoints)",
+ "inputs": {
+ "checkpoint_model": { "name": "大模型" },
+ "blocks_to_swap": { "name": "下放块数量" },
+ "offload_img_emb": { "name": "卸载图像嵌入层" },
+ "offload_txt_emb": { "name": "卸载文本嵌入层" },
+ "use_non_blocking": { "name": "启用非阻塞传输" }
+ },
+ "outputs": {
+ "0": { "name": "大模型" }
+ }
+ },
+ "XB_DynamicBus": {
+ "display_name": "XB-BOX - 🎛️ 动态总线插排"
+ },
+ "XB_UNetNameBroadcaster": {
+ "display_name": "XB-BOX - 🗂️ UNet 名称分发",
+ "inputs": {
+ "unet_name": { "name": "选择UNet模型" }
+ },
+ "outputs": {
+ "0": { "name": "UNet名称" }
+ }
+ },
+ "XB_CLIPNameBroadcaster": {
+ "display_name": "XB-BOX - 🗂️ CLIP 名称分发",
+ "inputs": {
+ "clip_name": { "name": "选择CLIP模型" }
+ },
+ "outputs": {
+ "0": { "name": "CLIP名称" }
+ }
+ },
+ "XB_Dashboard_Zen": {
+ "display_name": "XB-BOX - 🪄 远程控制中心"
+ },
+ "XB_SamplerChunkMaster": {
+ "display_name": "XB-BOX - 🧊 采样分块大师",
+ "inputs": {
+ "model": { "name": "模型" },
+ "stat_mode": { "name": "状态模式" },
+ "tile_size": { "name": "分块尺寸" },
+ "tile_overlap": { "name": "分块重叠" },
+ "frame_chunk_size": { "name": "时间切片大小" },
+ "frame_chunk_overlap": { "name": "时间切片重叠" },
+ "rocm_optimized": { "name": "ROCm显存优化" },
+ "latent_info": { "name": "潜变量信息" }
+ },
+ "outputs": {
+ "0": { "name": "模型" },
+ "1": { "name": "预览图" }
+ }
+ },
+ "XB_WanImageToVideo": {
+ "display_name": "XB-BOX - 🖼️ 单图转视频分块 (Wan)",
+ "inputs": {
+ "positive": { "name": "正面提示词" },
+ "negative": { "name": "负面提示词" },
+ "vae": { "name": "VAE解码器" },
+ "width": { "name": "宽度" },
+ "height": { "name": "高度" },
+ "length": { "name": "帧数" },
+ "batch_size": { "name": "生成数量" },
+ "vae_tile_size": { "name": "VAE分块大小" },
+ "clip_vision_output": { "name": "CLIP视觉特征" },
+ "start_image": { "name": "首帧图片" }
+ },
+ "outputs": {
+ "0": { "name": "正面提示词" },
+ "1": { "name": "负面提示词" },
+ "2": { "name": "潜变量" }
+ }
+ },
+ "XB_WanFirstLastFrameToVideo": {
+ "display_name": "XB-BOX - 🎞️ 首尾帧视频分块(Wan)",
+ "inputs": {
+ "positive": { "name": "正面提示词" },
+ "negative": { "name": "负面提示词" },
+ "vae": { "name": "VAE解码器" },
+ "width": { "name": "宽度" },
+ "height": { "name": "高度" },
+ "length": { "name": "帧数" },
+ "batch_size": { "name": "生成数量" },
+ "vae_tile_size": { "name": "VAE分块大小" },
+ "clip_vision_start_image": { "name": "首帧视觉特征" },
+ "clip_vision_end_image": { "name": "尾帧视觉特征" },
+ "start_image": { "name": "首帧图片" },
+ "end_image": { "name": "尾帧图片" }
+ },
+ "outputs": {
+ "0": { "name": "正面提示词" },
+ "1": { "name": "负面提示词" },
+ "2": { "name": "潜变量" }
+ }
+ },
+ "XB_BatchFolderLoader": {
+ "display_name": "XB-BOX - 📂 图片批量加载",
+ "inputs": {
+ "directory": { "name": "文件夹路径" },
+ "seed": { "name": "步进种子" }
+ },
+ "outputs": {
+ "0": { "name": "图像" },
+ "1": { "name": "遮罩" },
+ "2": { "name": "文件名" },
+ "3": { "name": "总数量" }
+ }
+ },
+ "XB_Wan_ParamBus": {
+ "display_name": "XB-BOX - 📦 视频参数总线",
+ "inputs": {
+ "model_high": { "name": "高噪模型" },
+ "model_low": { "name": "低噪模型" },
+ "clip": { "name": "CLIP模型" },
+ "vae": { "name": "VAE模型" },
+ "negative_prompt": { "name": "负面提示词" },
+ "width": { "name": "宽度" },
+ "height": { "name": "高度" },
+ "length": { "name": "帧数" },
+ "fps": { "name": "帧率" },
+ "vae_tile_size": { "name": "VAE分块大小" },
+ "steps": { "name": "总步数" },
+ "high_noise_steps": { "name": "高噪步数" },
+ "cfg": { "name": "引导系数" },
+ "sampler_name": { "name": "采样器" },
+ "scheduler": { "name": "调度器" }
+ },
+ "outputs": {
+ "0": { "name": "📦 WAN_BUS (动态总线)" }
+ }
+ },
+ "XB_Wan_RelayNode": {
+ "display_name": "XB-BOX - 🏃 首尾帧接力点",
+ "inputs": {
+ "wan_bus": { "name": "WAN动态总线" },
+ "start_image": { "name": "起始图片" },
+ "positive_prompt": { "name": "段落正向提示词" },
+ "seed": { "name": "随机种" },
+ "cut_first_frame": { "name": "去除首帧(接力去重)" },
+ "end_image_file": { "name": "选择尾图" },
+ "opt_end_image": { "name": "可选尾图连线" },
+ "prev_video": { "name": "前段视频" }
+ },
+ "outputs": {
+ "0": { "name": "📦 WAN_BUS (传给下段)" },
+ "1": { "name": "🖼️ 当前尾图 (接下段首图)" },
+ "2": { "name": "🎞️ 累加视频流" }
+ }
+ },
+ "XB_Video_Merger": {
+ "display_name": "XB-BOX - 🧩 视频无缝拼接",
+ "inputs": {
+ "video_1": { "name": "视频段 1" },
+ "video_2": { "name": "视频段 2" }
+ },
+ "outputs": {
+ "0": { "name": "🎞️ 完整长视频" }
+ }
+ },
+ "XB_StoryboardSlicer": {
+ "display_name": "XB-BOX - 🧊 分镜图片切割",
+ "inputs": {
+ "image": { "name": "图像" },
+ "mode": { "name": "切分模式" },
+ "crop_margin": { "name": "边缘裁剪比例" }
+ },
+ "outputs": {
+ "0": { "name": "图1" },
+ "1": { "name": "图2" },
+ "2": { "name": "图3" },
+ "3": { "name": "图4" },
+ "4": { "name": "图5" },
+ "5": { "name": "图6" },
+ "6": { "name": "图7" },
+ "7": { "name": "图8" },
+ "8": { "name": "图9" }
+ }
+ },
+ "XB_SageAttentionAccelerator": {
+ "display_name": "XB-BOX - 🚀 Sage注意力加速",
+ "inputs": {
+ "model": { "name": "模型" },
+ "preset": { "name": "加速档位预设" }
+ },
+ "outputs": {
+ "0": { "name": "模型" }
+ }
+ }
+}
\ No newline at end of file
diff --git a/nodes_batch.py b/nodes_batch.py
index f0c62ae..d7e856c 100644
--- a/nodes_batch.py
+++ b/nodes_batch.py
@@ -7,27 +7,23 @@ import folder_paths
import datetime
class XB_BatchFolderLoader:
- """
- 文件夹图片接力加载节点 (架构优化版)
- 支持自动归档路径输出与友好数量提示
- """
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"directory": ("STRING", {"default": ""}),
- "seed": ("INT", {"default": 0, "min": 0, "max": 9999999, "step": 1, "tooltip": "利用 seed 机制内置的自动步进器"}),
+ "seed": ("INT", {"default": 0, "min": 0, "max": 9999999, "step": 1, "tooltip": "Built-in auto-stepper using the seed mechanism"}),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "STRING", "STRING")
RETURN_NAMES = ("IMAGE", "MASK", "FILE_NAME", "TOTAL_COUNT")
FUNCTION = "load_images"
- CATEGORY = "小白工具箱/批量处理"
+ CATEGORY = "XB_ToolBox/Batch_Process"
def load_images(self, directory, seed):
if not os.path.isdir(directory):
- raise FileNotFoundError(f"未找到文件夹: {directory}")
+ raise FileNotFoundError(f"Directory not found: {directory}")
valid_extensions = {'.png', '.jpg', '.jpeg', '.webp', '.bmp', '.jfif'}
files = [f for f in os.listdir(directory) if os.path.splitext(f)[1].lower() in valid_extensions]
@@ -38,23 +34,16 @@ class XB_BatchFolderLoader:
files.sort(key=natural_sort_key)
if not files:
- raise Exception(f"文件夹内没有有效图片: {directory}")
+ raise Exception(f"No valid images found in directory: {directory}")
total_count = len(files)
current_file_idx = seed % total_count
file_path = os.path.join(directory, files[current_file_idx])
- # 1. 提取原始文件名(不含后缀)
raw_name = os.path.splitext(files[current_file_idx])[0]
-
- # 2. 获取真实的系统当天日期 (格式:2026-04-15)
today_str = datetime.datetime.now().strftime("%Y-%m-%d")
-
- # 3. 注入纯净的路径前缀,彻底绕过 ComfyUI 的解析坑
- formatted_file_name = f"图片/批量处理/{today_str}/{raw_name}"
-
- # 4. 构造友好的数量提示字符串
- total_count_display = f"识别到图片总数为:{total_count}"
+ formatted_file_name = f"Images/Batch/{today_str}/{raw_name}"
+ total_count_display = f"Total images detected: {total_count}"
img = Image.open(file_path)
img = ImageOps.exif_transpose(img)
diff --git a/nodes_blockswap.py b/nodes_blockswap.py
index 2dec27c..c3e7f53 100644
--- a/nodes_blockswap.py
+++ b/nodes_blockswap.py
@@ -12,46 +12,40 @@ class AnyType(str):
anyType = AnyType("*")
def is_dynamic_vram_flag_used():
- """嗅探用户是否在启动参数中显式使用了 --enable-dynamic-vram"""
return "--enable-dynamic-vram" in sys.argv
-
-# ==============================================================================
-# 节点 1:UNet/DiT 纯净版切割 (严格还原第一版的纯物理转移)
-# ==============================================================================
class XB_UNetBlockSwap:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
- "unet_model": (anyType, {"tooltip": "仅接受纯净的 UNet/DiT 模型输入"}),
+ "unet_model": (anyType, {"tooltip": "Accepts pure UNet/DiT model input only"}),
"blocks_to_swap": ("INT", {
"default": 10,
"min": 0,
"max": 200,
"step": 1,
- "tooltip": "分配:将前 N 个核心块外包给系统内存。数值越大越省显存,但速度越慢。"
+ "tooltip": "Number of core blocks to offload to system RAM. Higher values save VRAM but slow down generation."
}),
},
}
RETURN_TYPES = (anyType,)
RETURN_NAMES = ("unet_model",)
- CATEGORY = "小白工具箱/显存特攻"
+ CATEGORY = "XB_ToolBox/VRAM_Hacks"
FUNCTION = "set_callback"
def set_callback(self, unet_model: ModelPatcher, blocks_to_swap):
if not isinstance(unet_model, ModelPatcher):
return (unet_model,)
- # 🚨 嗅探与休眠机制 (亮橙色)
if is_dynamic_vram_flag_used():
- print("\n\033[93m[XB UNet块交换]\033[0m: ⚠️ 检测到参数 [--enable-dynamic-vram],分块节点自动休眠退让。")
+ print("\n\033[92m[XB UNet Block Swap]\033[0m: ⚠️ Detected parameter [--enable-dynamic-vram], block swap node auto-sleeping.")
return (unet_model,)
def swap_blocks(model_patcher: ModelPatcher, device_to, lowvram_model_memory, force_patch_weights, full_load):
base_model = model_patcher.model
- main_device = torch.device('cuda') # 严格还原第一版的设备指定
+ main_device = torch.device('cuda')
diffusion_model = getattr(base_model, 'diffusion_model', None)
if not diffusion_model:
@@ -70,11 +64,9 @@ class XB_UNetBlockSwap:
if not all_blocks:
return
- # 正常分块亮蓝色提示
- print(f"\033[96m[XB UNet块交换]\033[0m: 静态物理分块已激活!锁定 {len(all_blocks)} 个引擎模块")
+ print(f"\033[96m[XB UNet Block Swap]\033[0m: Static physical block swap activated! Locked {len(all_blocks)} engine modules.")
- # 🚀 大道至简:完全使用第一版的最原生的转移逻辑!
- for b, block in tqdm(enumerate(all_blocks), total=len(all_blocks), desc="正在切割 UNet 流水线"):
+ for b, block in tqdm(enumerate(all_blocks), total=len(all_blocks), desc="Slicing UNet pipeline"):
if b > blocks_to_swap:
block.to(main_device)
else:
@@ -88,15 +80,12 @@ class XB_UNetBlockSwap:
return (unet_model, )
-# ==============================================================================
-# 节点 2:Checkpoint 混合包裹切割 (严格还原第一版的纯物理转移)
-# ==============================================================================
class XB_CheckpointBlockSwap:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
- "checkpoint_model": (anyType, {"tooltip": "接受任何模型输入 (含 CLIP/VAE 等完整架构)。"}),
+ "checkpoint_model": (anyType, {"tooltip": "Accepts any model input (including CLIP/VAE)."}),
"blocks_to_swap": ("INT", {"default": 15, "min": 0, "max": 1000, "step": 1}),
"offload_img_emb": ("BOOLEAN", {"default": True}),
"offload_txt_emb": ("BOOLEAN", {"default": True}),
@@ -106,16 +95,15 @@ class XB_CheckpointBlockSwap:
RETURN_TYPES = (anyType,)
RETURN_NAMES = ("checkpoint_model",)
- CATEGORY = "小白工具箱/显存特攻"
+ CATEGORY = "XB_ToolBox/VRAM_Hacks"
FUNCTION = "set_callback"
def set_callback(self, checkpoint_model: ModelPatcher, blocks_to_swap, offload_txt_emb, offload_img_emb, use_non_blocking):
if not isinstance(checkpoint_model, ModelPatcher):
return (checkpoint_model,)
- # 🚨 嗅探与休眠机制 (亮橙色)
if is_dynamic_vram_flag_used():
- print("\n\033[93m[XB Checkpoint块交换]\033[0m: ⚠️ 检测到参数 [--enable-dynamic-vram],分块节点自动休眠退让。")
+ print("\n\033[92m[XB Checkpoint Block Swap]\033[0m: ⚠️ Detected parameter [--enable-dynamic-vram], block swap node auto-sleeping.")
return (checkpoint_model,)
def swap_blocks(model_patcher: ModelPatcher, device_to, lowvram_model_memory, force_patch_weights, full_load):
@@ -137,15 +125,13 @@ class XB_CheckpointBlockSwap:
break
if all_blocks:
- print(f"\033[96m[XB Checkpoint块交换]\033[0m: 静态物理分块已激活!锁定 {len(all_blocks)} 个引擎模块")
- # 🚀 大道至简:完全使用第一版的最原生的转移逻辑!
- for b, block in tqdm(enumerate(all_blocks), total=len(all_blocks), desc="正在切割 Checkpoint 流水线"):
+ print(f"\033[96m[XB Checkpoint Block Swap]\033[0m: Static physical block swap activated! Locked {len(all_blocks)} engine modules.")
+ for b, block in tqdm(enumerate(all_blocks), total=len(all_blocks), desc="Slicing Checkpoint pipeline"):
if b > blocks_to_swap:
block.to(main_device)
else:
block.to(model_patcher.offload_device)
- # 原生方法处理 Embedding
if offload_txt_emb:
for path in ['text_embedding', 'caption_encoder', 'text_encoder']:
if hasattr(diffusion_model, path) and getattr(diffusion_model, path) is not None:
diff --git a/nodes_dashboard.py b/nodes_dashboard.py
index 9e5c4e2..03379a0 100644
--- a/nodes_dashboard.py
+++ b/nodes_dashboard.py
@@ -1,10 +1,9 @@
class XB_Dashboard_Zen:
- """ XB 远程控制中心 (终极版) """
@classmethod
def INPUT_TYPES(s):
return {"required": {}}
RETURN_TYPES = ()
FUNCTION = "do_nothing"
- CATEGORY = "小白工具箱/总控中心"
+ CATEGORY = "XB_ToolBox/Dashboard"
def do_nothing(self, **kwargs):
return ()
\ No newline at end of file
diff --git a/nodes_pipeline.py b/nodes_pipeline.py
index 2fa8f86..b478069 100644
--- a/nodes_pipeline.py
+++ b/nodes_pipeline.py
@@ -7,9 +7,6 @@ import comfy.samplers
import folder_paths
from .nodes_wan_vae import XB_WanFirstLastFrameToVideo
-# ====================================================================
-# 积木一:【📦 视频参数打包总线】
-# ====================================================================
class XB_Wan_ParamBus:
@classmethod
def INPUT_TYPES(cls):
@@ -19,14 +16,11 @@ class XB_Wan_ParamBus:
"model_low": ("MODEL",),
"clip": ("CLIP",),
"vae": ("VAE",),
-
- "negative_prompt": ("STRING", {"multiline": True, "default": "色调艳丽,过曝,静态,细节模糊不清..."}),
-
+ "negative_prompt": ("STRING", {"multiline": True, "default": "vivid colors, overexposed, static, blurry details..."}),
"width": ("INT", {"forceInput": True}),
"height": ("INT", {"forceInput": True}),
"length": ("INT", {"forceInput": True}),
"fps": ("FLOAT", {"forceInput": True}),
-
"vae_tile_size": ("INT", {"default": 64, "min": 64, "max": 3840, "step": 32}),
"steps": ("INT", {"default": 4, "min": 1, "max": 100}),
"high_noise_steps": ("INT", {"default": 2, "min": 1, "max": 50}),
@@ -37,17 +31,13 @@ class XB_Wan_ParamBus:
}
RETURN_TYPES = ("WAN_BUS",)
- RETURN_NAMES = ("📦 WAN_BUS (动态总线)",)
+ RETURN_NAMES = ("📦 WAN_BUS (Dynamic Bus)",)
FUNCTION = "pack_bus"
- CATEGORY = "小白工具箱/流水线架构"
+ CATEGORY = "XB_ToolBox/Pipeline"
def pack_bus(self, **kwargs):
return (kwargs,)
-
-# ====================================================================
-# 积木二:【🏃 首尾帧接力节点】 (🟢 回归 Required,重新召唤图片面板)
-# ====================================================================
class XB_Wan_RelayNode:
@classmethod
def INPUT_TYPES(cls):
@@ -55,20 +45,16 @@ class XB_Wan_RelayNode:
files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))] if os.path.exists(input_dir) else []
files = sorted(files)
- # 兜底:防止真没图的时候前端发神经
if not files:
- files = ["[文件夹为空_请连线或上传]"]
+ files = ["[Folder empty_Please connect or upload]"]
return {
"required": {
"wan_bus": ("WAN_BUS",),
"start_image": ("IMAGE",),
-
- "positive_prompt": ("STRING", {"multiline": True, "default": "描述这一段的具体动作..."}),
+ "positive_prompt": ("STRING", {"multiline": True, "default": "Describe the specific action for this segment..."}),
"seed": ("INT", {"default": 123456789}),
- "cut_first_frame": ("BOOLEAN", {"default": True, "label_on": "是 (接力去重)", "label_off": "否 (保留首帧)"}),
-
- # 🟢 核心修复:必须放在 required 里,面板才会重新出现!
+ "cut_first_frame": ("BOOLEAN", {"default": True, "label_on": "Yes (Relay Deduplication)", "label_off": "No (Keep First Frame)"}),
"end_image_file": (files, {"image_upload": True})
},
"optional": {
@@ -78,31 +64,31 @@ class XB_Wan_RelayNode:
}
RETURN_TYPES = ("WAN_BUS", "IMAGE", "IMAGE")
- RETURN_NAMES = ("📦 WAN_BUS (传给下段)", "🖼️ 当前尾图 (接下段首图)", "🎞️ 累加视频流 (接下段或输出)")
+ RETURN_NAMES = ("📦 WAN_BUS (Pass to next)", "🖼️ Current End Image (Connect to next Start Image)", "🎞️ Accumulated Video Stream")
FUNCTION = "execute_relay"
- CATEGORY = "小白工具箱/流水线架构"
+ CATEGORY = "XB_ToolBox/Pipeline"
def execute_relay(self, wan_bus, start_image, positive_prompt, seed, cut_first_frame, end_image_file, opt_end_image=None, prev_video=None):
- print("\n🏃 [XB-BOX] 正在执行单批次首尾帧生成任务...")
+ print("\n🏃 [XB-BOX] Executing single-batch first/last frame generation task...")
b = wan_bus
if opt_end_image is not None:
end_image = opt_end_image
- print(f"🖼️ [XB-BOX] 检测到连线,正在优先使用 opt_end_image 端点传入的尾帧。")
+ print(f"🖼️ [XB-BOX] Connection detected, prioritizing end frame from opt_end_image endpoint.")
else:
- if end_image_file == "[文件夹为空_请连线或上传]":
- raise ValueError("🚨 [XB-BOX] 尾帧缺失!请使用左侧 opt_end_image 连入图片,或点击面板上传图片!")
+ if end_image_file == "[Folder empty_Please connect or upload]":
+ raise ValueError("🚨 [XB-BOX] End frame missing! Connect image to opt_end_image or upload via panel!")
image_path = folder_paths.get_annotated_filepath(end_image_file)
if not os.path.exists(image_path):
- raise ValueError(f"🚨 [XB-BOX] 找不到图片文件: {image_path}")
+ raise ValueError(f"🚨 [XB-BOX] Image file not found: {image_path}")
i = Image.open(image_path)
i = ImageOps.exif_transpose(i)
image_data = i.convert("RGB")
image_data = np.array(image_data).astype(np.float32) / 255.0
end_image = torch.from_numpy(image_data)[None,]
- print(f"🖼️ [XB-BOX] 已成功加载面板预览图: {end_image_file}")
+ print(f"🖼️ [XB-BOX] Successfully loaded panel preview image: {end_image_file}")
pos_cond, = nodes.CLIPTextEncode().encode(b["clip"], positive_prompt)
neg_cond, = nodes.CLIPTextEncode().encode(b["clip"], b["negative_prompt"])
@@ -115,7 +101,7 @@ class XB_Wan_RelayNode:
batch_size=1, vae_tile_size=b["vae_tile_size"]
)
- print(f"🔥 [XB-BOX] 启动高噪引擎 (0 -> {b['high_noise_steps']} 步)...")
+ print(f"🔥 [XB-BOX] Starting high noise engine (0 -> {b['high_noise_steps']} steps)...")
latent_high, = nodes.KSamplerAdvanced().sample(
model=b["model_high"], add_noise="enable", noise_seed=seed,
steps=b["steps"], cfg=b["cfg"], sampler_name=b["sampler_name"], scheduler=b["scheduler"],
@@ -123,7 +109,7 @@ class XB_Wan_RelayNode:
start_at_step=0, end_at_step=b["high_noise_steps"], return_with_leftover_noise="enable"
)
- print(f"❄️ [XB-BOX] 切换低噪引擎 ({b['high_noise_steps']} -> 补齐满步)...")
+ print(f"❄️ [XB-BOX] Switching to low noise engine ({b['high_noise_steps']} -> completion)...")
latent_low, = nodes.KSamplerAdvanced().sample(
model=b["model_low"], add_noise="disable", noise_seed=seed,
steps=b["steps"], cfg=b["cfg"], sampler_name=b["sampler_name"], scheduler=b["scheduler"],
@@ -143,7 +129,7 @@ class XB_Wan_RelayNode:
decoded_image = decoded_image[1:, :, :, :]
else:
decoded_image = decoded_image[:, 1:, :, :, :]
- print("✂️ [XB-BOX] 已触发物理去重:切除重叠首帧。")
+ print("✂️ [XB-BOX] Physical deduplication triggered: overlap first frame removed.")
if is_4d:
last_frame = decoded_image[-1:, :, :, :]
@@ -152,22 +138,18 @@ class XB_Wan_RelayNode:
last_frame = decoded_image[:, -1:, :, :, :]
out_length = decoded_image.shape[1]
- print(f"✅ [XB-BOX] 当前批次完成!新生成片段长度: {out_length} 帧")
+ print(f"✅ [XB-BOX] Current batch complete! New segment length: {out_length} frames")
if prev_video is not None:
- print("🧩 [XB-BOX] 检测到连入的上一段视频,正在执行雪球累加合并...")
+ print("🧩 [XB-BOX] Previous video connection detected, executing snowball accumulation merge...")
concat_dim = 0 if is_4d else 1
final_video = torch.cat([prev_video, decoded_image], dim=concat_dim)
- print(f"🎉 [XB-BOX] 雪球累加完成!当前长视频已达: {final_video.shape[concat_dim]} 帧")
+ print(f"🎉 [XB-BOX] Snowball accumulation complete! Current long video reached: {final_video.shape[concat_dim]} frames")
else:
final_video = decoded_image
return (wan_bus, last_frame, final_video)
-
-# ====================================================================
-# 积木三:【🧩 视频无缝拼接器】
-# ====================================================================
class XB_Video_Merger:
@classmethod
def INPUT_TYPES(cls):
@@ -177,9 +159,9 @@ class XB_Video_Merger:
return inputs
RETURN_TYPES = ("IMAGE",)
- RETURN_NAMES = ("🎞️ 完整长视频",)
+ RETURN_NAMES = ("🎞️ Full Long Video",)
FUNCTION = "merge_videos"
- CATEGORY = "小白工具箱/流水线架构"
+ CATEGORY = "XB_ToolBox/Pipeline"
def merge_videos(self, **kwargs):
videos = []
@@ -189,22 +171,18 @@ class XB_Video_Merger:
videos.append(vid)
if not videos:
- raise ValueError("🚨 [XB-BOX] 合并失败:至少需要连入一段视频!")
+ raise ValueError("🚨 [XB-BOX] Merge failed: At least one video must be connected!")
- print(f"🧩 [XB-BOX] 正在执行 {len(videos)} 段视频无缝物理合并...")
+ print(f"🧩 [XB-BOX] Executing seamless physical merge of {len(videos)} video segments...")
concat_dim = 0 if len(videos[0].shape) == 4 else 1
final_video = torch.cat(videos, dim=concat_dim)
total_frames = final_video.shape[concat_dim]
- print(f"🎉 [XB-BOX] 视频合并成功!长视频总帧数: {total_frames} 帧")
+ print(f"🎉 [XB-BOX] Video merge successful! Total frames: {total_frames}")
return (final_video,)
-
-# ====================================================================
-# 积木四:【🧊 分镜阵列切割机】
-# ====================================================================
class XB_StoryboardSlicer:
@classmethod
def INPUT_TYPES(cls):
@@ -212,31 +190,31 @@ class XB_StoryboardSlicer:
"required": {
"image": ("IMAGE",),
"mode": ([
- "三宫格 (1行3列 · 横向)",
- "三宫格 (3行1列 · 竖向)",
- "四宫格 (2行2列)",
- "六宫格 (2行3列 · 横向)",
- "六宫格 (3行2列 · 竖向)",
- "九宫格 (3行3列)"
- ], {"default": "四宫格 (2行2列)"}),
+ "3-Grid (1x3 Horizontal)",
+ "3-Grid (3x1 Vertical)",
+ "4-Grid (2x2)",
+ "6-Grid (2x3 Horizontal)",
+ "6-Grid (3x2 Vertical)",
+ "9-Grid (3x3)"
+ ], {"default": "4-Grid (2x2)"}),
"crop_margin": ("FLOAT", {"default": 0.00, "min": 0.00, "max": 0.45, "step": 0.01}),
}
}
RETURN_TYPES = ("IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE", "IMAGE")
- RETURN_NAMES = ("图1", "图2", "图3", "图4", "图5", "图6", "图7", "图8", "图9")
+ RETURN_NAMES = ("Img1", "Img2", "Img3", "Img4", "Img5", "Img6", "Img7", "Img8", "Img9")
FUNCTION = "slice_image"
- CATEGORY = "小白工具箱/流水线架构"
+ CATEGORY = "XB_ToolBox/Pipeline"
def slice_image(self, image, mode, crop_margin):
- print(f"\n✂️ [XB-BOX] 启动分镜切割机,当前模式: {mode} | 裁边比例: {crop_margin}")
+ print(f"\n✂️ [XB-BOX] Starting storyboard slicer, mode: {mode} | crop margin: {crop_margin}")
- if "1行3列" in mode: rows, cols = 1, 3
- elif "3行1列" in mode: rows, cols = 3, 1
- elif "2行2列" in mode: rows, cols = 2, 2
- elif "2行3列" in mode: rows, cols = 2, 3
- elif "3行2列" in mode: rows, cols = 3, 2
- elif "3行3列" in mode: rows, cols = 3, 3
+ if "1x3" in mode: rows, cols = 1, 3
+ elif "3x1" in mode: rows, cols = 3, 1
+ elif "2x2" in mode: rows, cols = 2, 2
+ elif "2x3" in mode: rows, cols = 2, 3
+ elif "3x2" in mode: rows, cols = 3, 2
+ elif "3x3" in mode: rows, cols = 3, 3
else: rows, cols = 2, 2
batch_size, h, w, channels = image.shape
@@ -247,7 +225,7 @@ class XB_StoryboardSlicer:
crop_y = int(slice_h * crop_margin)
crop_x = int(slice_w * crop_margin)
- print(f"📐 [XB-BOX] 原图: {w}x{h} -> 原始切片: {slice_w}x{slice_h} -> 环切后实际输出: {slice_w - crop_x*2}x{slice_h - crop_y*2}")
+ print(f"📐 [XB-BOX] Original: {w}x{h} -> Raw slice: {slice_w}x{slice_h} -> Final output after crop: {slice_w - crop_x*2}x{slice_h - crop_y*2}")
sliced_images = []
@@ -271,6 +249,6 @@ class XB_StoryboardSlicer:
sliced_images.append(black_placeholder)
empty_count += 1
- print(f"✅ [XB-BOX] 分割完成: {rows * cols} 张有效画面 | 🛡️ 已生成 {empty_count} 个纯黑防爆占位符。")
+ print(f"✅ [XB-BOX] Slicing complete: {rows * cols} valid images | 🛡️ Generated {empty_count} black placeholder(s).")
return tuple(sliced_images)
\ No newline at end of file
diff --git a/nodes_sageatt.py b/nodes_sageatt.py
new file mode 100644
index 0000000..2de9faf
--- /dev/null
+++ b/nodes_sageatt.py
@@ -0,0 +1,81 @@
+import comfy.model_patcher
+import torch
+
+class XB_SageAttentionAccelerator:
+ @classmethod
+ def INPUT_TYPES(s):
+ return {
+ "required": {
+ "model": ("MODEL",),
+ "preset": ([
+ "Mode A (128x128x32)",
+ "Mode B (128x64x96)",
+ "Mode C (128x16x16)",
+ "Mode D (64x64x16)",
+ ], {"default": "Mode C (128x16x16)"}),
+ }
+ }
+
+ RETURN_TYPES = ("MODEL",)
+ FUNCTION = "patch"
+ CATEGORY = "XB_ToolBox/VRAM_Hacks"
+
+ def patch(self, model, preset):
+ configs = {
+ "Mode A (128x128x32)": {'M': 128, 'N': 128, 'GROUP': 32, 'WAVE': 2, 'WARP': 8, 'NSTAGES': 1},
+ "Mode B (128x64x96)": {'M': 128, 'N': 64, 'GROUP': 96, 'WAVE': 3, 'WARP': 8, 'NSTAGES': 2},
+ "Mode C (128x16x16)": {'M': 128, 'N': 16, 'GROUP': 16, 'WAVE': 2, 'WARP': 4, 'NSTAGES': 2},
+ "Mode D (64x64x16)": {'M': 64, 'N': 64, 'GROUP': 16, 'WAVE': 4, 'WARP': 4, 'NSTAGES': 2},
+ }
+
+ selected_cfg = configs[preset]
+
+ def attention_override_sage(func, q, k, v, heads, mask=None, attn_precision=None, skip_reshape=False, skip_output_reshape=False, **kwargs):
+ try:
+ from sageattention import sageattn
+ except ImportError:
+ raise Exception("🚨 Error: sageattention module not found. Please verify installation.")
+
+ in_dtype = v.dtype
+
+ if q.dtype == torch.float32 or k.dtype == torch.float32 or v.dtype == torch.float32:
+ q, k, v = q.to(torch.float16), k.to(torch.float16), v.to(torch.float16)
+
+ if skip_reshape:
+ b, _, _, dim_head = q.shape
+ tensor_layout = "HND"
+ else:
+ b, _, dim_head = q.shape
+ dim_head //= heads
+ q = q.view(b, -1, heads, dim_head)
+ k = k.view(b, -1, heads, dim_head)
+ v = v.view(b, -1, heads, dim_head)
+ tensor_layout = "NHD"
+
+ if mask is not None:
+ if mask.ndim == 2: mask = mask.unsqueeze(0)
+ if mask.ndim == 3: mask = mask.unsqueeze(1)
+
+ out = sageattn(q, k, v, is_causal=False, tensor_layout=tensor_layout, sage_config=selected_cfg).to(in_dtype)
+
+ if tensor_layout == "HND":
+ if not skip_output_reshape:
+ out = out.transpose(1, 2).reshape(b, -1, heads * dim_head)
+ else:
+ if skip_output_reshape:
+ out = out.transpose(1, 2)
+ else:
+ out = out.reshape(b, -1, heads * dim_head)
+
+ return out
+
+ m = model.clone()
+
+ if "transformer_options" not in m.model_options:
+ m.model_options["transformer_options"] = {}
+
+ m.model_options["transformer_options"]["optimized_attention_override"] = attention_override_sage
+
+ print(f"\033[96m[XB-BOX]\033[0m: SageAttention engine switched to \033[92m{preset}\033[0m")
+
+ return (m,)
\ No newline at end of file
diff --git a/nodes_tile.py b/nodes_tile.py
index 1b28781..b6a0365 100644
--- a/nodes_tile.py
+++ b/nodes_tile.py
@@ -9,7 +9,7 @@ class XB_SamplerChunkMaster:
return {
"required": {
"model": ("MODEL",),
- "stat_mode": (["完整预览", "仅显示分块"], {"default": "完整预览"}),
+ "stat_mode": (["Full Preview", "Chunks Only"], {"default": "Full Preview"}),
"tile_size": ("INT", {"default": 448, "min": 128, "max": 1024, "step": 32}),
"tile_overlap": ("INT", {"default": 32, "min": 0, "max": 128, "step": 8}),
"frame_chunk_size": ("INT", {"default": 17, "min": 1, "max": 129, "step": 8}),
@@ -24,28 +24,24 @@ class XB_SamplerChunkMaster:
RETURN_TYPES = ("MODEL", "IMAGE")
RETURN_NAMES = ("model", "preview_image")
FUNCTION = "apply_and_preview"
- CATEGORY = "小白工具箱/分块工具"
+ CATEGORY = "XB_ToolBox/Tile_Tools"
def apply_and_preview(self, model, stat_mode, tile_size, tile_overlap, frame_chunk_size, frame_chunk_overlap, rocm_optimized, latent_info=None):
- # 🚨 架构级修复:绝对禁止使用 clone()!直接原地暴力注入,确保底层 C++/HIP 钩子能抓取到内存地址!
model.model_options["wan_tile_size"] = tile_size
model.model_options["wan_tile_overlap"] = tile_overlap
model.model_options["wan_frame_chunk"] = frame_chunk_size
- model_copy = model # 返回原对象引用
+ model_copy = model
- # 针对 ROCm 的激进策略适配
if rocm_optimized:
model.model_options["wan_frame_overlap"] = frame_chunk_overlap
model.model_options["rocm_optimized"] = True
- # 🧹 执行分块注入前,强制进行一次深度垃圾回收,打破 PyTorch 显存缓存池假象
gc.collect()
if torch.cuda.is_available():
torch.cuda.empty_cache()
- if hasattr(torch.cuda, "ipc_collect"): # 安全防护
+ if hasattr(torch.cuda, "ipc_collect"):
torch.cuda.ipc_collect()
- # 📊 自动推算显示参数
width = 1280
height = 720
total_frames = 81
@@ -59,7 +55,6 @@ class XB_SamplerChunkMaster:
height = samples.shape[2] * 8
width = samples.shape[3] * 8
- # 🎨 绘制高级可视化面板
preview_img = self.draw_complex_preview(
stat_mode, width, height, total_frames,
tile_size, tile_overlap,
diff --git a/nodes_video.py b/nodes_video.py
index f77f565..be39e87 100644
--- a/nodes_video.py
+++ b/nodes_video.py
@@ -3,7 +3,7 @@ class XB_ImageParamsMaster:
def INPUT_TYPES(cls):
return {
"required": {
- "aspect_ratio": (["自由 (Free)", "1:1", "16:9", "9:16", "4:3", "3:4", "21:9"], {"default": "自由 (Free)"}),
+ "aspect_ratio": (["Free", "1:1", "16:9", "9:16", "4:3", "3:4", "21:9"], {"default": "Free"}),
"width": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 1}),
"height": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 1}),
"batch_size": ("INT", {"default": 1, "min": 1, "max": 1000, "step": 1}),
@@ -13,12 +13,12 @@ class XB_ImageParamsMaster:
}
RETURN_TYPES = ("INT", "INT", "INT", "FLOAT", "INT", "INT")
- RETURN_NAMES = ("图片宽度", "图片高度", "生成数量", "浮点控制", "整数控制", "缩放尺寸")
+ RETURN_NAMES = ("Image Width", "Image Height", "Batch Size", "Float Control", "Int Control", "Scale Size")
FUNCTION = "process"
- CATEGORY = "小白工具箱/图像参数"
+ CATEGORY = "XB_ToolBox/Image_Params"
def process(self, aspect_ratio, width, height, batch_size, strength_float, strength_int):
- if "自由" in aspect_ratio:
+ if "Free" in aspect_ratio:
return (width, height, batch_size, float(strength_float), int(strength_int), max(width, height))
step = 16
@@ -27,14 +27,13 @@ class XB_ImageParamsMaster:
return (safe_w, safe_h, batch_size, float(strength_float), int(strength_int), max(safe_w, safe_h))
-
class XB_VideoParamsMaster:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
- "aspect_ratio": (["自由 (Free)", "1:1", "16:9", "9:16", "4:3", "3:4", "21:9"], {"default": "自由 (Free)"}),
- "duration_display": ("STRING", {"default": "视频时长: 0.00 秒", "multiline": False}),
+ "aspect_ratio": (["Free", "1:1", "16:9", "9:16", "4:3", "3:4", "21:9"], {"default": "Free"}),
+ "duration_display": ("STRING", {"default": "Video Duration: 0.00 s", "multiline": False}),
"width": ("INT", {"default": 480, "min": 64, "max": 8192, "step": 1}),
"height": ("INT", {"default": 832, "min": 64, "max": 8192, "step": 1}),
"length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 1}),
@@ -44,12 +43,12 @@ class XB_VideoParamsMaster:
}
RETURN_TYPES = ("INT", "INT", "INT", "INT", "FLOAT", "INT")
- RETURN_NAMES = ("宽度", "高度", "帧数", "帧率", "帧率_浮点", "缩放尺寸")
+ RETURN_NAMES = ("Width", "Height", "Frames", "FPS", "FPS_Float", "Scale Size")
FUNCTION = "process"
- CATEGORY = "小白工具箱/图像参数"
+ CATEGORY = "XB_ToolBox/Image_Params"
def process(self, aspect_ratio, duration_display, width, height, length, fps, fps_float):
- if "自由" in aspect_ratio:
+ if "Free" in aspect_ratio:
return (width, height, length, int(round(fps)), float(fps), max(width, height))
golden_buckets = {
@@ -71,37 +70,32 @@ class XB_VideoParamsMaster:
return (safe_w, safe_h, safe_len, final_fps, float(final_fps), max(safe_w, safe_h))
-
-# ==========================================
-# 🎛️ 新增:全能参数控制节点
-# ==========================================
class XB_MasterParameter:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
- "mode": (["自由模式", "模型模式", "编码模式", "解码模式", "比例模式", "其他模式"], {"default": "自由模式"}),
- # 🟢 恢复最纯正的 0-9999 范围,默认步长 0.01
+ "mode": (["Free Mode", "Model Mode", "Encode Mode", "Decode Mode", "Ratio Mode", "Other Mode"], {"default": "Free Mode"}),
"value": ("FLOAT", {"default": 1024.0, "min": 0.0, "max": 9999.0, "step": 0.01}),
}
}
RETURN_TYPES = ("INT", "FLOAT")
- RETURN_NAMES = ("整数 (INT)", "浮点 (FLOAT)")
+ RETURN_NAMES = ("Integer (INT)", "Float (FLOAT)")
FUNCTION = "get_value"
- CATEGORY = "小白工具箱/参数控制"
+ CATEGORY = "XB_ToolBox/Params_Control"
def get_value(self, mode, value):
- if mode == "自由模式":
+ if mode == "Free Mode":
val = max(0.0, min(9999.0, value))
- elif mode == "模型模式":
+ elif mode == "Model Mode":
val = max(0.0, min(50.0, value))
- elif mode in ["编码模式", "解码模式"]:
+ elif mode in ["Encode Mode", "Decode Mode"]:
val = max(64.0, min(3840.0, value))
val = round(val / 32) * 32
- elif mode == "比例模式":
+ elif mode == "Ratio Mode":
val = max(0.0, min(1.0, value))
- elif mode == "其他模式":
+ elif mode == "Other Mode":
val = max(0.0, min(9999.0, value))
else:
val = value
diff --git a/nodes_vis.py b/nodes_vis.py
index 6f206c0..761fde6 100644
--- a/nodes_vis.py
+++ b/nodes_vis.py
@@ -3,15 +3,9 @@ import numpy as np
import os
from PIL import Image, ImageDraw, ImageFont
-# ==========================================
-# 极客魔法:标识数据来源,并携带分层数据的超级浮点数
-# ==========================================
class CalculatedFloat(float):
pass
-# ==========================================
-# 📟 可用显存计算
-# ==========================================
class XB_VRAM_Calculator:
def __init__(self):
pass
@@ -20,22 +14,22 @@ class XB_VRAM_Calculator:
def INPUT_TYPES(cls):
return {
"required": {
- "显卡总显存_GB": ("INT", {"default": 24, "min": 4, "max": 128, "step": 1, "display": "number"}),
- "系统及背景开销_GB": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 64.0, "step": 0.5, "display": "number"}),
- "视频主模型": (["LTX-2.3 (22B)", "WAN2.2-I2V (14B)", "WAN2.2-T2V (14B)", "WAN2.2-Animate (14B)"], {"default": "LTX-2.3 (22B)"}),
- "模型量化": (["FP16/BF16", "FP8", "GGUF-Q8", "GGUF-Q6", "GGUF-Q4"], {"default": "FP8"}),
- "主模型总层数": ("INT", {"default": 48, "min": 1, "max": 200, "step": 1, "display": "number"}),
- "分层交换数值": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1, "display": "number"}),
- "LoRA及插件总计_GB": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 64.0, "step": 0.1, "display": "number"}),
+ "Total_VRAM_GB": ("INT", {"default": 24, "min": 4, "max": 128, "step": 1, "display": "number"}),
+ "System_Overhead_GB": ("FLOAT", {"default": 2.0, "min": 0.0, "max": 64.0, "step": 0.5, "display": "number"}),
+ "Main_Video_Model": (["LTX-2.3 (22B)", "WAN2.2-I2V (14B)", "WAN2.2-T2V (14B)", "WAN2.2-Animate (14B)"], {"default": "LTX-2.3 (22B)"}),
+ "Model_Quantization": (["FP16/BF16", "FP8", "GGUF-Q8", "GGUF-Q6", "GGUF-Q4"], {"default": "FP8"}),
+ "Total_Model_Layers": ("INT", {"default": 48, "min": 1, "max": 200, "step": 1, "display": "number"}),
+ "Layers_to_Swap": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1, "display": "number"}),
+ "LoRA_Plugin_VRAM_GB": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 64.0, "step": 0.1, "display": "number"}),
}
}
RETURN_TYPES = ("FLOAT",)
- RETURN_NAMES = ("可用显存输出",)
+ RETURN_NAMES = ("Available_VRAM",)
FUNCTION = "calculate_vram"
- CATEGORY = "小白工具箱/分块预览"
+ CATEGORY = "XB_ToolBox/Chunk_Preview"
- def calculate_vram(self, 显卡总显存_GB, 系统及背景开销_GB, 视频主模型, 模型量化, 主模型总层数, 分层交换数值, LoRA及插件总计_GB):
+ def calculate_vram(self, Total_VRAM_GB, System_Overhead_GB, Main_Video_Model, Model_Quantization, Total_Model_Layers, Layers_to_Swap, LoRA_Plugin_VRAM_GB):
vram_map = {
"LTX-2.3 (22B)": {"FP16/BF16": 44.0, "FP8": 22.5, "GGUF-Q8": 22.8, "GGUF-Q6": 17.8, "GGUF-Q4": 13.5},
"WAN2.2-I2V (14B)": {"FP16/BF16": 28.0, "FP8": 14.0, "GGUF-Q8": 14.5, "GGUF-Q6": 11.0, "GGUF-Q4": 9.0},
@@ -43,21 +37,18 @@ class XB_VRAM_Calculator:
"WAN2.2-Animate (14B)": {"FP16/BF16": 28.0, "FP8": 14.0, "GGUF-Q8": 14.5, "GGUF-Q6": 11.0, "GGUF-Q4": 9.0},
}
- full_model_size = vram_map.get(视频主模型, {}).get(模型量化, 14.0)
- stay_layers = max(0, 主模型总层数 - 分层交换数值)
- active_model_vram = (full_model_size / max(1, 主模型总层数)) * stay_layers
- available_vram = 显卡总显存_GB - 系统及背景开销_GB - active_model_vram - LoRA及插件总计_GB
+ full_model_size = vram_map.get(Main_Video_Model, {}).get(Model_Quantization, 14.0)
+ stay_layers = max(0, Total_Model_Layers - Layers_to_Swap)
+ active_model_vram = (full_model_size / max(1, Total_Model_Layers)) * stay_layers
+ available_vram = Total_VRAM_GB - System_Overhead_GB - active_model_vram - LoRA_Plugin_VRAM_GB
available_vram = max(0.1, available_vram)
res = CalculatedFloat(available_vram)
res._stay = stay_layers
- res._total = 主模型总层数
+ res._total = Total_Model_Layers
return (res,)
-# ==========================================
-# 🧊 极简双区浏览器:左 2D 原生镂空 + 右完美重叠圆柱
-# ==========================================
class XB_ChunkVisualization:
def __init__(self):
pass
@@ -66,26 +57,26 @@ class XB_ChunkVisualization:
def INPUT_TYPES(cls):
return {
"required": {
- "统计模式": (["仅画面", "仅时间", "画面与时间"], {"default": "画面与时间"}),
- "可用显存_GB": ("FLOAT", {"default": 12.0, "min": 0.1, "max": 128.0, "step": 0.1, "display": "number"}),
- "图像宽度": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16, "display": "number"}),
- "图像高度": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16, "display": "number"}),
- "图像帧数": ("INT", {"default": 1, "min": 1, "max": 10000, "step": 4, "display": "number"}),
- "当前阶段": (["编码阶段", "解码阶段"], {"default": "解码阶段"}),
- "图像分块大小": ("INT", {"default": 512, "min": 64, "max": 8192, "step": 32, "display": "number"}),
- "图像分块重叠": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32, "display": "number"}),
- "帧数分段大小": ("INT", {"default": 33, "min": 8, "max": 10000, "step": 4, "display": "number"}),
- "帧数分段重叠": ("INT", {"default": 4, "min": 0, "max": 1000, "step": 4, "display": "number"}),
+ "Stat_Mode": (["Spatial Only", "Temporal Only", "Spatial & Temporal"], {"default": "Spatial & Temporal"}),
+ "Available_VRAM_GB": ("FLOAT", {"default": 12.0, "min": 0.1, "max": 128.0, "step": 0.1, "display": "number"}),
+ "Image_Width": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16, "display": "number"}),
+ "Image_Height": ("INT", {"default": 1024, "min": 64, "max": 8192, "step": 16, "display": "number"}),
+ "Image_Frames": ("INT", {"default": 1, "min": 1, "max": 10000, "step": 4, "display": "number"}),
+ "Current_Stage": (["Encode Stage", "Decode Stage"], {"default": "Decode Stage"}),
+ "Spatial_Tile_Size": ("INT", {"default": 512, "min": 64, "max": 8192, "step": 32, "display": "number"}),
+ "Spatial_Tile_Overlap": ("INT", {"default": 64, "min": 0, "max": 4096, "step": 32, "display": "number"}),
+ "Temporal_Chunk_Size": ("INT", {"default": 33, "min": 8, "max": 10000, "step": 4, "display": "number"}),
+ "Temporal_Chunk_Overlap": ("INT", {"default": 4, "min": 0, "max": 1000, "step": 4, "display": "number"}),
},
"optional": {
- "图像输入": ("IMAGE",)
+ "Image_Input": ("IMAGE",)
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("preview_image",)
FUNCTION = "visualize_chunks"
- CATEGORY = "小白工具箱"
+ CATEGORY = "XB_ToolBox"
def get_1d_chunks(self, total_len, chunk_size, overlap):
chunks = []
@@ -99,28 +90,28 @@ class XB_ChunkVisualization:
curr += stride
return chunks
- def visualize_chunks(self, 统计模式, 可用显存_GB, 图像宽度, 图像高度, 图像帧数, 当前阶段,
- 图像分块大小, 图像分块重叠, 帧数分段大小, 帧数分段重叠, 图像输入=None):
+ def visualize_chunks(self, Stat_Mode, Available_VRAM_GB, Image_Width, Image_Height, Image_Frames, Current_Stage,
+ Spatial_Tile_Size, Spatial_Tile_Overlap, Temporal_Chunk_Size, Temporal_Chunk_Overlap, Image_Input=None):
- active_vram = float(可用显存_GB)
- is_calc = type(可用显存_GB).__name__ == "CalculatedFloat"
- stay_info = f" (模型驻留:{可用显存_GB._stay}/{可用显存_GB._total}层)" if (is_calc and hasattr(可用显存_GB, '_stay')) else ""
+ active_vram = float(Available_VRAM_GB)
+ is_calc = type(Available_VRAM_GB).__name__ == "CalculatedFloat"
+ stay_info = f" (Reside:{Available_VRAM_GB._stay}/{Available_VRAM_GB._total}L)" if (is_calc and hasattr(Available_VRAM_GB, '_stay')) else ""
- if 图像输入 is not None:
- B, H_orig, W_orig, C = 图像输入.shape
- source_img = Image.fromarray((图像输入[0].cpu().numpy() * 255).astype(np.uint8)).convert("RGBA")
- res_info = f"【图像尺寸】: {W_orig}x{H_orig} (来自图像输入)"
+ if Image_Input is not None:
+ B, H_orig, W_orig, C = Image_Input.shape
+ source_img = Image.fromarray((Image_Input[0].cpu().numpy() * 255).astype(np.uint8)).convert("RGBA")
+ res_info = f"[Size]: {W_orig}x{H_orig} (Input)"
else:
- W_orig, H_orig = 图像宽度, 图像高度
+ W_orig, H_orig = Image_Width, Image_Height
source_img = Image.new("RGBA", (W_orig, H_orig), (25, 25, 25, 255))
- res_info = f"【图像尺寸】: {W_orig}x{H_orig} (来自手动参数)"
+ res_info = f"[Size]: {W_orig}x{H_orig} (Param)"
- x_chunks = self.get_1d_chunks(W_orig, 图像分块大小 if "画面" in 统计模式 else 0, 图像分块重叠)
- y_chunks = self.get_1d_chunks(H_orig, 图像分块大小 if "画面" in 统计模式 else 0, 图像分块重叠)
- t_chunks = self.get_1d_chunks(图像帧数, 帧数分段大小 if "时间" in 统计模式 else 0, 帧数分段重叠)
+ x_chunks = self.get_1d_chunks(W_orig, Spatial_Tile_Size if "Spatial" in Stat_Mode else 0, Spatial_Tile_Overlap)
+ y_chunks = self.get_1d_chunks(H_orig, Spatial_Tile_Size if "Spatial" in Stat_Mode else 0, Spatial_Tile_Overlap)
+ t_chunks = self.get_1d_chunks(Image_Frames, Temporal_Chunk_Size if "Temporal" in Stat_Mode else 0, Temporal_Chunk_Overlap)
total_batches = len(x_chunks) * len(y_chunks) * len(t_chunks)
- base_vol = W_orig * H_orig * 图像帧数
+ base_vol = W_orig * H_orig * Image_Frames
real_vol, max_chunk_vol = 0, 0
for x1, x2 in x_chunks:
for y1, y2 in y_chunks:
@@ -141,35 +132,31 @@ class XB_ChunkVisualization:
return ImageFont.truetype(p, size) if os.path.exists(p) else ImageFont.load_default()
f_info = get_font(22)
- f_num = get_font(28) # 大号字体用于分区大标题和块标号
+ f_num = get_font(28)
temp_draw = ImageDraw.Draw(Image.new("RGBA", (1,1)))
- # ==========================================
- # 📐 独立区域布局计算
- # ==========================================
- show_time = ("时间" in 统计模式 and 图像帧数 > 1)
+ show_time = ("Temporal" in Stat_Mode and Image_Frames > 1)
sidebar_w = 250 if show_time else 0
gap = 50 if show_time else 0
- padding = 70 # 增加顶部 padding 以容纳大标题
+ padding = 70
draw_W = W_orig + gap + sidebar_w
- new_W = max(draw_W + 80, 700) # 左右各留 40
+ new_W = max(draw_W + 80, 700)
max_txt_w = new_W - 50
- # 数据面板排版
info_cats = [
- {"text": f"【统计模式】: {统计模式} / 【当前阶段】: {当前阶段}", "color": (0, 255, 255)},
+ {"text": f"[Mode]: {Stat_Mode} / [Stage]: {Current_Stage}", "color": (0, 255, 255)},
{"text": res_info, "color": (220, 220, 220)},
- {"text": f"【加载批次】: {total_batches} 批 (图像:{len(x_chunks)*len(y_chunks)} × 时间:{len(t_chunks)})", "color": (255, 255, 255)},
- {"text": f"【可用显存】: {active_vram:.1f}G{stay_info} ({'计算参数' if is_calc else '手输参数'})", "color": (150, 200, 255)},
- {"text": f"【总数据量】: {real_vol/1e6:.1f}M (基准:{base_vol/1e6:.1f}M | 冗余:+{redundancy:.1f}%) [注:M为百万像素点]", "color": (255, 150, 200)},
- {"text": f"【单批体积】: {max_chunk_vol/1e6:.1f}M / 批 / 【显存压力】: {kappa:.2f}", "color": p_color}
+ {"text": f"[Batches]: {total_batches} (S:{len(x_chunks)*len(y_chunks)} x T:{len(t_chunks)})", "color": (255, 255, 255)},
+ {"text": f"[VRAM]: {active_vram:.1f}G{stay_info}", "color": (150, 200, 255)},
+ {"text": f"[Volume]: {real_vol/1e6:.1f}M (Base:{base_vol/1e6:.1f}M | Redundancy:+{redundancy:.1f}%)", "color": (255, 150, 200)},
+ {"text": f"[Chunk]: {max_chunk_vol/1e6:.1f}M/b / [Pressure]: {kappa:.2f}", "color": p_color}
]
final_lines = []
for cat in info_cats:
- prefix, content = cat["text"].split("】: ", 1) if "】: " in cat["text"] else ("", cat["text"])
- if prefix: prefix += "】: "
+ prefix, content = cat["text"].split("]: ", 1) if "]: " in cat["text"] else ("", cat["text"])
+ if prefix: prefix += "]: "
prefix_w = int(temp_draw.textlength(prefix, font=f_info))
curr, is_first = "", True
for char in content:
@@ -185,13 +172,12 @@ class XB_ChunkVisualization:
line_h = 36
header_H = len(final_lines) * line_h + 80
- # 画布定位
paste_x = int((new_W - draw_W) / 2)
paste_y = header_H + padding
full_canvas = Image.new("RGBA", (int(new_W), int(paste_y + H_orig + 40)), (20, 20, 20, 255))
- if 统计模式 != "仅时间":
+ if Stat_Mode != "Temporal Only":
full_canvas.paste(source_img, (paste_x, paste_y))
else:
faded = source_img.copy()
@@ -200,7 +186,6 @@ class XB_ChunkVisualization:
draw = ImageDraw.Draw(full_canvas, "RGBA")
- # 数据区文本
y = 15
for txt, clr, x_off in final_lines:
draw.text((25 + x_off, y), txt, fill=clr, font=f_info)
@@ -211,23 +196,18 @@ class XB_ChunkVisualization:
colors = [(0, 255, 0), (0, 255, 255), (255, 255, 0), (255, 0, 255), (0, 150, 255), (255, 120, 0), (255, 50, 50)]
- # 🚨 绘制分区大标题
title_y = paste_y - 45
bar_cx = paste_x + W_orig + gap + sidebar_w // 2 - 40
- if "画面" in 统计模式:
- draw.text((paste_x + W_orig//2 - 84, title_y), "【图像分块】", fill=(220,220,220,255), font=f_num)
+ if "Spatial" in Stat_Mode:
+ draw.text((paste_x + W_orig//2 - 84, title_y), "[Spatial]", fill=(220,220,220,255), font=f_num)
if show_time:
- draw.text((bar_cx - 84, title_y), "【时间分块】", fill=(220,220,220,255), font=f_num)
+ draw.text((bar_cx - 84, title_y), "[Temporal]", fill=(220,220,220,255), font=f_num)
- # ==========================================
- # 🖼️ 1. 左侧:绝对原版的镂空 2D 网格
- # ==========================================
- if "画面" in 统计模式:
+ if "Spatial" in Stat_Mode:
spatial_idx = 0
for y1, y2 in y_chunks:
for x1, x2 in x_chunks:
c = colors[spatial_idx % len(colors)]
- # 绝对镂空,不加任何透明底色
rect = [paste_x + x1, paste_y + y1, paste_x + x2, paste_y + y2]
draw.rectangle(rect, fill=None, outline=c, width=3)
@@ -237,17 +217,12 @@ class XB_ChunkVisualization:
spatial_idx += 1
- # ==========================================
- # ⏱️ 2. 右侧:完美的 3D 画家算法圆柱堆叠
- # ==========================================
if show_time:
rx = 60
ry = 18
- # 画轨道底线
draw.line([bar_cx, paste_y, bar_cx, paste_y + H_orig], fill=(70, 70, 70, 255), width=2)
-
- scale_y = H_orig / max(1, 图像帧数)
+ scale_y = H_orig / max(1, Image_Frames)
def draw_cylinder(d, cx, cy1, cy2, rx, ry, fill_color, outline_color, line_width):
if fill_color:
@@ -262,13 +237,11 @@ class XB_ChunkVisualization:
d.line([cx + rx, cy1, cx + rx, cy2], fill=outline_color, width=line_width)
d.ellipse([cx - rx, cy1 - ry, cx + rx, cy1 + ry], fill=None, outline=outline_color, width=line_width)
- # 2.1 画家算法排序:从底部的切片开始往上画
for i in range(len(t_chunks)-1, -1, -1):
t1, t2 = t_chunks[i]
y1 = paste_y + int(t1 * scale_y)
y2 = paste_y + int(t2 * scale_y)
- # 🚨 修正核心:对称膨胀,保证真理中心绝对不偏移
true_mid_y = (y1 + y2) / 2
if y2 - y1 < ry * 2:
y1 = true_mid_y - ry
@@ -278,11 +251,9 @@ class XB_ChunkVisualization:
body_color = (c[0]//5, c[1]//5, c[2]//5, 200)
draw_cylinder(draw, bar_cx, y1, y2, rx, ry, fill_color=body_color, outline_color=c, line_width=2)
- # 指示线永远对准 true_mid_y
draw.line([bar_cx + rx + 5, true_mid_y, bar_cx + rx + 15, true_mid_y], fill=c, width=3)
draw.text((bar_cx + rx + 22, true_mid_y - 15), f"T{i+1}: F{t1}-{t2}", fill=c, font=f_info, stroke_width=3, stroke_fill=(0,0,0,255))
- # 2.2 提取并绘制纯白的“极粗覆盖重叠框”
overlaps = []
for i in range(len(t_chunks)):
for j in range(i+1, len(t_chunks)):
@@ -296,18 +267,15 @@ class XB_ChunkVisualization:
y1 = paste_y + int(o_start * scale_y)
y2 = paste_y + int(o_end * scale_y)
- # 🚨 修正核心:对称膨胀,保证真理中心绝对不偏移
true_mid_y = (y1 + y2) / 2
if y2 - y1 < ry * 2:
y1 = true_mid_y - ry
y2 = true_mid_y + ry
- # 🚨 使用与彩色圆柱相同的半径,但加粗到 4,完美吞没多余边缘!
draw_cylinder(draw, bar_cx, y1, y2, rx, ry, fill_color=None, outline_color=(255, 255, 255, 255), line_width=4)
- # 🚨 警示黄高亮,彻底解决红底黑字不清晰的问题
- hl_color = (255, 255, 0, 255) # 纯黄
+ hl_color = (255, 255, 0, 255)
draw.line([bar_cx + rx + 4, true_mid_y, bar_cx + rx + 30, true_mid_y], fill=hl_color, width=3)
- draw.text((bar_cx + rx + 35, true_mid_y - 15), "重叠区", fill=hl_color, font=f_info, stroke_width=2, stroke_fill=(0,0,0,255))
+ draw.text((bar_cx + rx + 35, true_mid_y - 15), "Overlap", fill=hl_color, font=f_info, stroke_width=2, stroke_fill=(0,0,0,255))
return (torch.from_numpy(np.array(full_canvas.convert("RGB")).astype(np.float32) / 255.0).unsqueeze(0),)
\ No newline at end of file
diff --git a/nodes_vram.py b/nodes_vram.py
index 40f9bea..cbeebfd 100644
--- a/nodes_vram.py
+++ b/nodes_vram.py
@@ -2,9 +2,6 @@ import torch
import gc
import comfy.model_management as mm
-# ======================================================================
-# 🧹 显存清理大师 (数据过滤 & 战前清场)
-# ======================================================================
class XTX_VRAM_Cleaner:
@classmethod
def INPUT_TYPES(s):
@@ -14,64 +11,59 @@ class XTX_VRAM_Cleaner:
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"latent_image": ("LATENT",),
- "核爆级清理": ("BOOLEAN", {"default": True}),
+ "Nuclear_Cleanup": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("MODEL", "CONDITIONING", "CONDITIONING", "LATENT")
RETURN_NAMES = ("model", "positive", "negative", "latent")
FUNCTION = "clean_vram"
- CATEGORY = "小白工具箱/显存特攻"
+ CATEGORY = "XB_ToolBox/VRAM_Hacks"
- def clean_vram(self, model, positive, negative, latent_image, 核爆级清理):
+ def clean_vram(self, model, positive, negative, latent_image, Nuclear_Cleanup):
print("\n" + "🚀" * 15)
- print("🧹 [小白工具箱] 显存清理大师:正在执行战前清场...")
- mm.unload_all_models() # 强制卸载无用模型(如 CLIP)
+ print("🧹 [XB-BOX] VRAM Cleaner: Executing pre-generation sweep...")
+ mm.unload_all_models()
gc.collect()
- if 核爆级清理:
+ if Nuclear_Cleanup:
mm.soft_empty_cache()
if torch.cuda.is_available():
torch.cuda.empty_cache()
- # 动态探测 0.20.X 的 IPC 垃圾回收 (专治多线程显存碎片)
if hasattr(torch.cuda, "ipc_collect"):
torch.cuda.ipc_collect()
- print("💥 已执行核爆级清理,显存碎片已彻底蒸发!")
- print("✅ 战场打扫完毕,核心数据已就绪!")
+ print("☢️ [XB-BOX] Nuclear VRAM Cleanup completed.")
print("🚀" * 15 + "\n")
return (model, positive, negative, latent_image)
-# ======================================================================
-# 🪞 生成数据预览 (代码保持不变)
-# ======================================================================
class XTX_Data_Radar:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
+ "model": ("MODEL",),
"latent": ("LATENT",),
},
"optional": {
- "正面条件": ("CONDITIONING",),
- "负面条件": ("CONDITIONING",),
- "标注信息": ("STRING", {"default": "生成数据预览"}),
+ "positive_cond": ("CONDITIONING",),
+ "negative_cond": ("CONDITIONING",),
}
}
- RETURN_TYPES = ("LATENT", "CONDITIONING", "CONDITIONING", "STRING")
- RETURN_NAMES = ("latent", "positive", "negative", "显存雷达报告")
+ RETURN_TYPES = ("MODEL", "LATENT")
+ RETURN_NAMES = ("model", "latent")
FUNCTION = "scan_data"
- CATEGORY = "小白工具箱/数据预览"
+ CATEGORY = "XB_ToolBox/VRAM_Hacks"
- def scan_data(self, latent, 正面条件=None, 负面条件=None, 标注信息=""):
- report = []
- report.append(f"🔍 [{标注信息}] 数据照妖镜扫描 🔍")
- report.append("-" * 35)
+ def scan_data(self, model, latent, positive_cond=None, negative_cond=None):
+ print("\n" + "=" * 35)
+ print(f"🪞 [XB-BOX] Data Generation Radar Scan 🔍")
+ print("-" * 35)
if "samples" in latent:
samples = latent["samples"]
size_mb = (samples.element_size() * samples.nelement()) / (1024 * 1024)
- report.append(f"📦 潜变量(Latent): {size_mb:.2f} MB")
- report.append(f"📐 形状: {list(samples.shape)}")
+ print(f"📦 Latent: {size_mb:.2f} MB")
+ print(f"📐 Shape: {list(samples.shape)}")
def get_cond_weight(cond):
total_size = 0
@@ -83,19 +75,17 @@ class XTX_Data_Radar:
total_size += tensor.element_size() * tensor.nelement()
return total_size / (1024 * 1024)
- pos_w = get_cond_weight(正面条件)
- neg_w = get_cond_weight(负面条件)
- report.append(f"✅ 正面条件重量: {pos_w:.2f} MB")
- report.append(f"❌ 负面条件重量: {neg_w:.2f} MB")
- report.append("-" * 35)
+ pos_w = get_cond_weight(positive_cond)
+ neg_w = get_cond_weight(negative_cond)
+ print(f"✅ Positive Cond Weight: {pos_w:.2f} MB")
+ print(f"❌ Negative Cond Weight: {neg_w:.2f} MB")
+ print("-" * 35)
if torch.cuda.is_available():
allocated = torch.cuda.memory_allocated() / (1024**3)
reserved = torch.cuda.memory_reserved() / (1024**3)
- report.append(f"💻 实际显存占用: {allocated:.2f} GB")
- report.append(f"🔒 系统圈占总量: {reserved:.2f} GB")
- report.append(f"⚠️ 缓存碎片大小: {(reserved - allocated):.2f} GB")
+ print(f"🔥 GPU Current Allocated: {allocated:.2f} GB")
+ print(f"🛡️ GPU Current Reserved: {reserved:.2f} GB")
+ print("=" * 35 + "\n")
- res_str = "\n".join(report)
- print("\n" + res_str + "\n")
- return (latent, 正面条件, 负面条件, res_str)
\ No newline at end of file
+ return (model, latent)
\ No newline at end of file
diff --git a/nodes_wan_vae.py b/nodes_wan_vae.py
index d1287ec..e90a537 100644
--- a/nodes_wan_vae.py
+++ b/nodes_wan_vae.py
@@ -4,20 +4,12 @@ import comfy.utils
import node_helpers
import comfy.clip_vision
-# ==========================================
-# 核心辅助函数:VAE 空间分块编码
-# ==========================================
def _encode_vae(vae, pixels, tile_size):
if tile_size == 0:
return vae.encode(pixels[:, :, :, :3])
else:
- # VAE 编码分块,降低图片转 Latent 瞬间的显存峰值
return vae.encode_tiled(pixels[:, :, :, :3], tile_x=tile_size, tile_y=tile_size, overlap=32, tile_t=256, overlap_t=8)
-
-# ==========================================
-# 节点 1:WAN 图像到视频 (带 VAE 分块)
-# ==========================================
class XB_WanImageToVideo:
@classmethod
def INPUT_TYPES(cls):
@@ -28,9 +20,9 @@ class XB_WanImageToVideo:
"vae": ("VAE",),
"width": ("INT", {"default": 480, "min": 16, "max": 8192, "step": 16}),
"height": ("INT", {"default": 832, "min": 16, "max": 8192, "step": 16}),
- "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}),
- "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
- "vae_tile_size": ("INT", {"default": 320, "min": 0, "max": 4096, "step": 16, "tooltip": "VAE编码分块大小,0表示不分块(默认0)"}),
+ "length": ("INT", {"default": 81, "min": 1, "max": 8192, "step": 1}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 8192, "step": 1}),
+ "vae_tile_size": ("INT", {"default": 64, "min": 64, "max": 3840, "step": 32}),
},
"optional": {
"clip_vision_output": ("CLIP_VISION_OUTPUT",),
@@ -41,35 +33,29 @@ class XB_WanImageToVideo:
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
RETURN_NAMES = ("positive", "negative", "latent")
FUNCTION = "process"
- CATEGORY = "小白工具箱/WAN解码分块"
+ CATEGORY = "XB_ToolBox/Pipeline"
- def process(self, positive, negative, vae, width, height, length, batch_size, vae_tile_size=0, start_image=None, clip_vision_output=None):
- latent = torch.zeros([batch_size, 16, ((length - 1) // 4) + 1, height // 8, width // 8], device=comfy.model_management.intermediate_device())
+ def process(self, positive, negative, vae, width, height, length, batch_size, vae_tile_size, clip_vision_output=None, start_image=None):
+ latent = torch.zeros([batch_size, 16, length, height // 8, width // 8], device=comfy.model_management.intermediate_device())
+
+ image = torch.zeros([batch_size, length, height, width, 3], device=vae.device)
+ mask = torch.zeros([batch_size, 1, length, height, width], device=vae.device)
if start_image is not None:
- start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
- image = torch.ones((length, height, width, start_image.shape[-1]), device=start_image.device, dtype=start_image.dtype) * 0.5
- image[:start_image.shape[0]] = start_image
+ image[:, 0:start_image.shape[0]] = start_image
+ mask[:, :, 0:start_image.shape[0]] = 0.0
- concat_latent_image = _encode_vae(vae, image, vae_tile_size)
+ concat_latent_image = _encode_vae(vae, image, vae_tile_size)
- mask = torch.ones((1, 1, latent.shape[2], concat_latent_image.shape[-2], concat_latent_image.shape[-1]), device=start_image.device, dtype=start_image.dtype)
- mask[:, :, :((start_image.shape[0] - 1) // 4) + 1] = 0.0
-
- positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
- negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
+ mask = mask.view(1, mask.shape[2] // 4, 4, mask.shape[3], mask.shape[4]).transpose(1, 2)
+ positive = node_helpers.conditioning_set_values(positive, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
+ negative = node_helpers.conditioning_set_values(negative, {"concat_latent_image": concat_latent_image, "concat_mask": mask})
if clip_vision_output is not None:
positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
- negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
- out_latent = {"samples": latent}
- return (positive, negative, out_latent)
+ return (positive, negative, {"samples": latent})
-
-# ==========================================
-# 节点 2:WAN 首尾帧视频 (带 VAE 分块)
-# ==========================================
class XB_WanFirstLastFrameToVideo:
@classmethod
def INPUT_TYPES(cls):
@@ -80,9 +66,9 @@ class XB_WanFirstLastFrameToVideo:
"vae": ("VAE",),
"width": ("INT", {"default": 480, "min": 16, "max": 8192, "step": 16}),
"height": ("INT", {"default": 832, "min": 16, "max": 8192, "step": 16}),
- "length": ("INT", {"default": 81, "min": 1, "max": 9999, "step": 4}),
- "batch_size": ("INT", {"default": 1, "min": 1, "max": 4096}),
- "vae_tile_size": ("INT", {"default": 320, "min": 0, "max": 4096, "step": 16}),
+ "length": ("INT", {"default": 81, "min": 1, "max": 8192, "step": 1}),
+ "batch_size": ("INT", {"default": 1, "min": 1, "max": 8192, "step": 1}),
+ "vae_tile_size": ("INT", {"default": 64, "min": 64, "max": 3840, "step": 32}),
},
"optional": {
"clip_vision_start_image": ("CLIP_VISION_OUTPUT",),
@@ -95,24 +81,18 @@ class XB_WanFirstLastFrameToVideo:
RETURN_TYPES = ("CONDITIONING", "CONDITIONING", "LATENT")
RETURN_NAMES = ("positive", "negative", "latent")
FUNCTION = "process"
- CATEGORY = "小白工具箱/WAN解码分块"
+ CATEGORY = "XB_ToolBox/Pipeline"
- def process(self, positive, negative, vae, width, height, length, batch_size, vae_tile_size=0, start_image=None, end_image=None, clip_vision_start_image=None, clip_vision_end_image=None):
- spacial_scale = vae.spacial_compression_encode()
- latent = torch.zeros([batch_size, vae.latent_channels, ((length - 1) // 4) + 1, height // spacial_scale, width // spacial_scale], device=comfy.model_management.intermediate_device())
+ def process(self, positive, negative, vae, width, height, length, batch_size, vae_tile_size, clip_vision_start_image=None, clip_vision_end_image=None, start_image=None, end_image=None):
+ latent = torch.zeros([batch_size, 16, length, height // 8, width // 8], device=comfy.model_management.intermediate_device())
+
+ image = torch.zeros([batch_size, length, height, width, 3], device=vae.device)
+ mask = torch.ones([batch_size, 1, length, height, width], device=vae.device)
if start_image is not None:
- start_image = comfy.utils.common_upscale(start_image[:length].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
- if end_image is not None:
- end_image = comfy.utils.common_upscale(end_image[-length:].movedim(-1, 1), width, height, "bilinear", "center").movedim(1, -1)
-
- image = torch.ones((length, height, width, 3)) * 0.5
- mask = torch.ones((1, 1, latent.shape[2] * 4, latent.shape[-2], latent.shape[-1]))
-
- if start_image is not None:
- image[:start_image.shape[0]] = start_image
- mask[:, :, :start_image.shape[0] + 3] = 0.0
-
+ image[:, 0:start_image.shape[0]] = start_image
+ mask[:, :, 0:start_image.shape[0]] = 0.0
+
if end_image is not None:
image[-end_image.shape[0]:] = end_image
mask[:, :, -end_image.shape[0]:] = 0.0
@@ -137,7 +117,5 @@ class XB_WanFirstLastFrameToVideo:
if clip_vision_output is not None:
positive = node_helpers.conditioning_set_values(positive, {"clip_vision_output": clip_vision_output})
- negative = node_helpers.conditioning_set_values(negative, {"clip_vision_output": clip_vision_output})
- out_latent = {"samples": latent}
- return (positive, negative, out_latent)
\ No newline at end of file
+ return (positive, negative, {"samples": latent})
\ No newline at end of file
diff --git a/nodes_wiring.py b/nodes_wiring.py
index 7839ea8..e73baf1 100644
--- a/nodes_wiring.py
+++ b/nodes_wiring.py
@@ -1,8 +1,5 @@
import folder_paths
-# ==========================================
-# 🎛️ 动态总线底层依赖 (我们的终极破壁武器 AnyType)
-# ==========================================
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
@@ -10,13 +7,7 @@ anyType = AnyType("*")
MAX_PORTS = 20
-# ==========================================
-# 节点 1:动态数据总线
-# ==========================================
class XB_DynamicBus:
- """
- 极客版动态数据总线 (N进N出)。
- """
@classmethod
def INPUT_TYPES(s):
inputs = {f"in_{i}": (anyType, ) for i in range(1, MAX_PORTS + 1)}
@@ -25,16 +16,12 @@ class XB_DynamicBus:
RETURN_TYPES = tuple([anyType] * MAX_PORTS)
RETURN_NAMES = tuple([f"out_{i}" for i in range(1, MAX_PORTS + 1)])
FUNCTION = "route"
- CATEGORY = "小白工具箱/布线整理"
+ CATEGORY = "XB_ToolBox/Wiring"
def route(self, **kwargs):
res = [kwargs.get(f"in_{i}") for i in range(1, MAX_PORTS + 1)]
return tuple(res)
-
-# ==========================================
-# 节点 2:UNet 名称分发器 (一键换挡中枢)
-# ==========================================
class XB_UNetNameBroadcaster:
@classmethod
def INPUT_TYPES(cls):
@@ -44,19 +31,14 @@ class XB_UNetNameBroadcaster:
}
}
- # 🟢 核心修复:把 "STRING" 改为 anyType,强行突破前端的类型隔离!
RETURN_TYPES = (anyType,)
- RETURN_NAMES = ("UNet名称",)
+ RETURN_NAMES = ("UNet_Name",)
FUNCTION = "broadcast"
- CATEGORY = "小白工具箱/布线整理"
+ CATEGORY = "XB_ToolBox/Wiring"
def broadcast(self, unet_name):
return (unet_name,)
-
-# ==========================================
-# 节点 3:CLIP 名称分发器 (一键换挡中枢)
-# ==========================================
class XB_CLIPNameBroadcaster:
@classmethod
def INPUT_TYPES(cls):
@@ -66,11 +48,10 @@ class XB_CLIPNameBroadcaster:
}
}
- # 🟢 核心修复:同样使用 anyType 强行突破!
RETURN_TYPES = (anyType,)
- RETURN_NAMES = ("CLIP名称",)
+ RETURN_NAMES = ("CLIP_Name",)
FUNCTION = "broadcast"
- CATEGORY = "小白工具箱/布线整理"
+ CATEGORY = "XB_ToolBox/Wiring"
def broadcast(self, clip_name):
return (clip_name,)
\ No newline at end of file