Files

698 lines
29 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
// 用於存儲節點 ID 映射
const nodeIdMap = new Map();
// 註冊自定義事件處理器
api.addEventListener("textbatch-node-feedback", (event) => {
console.log("Received node feedback:", event);
try {
// 從 CustomEvent 中獲取 data
const data = event.detail;
// 檢查 data 物件的完整性
if (!data || !data.node_id) {
console.error("Invalid data received:", data);
return;
}
const nodeId = data.node_id;
console.log("Looking for node:", nodeId, "Data received:", data);
// 嘗試從 nodeIdMap 中獲取節點
let node = nodeIdMap.get(nodeId);
// 如果在 Map 中找不到,再嘗試其他方法
if (!node) {
node = app.graph._nodes_by_id?.[nodeId] ||
app.graph.getNodeById?.(parseInt(nodeId)) ||
[...(app.graph?.nodes || [])].find(n => n?.id == nodeId);
}
if (!node) {
console.warn("Node not found by ID:", nodeId, "Available nodes:",
Array.from(nodeIdMap.keys()));
return;
}
console.log("Found node:", node);
const widget = node.widgets?.find(w => w.name === data.widget_name);
if (!widget) {
console.warn("Widget not found:", data.widget_name);
return;
}
if (data.type === "int") {
console.log("Updating widget value:", data.value);
widget.value = parseInt(data.value);
} else {
widget.value = data.value;
}
// 觸發小部件的變更事件
if (widget.callback) {
widget.callback(widget.value);
}
} catch (error) {
console.error("Error in node feedback handler:", error);
}
});
// 註冊佇列事件處理器
api.addEventListener("textbatch-add-queue", (data) => {
try {
console.log("Received queue event:", data);
// 檢查是否正在處理中
if (app.isProcessing) {
console.log("Already processing, queueing next prompt");
}
// 獲取當前工作流程
const workflow = app.graph?.serialize?.(); // ✅ 安全访问
console.log("Current workflow:", workflow);
// 確保在下一個事件循環中執行
setTimeout(() => {
try {
console.log("Executing queued prompt");
// 使用 queuePrompt 的完整參數
app.queuePrompt?.(0, 1); // ✅ 兼容性检查
console.log("Queue prompt executed");
} catch (queueError) {
console.error("Error queueing prompt:", queueError);
}
}, 100);
} catch (error) {
console.error("Error in textbatch-add-queue handler:", error);
console.error("Error details:", {
message: error.message,
stack: error.stack
});
}
});
// 為特定節點添加自定義行為
app.registerExtension({
name: "TextBatch.TextBatchNode",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
//console.log("Registering node type:", nodeData.name);
if (nodeData.name === "TextBatch" ||
nodeData.name === "TextQueueProcessor" ||
nodeData.name === "ZippedPromptBatch" ||
nodeData.name === "ZippedPromptBatchAdvanced") {
console.log("Adding custom behavior to node:", nodeData.name);
// 添加自定義小部件行為
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const r = onNodeCreated?.apply?.(this, arguments); // ✅ 安全访问
// 確保節點有有效的 ID
if (!this?.id || this.id === -1) { // ✅ 可选链检查
console.warn("Invalid node ID detected, waiting for proper initialization");
// 等待下一個事件循環再進行初始化
setTimeout(() => {
console.log("Retrying node initialization:", nodeData.name, "ID:", this.id);
// 存儲節點 ID
if (this?.id && this.id !== -1) { // ✅ 双重检查
nodeIdMap.set(this.id, this);
// 為節點添加自定義標題
this.addWidget?.("text", "status", "", (v) => { // ✅ 兼容性检查
console.log("Status widget updated:", v);
this.status = v;
});
}
}, 0);
} else {
console.log("Node created:", nodeData.name, "ID:", this.id);
nodeIdMap.set(this.id, this);
this.addWidget?.("text", "status", "", (v) => { // ✅ 兼容性检查
console.log("Status widget updated:", v);
this.status = v;
});
}
return r;
};
// 添加節點刪除處理
const onNodeRemoved = nodeType.prototype.onRemoved;
nodeType.prototype.onRemoved = function() {
console.log("Node removed:", this.id);
if (this?.id) { // ✅ 关键修复:添加存在性检查
console.log("Node removed:", this.id);
nodeIdMap.delete(this.id);
}
onNodeRemoved?.apply?.(this, arguments); // ✅ 安全调用
};
}
}
});
// api.addEventListener('executed', async ({ detail }) => {
// console.log('#executed', detail)
// console.log(output)
// })
// TextQueueProcessor 節點擴展
class TextQueueProcessorNode {
constructor() {
if (!this.properties) {
this.properties = {};
}
this.addCustomWidgets?.(); // ✅ 兼容性检查
}
addCustomWidgets() {
// 添加重置按鈕
this.addWidget?.("button", "🔄 Reset", null, () => { // ✅ 安全访问
this.triggerReset?.(); // ✅ 兼容性检查
});
// 添加跳到開頭按鈕
this.addWidget("button", "⏮️ To Start", null, () => {
// 將 start_index 設為 0
this.widgets.find(w => w.name === "start_index").value = 0;
});
// 添加跳到結尾按鈕
this.addWidget("button", "⏭️ To End", null, () => {
// 獲取 total 值(如果有的話)
const total = this.outputs?.[2]?.value ?? 0;
if (total > 0) {
this.widgets.find(w => w.name === "start_index").value = total - 1;
}
});
}
triggerReset() {
// 發送重置事件到後端
const nodeId = this?.id; // ✅ 安全访问
app.graphToPrompt?.().then(workflow => { // ✅ 兼容性检查
if (workflow.output) {
app.queuePrompt(workflow.output, workflow.workflow);
}
});
}
}
// ImageQueueProcessor 節點擴展
class ImageQueueProcessorNode {
constructor() {
if (!this.properties) {
this.properties = {};
}
this.addCustomWidgets();
}
addCustomWidgets() {
// 添加重置按鈕
this.addWidget("button", "🔄 Reset", null, () => {
// 觸發重置
this.triggerReset();
});
// 添加跳到開頭按鈕
this.addWidget("button", "⏮️ To Start", null, () => {
// 將 start_index 設為 0
this.widgets.find(w => w.name === "start_index").value = 0;
});
// 添加跳到結尾按鈕
this.addWidget("button", "⏭️ To End", null, () => {
// 獲取 total 值(如果有的話)
const total = this.outputs?.[3]?.value ?? 0;
if (total > 0) {
this.widgets.find(w => w.name === "start_index").value = total - 1;
}
});
}
triggerReset() {
// 發送重置事件到後端
const nodeId = this.id;
app.graphToPrompt().then(workflow => {
if (workflow.output) {
app.queuePrompt(workflow.output, workflow.workflow);
}
});
}
}
// 註冊節點擴展
app.registerExtension({
name: "rgthree.TextBatch",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === "TextQueueProcessor") {
Object.assign(nodeType.prototype, TextQueueProcessorNode.prototype);
}
else if (nodeData.name === "ImageQueueProcessor") {
Object.assign(nodeType.prototype, ImageQueueProcessorNode.prototype);
}
}
});
// ============ 群組控制器節點 ============
// 監聽後端的群組狀態更新事件
api.addEventListener("groupcontroller-update", (event) => {
try {
const data = event.detail;
if (!data || !data.node_id) return;
const node = app.graph.getNodeById(parseInt(data.node_id));
if (!node) return;
// 應用群組狀態
if (node.applyGroupStates) {
node.applyGroupStates(data.group_states, data.control_mode);
}
} catch (error) {
console.error("Error in groupcontroller-update handler:", error);
}
});
// 註冊群組控制器節點
app.registerExtension({
name: "TextBatch.GroupController",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === "GroupController") {
console.log("Registering GroupController node");
// 修改 INPUT_TYPES 來動態添加群組 inputs
const originalGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function(_, options) {
if (originalGetExtraMenuOptions) {
originalGetExtraMenuOptions.apply(this, arguments);
}
options.unshift(
{
content: "🔄 重新掃描群組",
callback: () => {
this.refreshGroupInputs();
}
},
null
);
};
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const r = onNodeCreated?.apply(this, arguments);
// 初始化屬性
this.groupsData = [];
this.lastGroupSignature = "";
this.lastInputValues = {}; // 存儲上次的 input 值
// 延遲載入群組以確保 graph 已初始化
setTimeout(() => {
this.refreshGroupInputs();
}, 300);
// 啟動輪詢機制,每 100ms 檢查一次輸入值變化
this.updateInterval = setInterval(() => {
this.checkAndApplyInputChanges();
}, 100);
console.log(`GroupController ${this.id} created with polling enabled`);
return r;
};
// 獲取所有群組
nodeType.prototype.getAllGroups = function() {
if (!app.canvas || !app.canvas.graph) {
return [];
}
const groups = [];
// 從 _groups 獲取
if (app.canvas.graph._groups && Array.isArray(app.canvas.graph._groups)) {
groups.push(...app.canvas.graph._groups);
}
return groups;
};
// 獲取群組中的所有節點
nodeType.prototype.getNodesInGroup = function(group) {
if (!app.canvas || !app.canvas.graph || !app.canvas.graph._nodes) {
return [];
}
const nodes = [];
for (const node of app.canvas.graph._nodes) {
if (this.isNodeInGroup(node, group)) {
nodes.push(node);
}
}
return nodes;
};
// 判斷節點是否在群組內
nodeType.prototype.isNodeInGroup = function(node, group) {
if (!node || !group) return false;
const nodeX = node.pos[0];
const nodeY = node.pos[1];
const nodeWidth = node.size[0];
const nodeHeight = node.size[1];
const groupX = group._pos[0];
const groupY = group._pos[1];
const groupWidth = group._size[0];
const groupHeight = group._size[1];
const nodeCenterX = nodeX + nodeWidth / 2;
const nodeCenterY = nodeY + nodeHeight / 2;
return nodeCenterX >= groupX && nodeCenterX <= groupX + groupWidth &&
nodeCenterY >= groupY && nodeCenterY <= groupY + groupHeight;
};
// 刷新群組 inputs
nodeType.prototype.refreshGroupInputs = function() {
console.log("Refreshing group inputs...");
const groups = this.getAllGroups();
console.log("Found groups:", groups.length);
if (groups.length === 0) {
console.warn("No groups found");
return;
}
// 生成群組簽名以檢測變化
const groupSignature = groups.map(g => `${g.title}_${g._pos.join(',')}_${g._size.join(',')}`).join('|');
// 如果群組沒有變化,不需要重建
if (this.lastGroupSignature === groupSignature) {
console.log("Groups unchanged, skipping rebuild");
return;
}
this.lastGroupSignature = groupSignature;
this.groupsData = groups;
// 移除舊的群組 inputs 和 widgets
const inputsToRemove = [];
if (this.inputs) {
for (let i = 0; i < this.inputs.length; i++) {
const input = this.inputs[i];
if (input.name.startsWith("enable_group_")) {
inputsToRemove.push(i);
}
}
}
// 從後往前刪除以避免索引問題
for (let i = inputsToRemove.length - 1; i >= 0; i--) {
this.removeInput(inputsToRemove[i]);
}
// 移除舊的群組 widgets(保留 control_mode)
if (this.widgets) {
const widgetsToKeep = this.widgets.filter(w =>
w.name === "control_mode" || !w.name.startsWith("enable_group_")
);
this.widgets = widgetsToKeep;
}
// 為每個群組添加 BOOLEAN input 和對應的 widget
for (const group of groups) {
const groupId = this.sanitizeGroupId(group.title || group.id);
const inputName = `enable_group_${groupId}`;
const displayName = `Enable ${group.title || groupId}`;
// 添加 input(連接點)
this.addInput(inputName, "BOOLEAN", {
label: displayName
});
// 添加對應的 toggle widget
const widget = this.addWidget(
"toggle",
inputName,
true,
(value) => {
console.log(`Widget ${inputName} changed to:`, value);
// widget 變更時立即應用狀態
const modeWidget = this.widgets?.find(w => w.name === "control_mode");
const currentMode = modeWidget ? modeWidget.value : "bypass";
const groupStates = this.getGroupStates();
this.applyGroupStates(groupStates, currentMode);
},
{ on: "yes", off: "no" }
);
// 將 widget 與 input 關聯
widget.linkedInput = inputName;
console.log(`Added input and widget: ${inputName}`);
}
// 更新節點大小
this.setSize(this.computeSize());
console.log(`Loaded ${groups.length} group inputs`);
};
// 清理群組 ID(移除特殊字符)
nodeType.prototype.sanitizeGroupId = function(id) {
return String(id).replace(/[^a-zA-Z0-9_]/g, '_');
};
// 應用群組狀態
nodeType.prototype.applyGroupStates = function(groupStates, controlMode) {
console.log("Applying group states:", groupStates, "Mode:", controlMode);
for (const group of this.groupsData) {
const groupId = this.sanitizeGroupId(group.title || group.id);
const enabled = groupStates[groupId] !== undefined ? groupStates[groupId] : true;
this.setGroupState(group, enabled, controlMode);
}
};
// 設置群組狀態
nodeType.prototype.setGroupState = function(group, enabled, mode) {
const nodes = this.getNodesInGroup(group);
console.log(`Setting group "${group.title}" to ${enabled ? 'enabled' : 'disabled'} (mode: ${mode}), nodes: ${nodes.length}`);
for (const node of nodes) {
if (mode === "bypass") {
node.mode = enabled ? 0 : 4; // 0 = ALWAYS, 4 = BYPASS
} else if (mode === "mute") {
node.mode = enabled ? 0 : 2; // 0 = ALWAYS, 2 = NEVER (muted)
}
}
// 重繪畫布
if (app.canvas) {
app.canvas.setDirty(true, true);
}
};
// 檢查並應用輸入值變化
nodeType.prototype.checkAndApplyInputChanges = function() {
if (!this.inputs || !this.groupsData || this.groupsData.length === 0) {
return;
}
let hasChanged = false;
const currentValues = {};
// 獲取當前所有輸入值
for (const input of this.inputs) {
if (!input.name.startsWith("enable_group_")) continue;
const groupId = input.name.replace("enable_group_", "");
let currentValue = true; // 預設值
// 檢查是否有連接
const link = input.link;
if (link !== null && link !== undefined) {
const linkInfo = app.graph.links[link];
if (linkInfo) {
const originNode = app.graph.getNodeById(linkInfo.origin_id);
if (originNode) {
const outputIndex = linkInfo.origin_slot;
// 嘗試從 widget 獲取值
if (originNode.widgets && originNode.widgets.length > 0) {
const widget = originNode.widgets.find(w =>
w.name === "value" || w.name === "boolean_value" ||
w.name === "BOOLEAN" || w.type === "toggle"
) || originNode.widgets[0];
if (widget && widget.value !== undefined) {
currentValue = widget.value;
}
}
}
}
} else {
// 沒有連接,使用本地 widget 的值
const widget = this.widgets?.find(w => w.name === input.name);
if (widget) {
currentValue = widget.value !== false;
}
}
currentValues[groupId] = currentValue;
// 檢查是否變化
if (this.lastInputValues[groupId] !== currentValue) {
hasChanged = true;
console.log(`GroupController ${this.id}: ${input.name} changed from ${this.lastInputValues[groupId]} to ${currentValue}`);
}
}
// 如果有變化,應用新狀態
if (hasChanged) {
this.lastInputValues = currentValues;
const modeWidget = this.widgets?.find(w => w.name === "control_mode");
const currentMode = modeWidget ? modeWidget.value : "bypass";
this.applyGroupStates(currentValues, currentMode);
}
};
// 獲取群組狀態(從 input 連接或 widget)
nodeType.prototype.getGroupStates = function() {
const groupStates = {};
if (!this.inputs) return groupStates;
for (const input of this.inputs) {
if (!input.name.startsWith("enable_group_")) continue;
const groupId = input.name.replace("enable_group_", "");
// 檢查是否有連接
const link = input.link;
if (link !== null && link !== undefined) {
// 有連接,嘗試從連接獲取最新值
const linkInfo = app.graph.links[link];
if (linkInfo) {
const originNode = app.graph.getNodeById(linkInfo.origin_id);
if (originNode) {
const outputIndex = linkInfo.origin_slot;
// 先嘗試從 outputs[].value 獲取
if (originNode.outputs && originNode.outputs[outputIndex]) {
const output = originNode.outputs[outputIndex];
if (output.value !== undefined) {
groupStates[groupId] = output.value;
continue;
}
}
// 嘗試從 widget 獲取值(對於 bool 節點)
if (originNode.widgets && originNode.widgets.length > 0) {
// 查找 BOOLEAN 類型的 widget
const boolWidget = originNode.widgets.find(w =>
w.name === "value" || w.name === "boolean_value" || w.type === "toggle"
);
if (boolWidget && boolWidget.value !== undefined) {
groupStates[groupId] = boolWidget.value;
continue;
}
// 如果找不到特定的,就使用第一個 widget 的值
if (originNode.widgets[0].value !== undefined) {
groupStates[groupId] = originNode.widgets[0].value;
continue;
}
}
}
}
}
// 沒有連接或無法從連接獲取值,使用 widget 的值
const widget = this.widgets?.find(w => w.name === input.name);
if (widget) {
groupStates[groupId] = widget.value !== false;
} else {
groupStates[groupId] = true; // 預設啟用
}
}
return groupStates;
};
// 監聽執行前事件
const onExecute = nodeType.prototype.onExecute;
nodeType.prototype.onExecute = function() {
// 獲取當前模式
const modeWidget = this.widgets?.find(w => w.name === "control_mode");
const currentMode = modeWidget ? modeWidget.value : "bypass";
// 獲取所有群組狀態
const groupStates = this.getGroupStates();
// 應用狀態到群組
this.applyGroupStates(groupStates, currentMode);
if (onExecute) {
return onExecute.apply(this, arguments);
}
};
// 監聽 widget 變更
const onWidgetChanged = nodeType.prototype.onWidgetChanged;
nodeType.prototype.onWidgetChanged = function(name, value, oldValue, widget) {
if (onWidgetChanged) {
onWidgetChanged.apply(this, arguments);
}
// 如果是群組 widget 或 control_mode 變更,立即應用
if (name.startsWith("enable_group_") || name === "control_mode") {
const modeWidget = this.widgets?.find(w => w.name === "control_mode");
const currentMode = modeWidget ? modeWidget.value : "bypass";
const groupStates = this.getGroupStates();
this.applyGroupStates(groupStates, currentMode);
}
};
// 監聽連接變更
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info) {
if (onConnectionsChange) {
onConnectionsChange.apply(this, arguments);
}
// 當連接變更時,重新應用狀態
if (type === 1) { // 1 = input
console.log(`GroupController ${this.id}: connection changed`);
setTimeout(() => {
const modeWidget = this.widgets?.find(w => w.name === "control_mode");
const currentMode = modeWidget ? modeWidget.value : "bypass";
const groupStates = this.getGroupStates();
this.applyGroupStates(groupStates, currentMode);
}, 100);
}
};
// 清理定時器
const onRemoved = nodeType.prototype.onRemoved;
nodeType.prototype.onRemoved = function() {
console.log(`GroupController ${this.id} removed, stopping polling`);
if (this.updateInterval) {
clearInterval(this.updateInterval);
this.updateInterval = null;
}
if (onRemoved) {
return onRemoved.apply(this, arguments);
}
};
}
}
});