Skip to content

Commit 1617320

Browse files
Create error_fixer.js
1 parent d60db07 commit 1617320

File tree

1 file changed

+324
-0
lines changed

1 file changed

+324
-0
lines changed

web/error_fixer.js

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import { app } from "../../scripts/app.js";
2+
import { api } from "../../scripts/api.js";
3+
4+
let lastApiError = null;
5+
6+
function setupExecutionErrorListener() {
7+
api.addEventListener("execution_error", (e) => {
8+
const errorData = e.detail;
9+
console.log("🔧 [API] High-quality error event captured:", errorData);
10+
11+
lastApiError = {
12+
type: 'execution_error',
13+
nodeId: errorData.node_id,
14+
nodeType: errorData.node_type,
15+
message: errorData.exception_message,
16+
traceback: errorData.traceback,
17+
timestamp: new Date().toISOString(),
18+
captureTime: Date.now(),
19+
workflow: getWorkflowData()
20+
};
21+
22+
if (errorData.node_id) {
23+
setTimeout(() => addErrorMarkerToNode(errorData.node_id, lastApiError), 200);
24+
}
25+
});
26+
}
27+
28+
function observeErrorDialogs() {
29+
console.log("🔧 Starting to observe error dialogs...");
30+
31+
const observer = new MutationObserver((mutations) => {
32+
mutations.forEach((mutation) => {
33+
mutation.addedNodes.forEach((node) => {
34+
if (node.nodeType === Node.ELEMENT_NODE) {
35+
checkForErrorDialog(node);
36+
}
37+
});
38+
});
39+
});
40+
41+
observer.observe(document.body, {
42+
childList: true,
43+
subtree: true
44+
});
45+
46+
// 也检查已存在的元素
47+
setTimeout(() => {
48+
const existingDialogs = document.querySelectorAll('div, [class*="modal"], [class*="dialog"], [class*="popup"]');
49+
existingDialogs.forEach(checkForErrorDialog);
50+
}, 1000);
51+
}
52+
53+
function checkForErrorDialog(element) {
54+
try {
55+
// 避免在自己的按钮上触发
56+
if (element.closest('.error-fixer-button-container') || element.classList.contains('error-fixer-button')) {
57+
return;
58+
}
59+
60+
const textContent = element.textContent || element.innerText || '';
61+
62+
// 多种错误模式检测(参考工作版本)
63+
const errorPatterns = [
64+
/error/i,
65+
/exception/i,
66+
/failed/i,
67+
/object has no attribute/i,
68+
/nonetype/i,
69+
/traceback/i,
70+
/invalid/i,
71+
/cannot/i,
72+
/unable/i,
73+
//i,
74+
//i
75+
];
76+
77+
const hasErrorText = errorPatterns.some(pattern => pattern.test(textContent));
78+
79+
// 检查是否是弹窗样式(参考工作版本的判断逻辑)
80+
const style = window.getComputedStyle(element);
81+
const isModal = style.position === 'fixed' || style.position === 'absolute';
82+
const hasHighZIndex = parseInt(style.zIndex) > 100;
83+
84+
if (hasErrorText && (isModal || hasHighZIndex || element.offsetParent === document.body)) {
85+
console.log("🔧 Found potential error dialog:", element);
86+
handleErrorDialog(element, textContent);
87+
}
88+
89+
// 特别检查ComfyUI特定错误
90+
if (textContent.includes("'NoneType' object has no attribute 'shape'") ||
91+
textContent.includes("K采样器") ||
92+
textContent.includes("帮助修复这个")) {
93+
console.log("🔧 Found ComfyUI specific error dialog:", element);
94+
handleErrorDialog(element, textContent);
95+
}
96+
97+
} catch (e) {
98+
// 忽略检查错误
99+
}
100+
}
101+
102+
function handleErrorDialog(dialogElement, errorText) {
103+
try {
104+
console.log("🔧 Processing error dialog:", errorText.substring(0, 100));
105+
106+
// 防止重复处理
107+
if (dialogElement.querySelector('.error-fixer-button')) {
108+
return;
109+
}
110+
111+
let errorInfo;
112+
const now = Date.now();
113+
114+
if (lastApiError && (now - lastApiError.captureTime) < 3000) {
115+
console.log("🔧 Using high-quality data from recent API event.");
116+
errorInfo = lastApiError;
117+
} else {
118+
console.log("🔧 API data not found or too old. Falling back to parsing dialog text.");
119+
120+
// 提取错误信息(参考工作版本)
121+
let errorMessage = errorText.trim();
122+
let nodeName = '';
123+
let nodeType = '';
124+
let nodeId = '';
125+
126+
// 尝试从对话框标题中提取节点信息
127+
const titleElement = dialogElement.querySelector('h1, h2, h3, .title, [class*="title"]');
128+
if (titleElement) {
129+
const titleText = titleElement.textContent || titleElement.innerText;
130+
if (titleText && titleText !== errorMessage) {
131+
nodeName = titleText.trim();
132+
nodeType = titleText.trim();
133+
}
134+
}
135+
136+
// 从错误信息中提取节点ID
137+
const nodeIdMatch = errorMessage.match(/node[^\d]*(\d+)/i);
138+
if (nodeIdMatch) {
139+
nodeId = nodeIdMatch[1];
140+
}
141+
142+
errorInfo = {
143+
type: 'dialog_text_error',
144+
message: errorMessage,
145+
nodeName: nodeName,
146+
nodeType: nodeType,
147+
nodeId: nodeId,
148+
timestamp: new Date().toISOString()
149+
};
150+
}
151+
152+
// 添加按钮到对话框
153+
addFixButtonToDialog(dialogElement, errorInfo);
154+
155+
console.log("🔧 Error processed successfully");
156+
157+
} catch (e) {
158+
console.error("🔧 Error processing dialog:", e);
159+
}
160+
}
161+
162+
function addFixButtonToDialog(dialogElement, errorInfo) {
163+
try {
164+
const fixButton = createFixButton(errorInfo);
165+
166+
const insertionTargets = [
167+
dialogElement.querySelector('.buttons'),
168+
dialogElement.querySelector('.actions'),
169+
dialogElement.querySelector('.footer'),
170+
dialogElement.querySelector('button')?.parentNode,
171+
dialogElement.querySelector('.comfy-manager-dialog-actions'), // ComfyUI-Manager
172+
dialogElement.querySelector('.dialog_actions'),
173+
dialogElement.querySelector('.comfy-modal-actions'),
174+
dialogElement.querySelector('.dialog_content'),
175+
dialogElement.querySelector('.modal_content'),
176+
dialogElement // 最终备选
177+
];
178+
179+
let buttonAdded = false;
180+
for (const target of insertionTargets) {
181+
if (target) {
182+
console.log("🔧 Adding button to dialog target:", target.className || target.nodeName);
183+
target.appendChild(fixButton);
184+
buttonAdded = true;
185+
break;
186+
}
187+
}
188+
189+
if (!buttonAdded) {
190+
console.log("🔧 Creating new button container");
191+
const buttonContainer = document.createElement('div');
192+
buttonContainer.className = 'error-fixer-button-container';
193+
buttonContainer.style.cssText = `
194+
text-align: center !important;
195+
padding: 10px !important;
196+
border-top: 1px solid #333 !important;
197+
margin-top: 10px !important;
198+
`;
199+
buttonContainer.appendChild(fixButton);
200+
dialogElement.appendChild(buttonContainer);
201+
}
202+
203+
} catch (e) {
204+
console.error("🔧 Error adding button to dialog:", e);
205+
}
206+
}
207+
208+
function createFixButton(errorInfo) {
209+
const button = document.createElement('button');
210+
button.className = 'error-fixer-button';
211+
button.innerHTML = '🔧 Error Fixer Online';
212+
213+
// 使用工作版本的样式
214+
button.style.cssText = `
215+
background: #ff6b35 !important;
216+
color: white !important;
217+
border: none !important;
218+
padding: 8px 16px !important;
219+
border-radius: 6px !important;
220+
cursor: pointer !important;
221+
margin: 10px 5px !important;
222+
font-size: 14px !important;
223+
font-weight: bold !important;
224+
transition: all 0.3s ease !important;
225+
box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
226+
z-index: 10000 !important;
227+
display: inline-block !important;
228+
`;
229+
230+
button.addEventListener('mouseenter', () => {
231+
button.style.background = '#e55a2b';
232+
button.style.transform = 'translateY(-1px)';
233+
});
234+
235+
button.addEventListener('mouseleave', () => {
236+
button.style.background = '#ff6b35';
237+
button.style.transform = 'translateY(0)';
238+
});
239+
240+
button.addEventListener('click', (e) => {
241+
e.preventDefault();
242+
e.stopPropagation();
243+
console.log("🔧 Fix button clicked for error:", errorInfo.message);
244+
openErrorFixPage(errorInfo);
245+
});
246+
247+
return button;
248+
}
249+
250+
function openErrorFixPage(errorInfo) {
251+
const baseUrl = "https://bug.aix.ink";
252+
const params = new URLSearchParams({
253+
q: errorInfo.message,
254+
source: 'comfyui_plugin_final'
255+
});
256+
window.open(`${baseUrl}?${params.toString()}`, '_blank');
257+
}
258+
259+
function getWorkflowData() {
260+
try {
261+
return app.graph ? JSON.stringify(app.graph.serialize()) : "{}";
262+
} catch (e) {
263+
return `{"error": "Unable to capture workflow: ${e.message}"}`;
264+
}
265+
}
266+
267+
function addErrorMarkerToNode(nodeId, errorInfo) {
268+
const targetNode = app.graph?.getNodeById(nodeId);
269+
if (!targetNode) return;
270+
271+
if (targetNode.hasErrorMarker) {
272+
targetNode.errorInfo = errorInfo;
273+
return;
274+
}
275+
targetNode.errorInfo = errorInfo;
276+
const originalOnDrawForeground = targetNode.onDrawForeground;
277+
targetNode.onDrawForeground = function(ctx) {
278+
if (originalOnDrawForeground) originalOnDrawForeground.apply(this, arguments);
279+
if (typeof LiteGraph === "undefined") return;
280+
const iconSize = 22, margin = 4, titleHeight = LiteGraph.NODE_TITLE_HEIGHT || 30;
281+
const existingIconsWidth = 38;
282+
const iconX = this.size[0] - iconSize - margin - existingIconsWidth;
283+
const iconY = -titleHeight + (titleHeight - iconSize) / 2;
284+
ctx.save();
285+
ctx.fillStyle = "#E53E3E";
286+
ctx.beginPath();
287+
ctx.arc(iconX + iconSize / 2, iconY + iconSize / 2, iconSize / 2, 0, 2 * Math.PI);
288+
ctx.fill();
289+
ctx.strokeStyle = "white";
290+
ctx.lineWidth = 1.5;
291+
ctx.stroke();
292+
ctx.font = `bold ${iconSize * 0.65}px Arial`;
293+
ctx.fillStyle = "white";
294+
ctx.textAlign = "center";
295+
ctx.textBaseline = "middle";
296+
ctx.fillText("🔧", iconX + iconSize / 2, iconY + iconSize / 2 + 1);
297+
ctx.restore();
298+
this.errorIconBounds = { x: iconX, y: iconY, width: iconSize, height: iconSize };
299+
};
300+
const originalOnMouseDown = targetNode.onMouseDown;
301+
targetNode.onMouseDown = function(e, localPos) {
302+
if (this.errorIconBounds && typeof LiteGraph !== "undefined" && LiteGraph.isInsideRectangle(localPos[0], localPos[1], this.errorIconBounds.x, this.errorIconBounds.y, this.errorIconBounds.width, this.errorIconBounds.height)) {
303+
openErrorFixPage(this.errorInfo);
304+
return true;
305+
}
306+
return originalOnMouseDown ? originalOnMouseDown.apply(this, arguments) : false;
307+
};
308+
targetNode.hasErrorMarker = true;
309+
app.canvas?.setDirty(true, true);
310+
}
311+
312+
// 插件注册
313+
app.registerExtension({
314+
name: "ComfyUI.ErrorFixer.Final",
315+
async setup() {
316+
console.log("🔧 Error Fixer Plugin [Final Version] Loaded. Using robust DOM observation.");
317+
318+
setTimeout(() => {
319+
observeErrorDialogs();
320+
setupExecutionErrorListener();
321+
console.log("🔧 Error monitoring started.");
322+
}, 2000);
323+
}
324+
});

0 commit comments

Comments
 (0)