698 lines
29 KiB
JavaScript
698 lines
29 KiB
JavaScript
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);
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}); |