import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js"; app.registerExtension({ name: "SigmasEditor.Interactive", async beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData.name === "SigmasEditor") { // 扩展节点的构造函数 const onNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function() { const result = onNodeCreated?.apply(this, arguments); // 初始化节点数据 this.sigmas_data = null; this.adjustments = []; // 存储实际调整后的sigma值 this.dragging_point = -1; this.isAdjusting = false; // 允许调整节点大小 this.resizable = true; // 设置初始大小(宽度自动,高度400) this.size = [this.size[0] || 600, 400]; // 设置WebSocket监听 this.setupWebSocket(); return result; }; // 添加WebSocket设置方法 nodeType.prototype.setupWebSocket = function() { const messageHandler = (event) => { const data = event.detail; if (!data || !data.node_id || !data.sigmas_data) { return; } // 通过node_id查找对应的节点 const targetNode = app.graph.getNodeById(parseInt(data.node_id)); // 检查是否是当前节点 if (targetNode && targetNode === this) { this.sigmas_data = data.sigmas_data.original; // 从后端接收调整后的值,如果为空则使用原始值 this.adjustments = data.sigmas_data.adjusted || data.sigmas_data.original.slice(); // 第一次执行时,触发 sigmas_adjustments 组件更新 this.updateAdjustmentsWidget(); // 立即更新画布(强制重新计算尺寸) if (this.canvas) { this.updateCanvas(true); } else { // 如果画布还没创建,等待一下再尝试 setTimeout(() => { if (this.canvas) { this.updateCanvas(true); } }, 100); } this.setDirtyCanvas(true, true); } }; api.addEventListener("sigmas_editor_update", messageHandler); // 存储handler引用以便后续清理 this._sigmasEditorMessageHandler = messageHandler; }; // 添加节点时的处理 const onAdded = nodeType.prototype.onAdded; nodeType.prototype.onAdded = function() { const result = onAdded?.apply(this, arguments); if (!this.canvasContainer && this.id !== undefined && this.id !== -1) { // 创建画布容器(高度由节点自适应) const container = document.createElement("div"); container.style.position = "relative"; container.style.width = "100%"; container.style.height = "100%"; container.style.minHeight = "300px"; container.style.backgroundColor = "#1e1e1e"; container.style.borderRadius = "8px"; container.style.overflow = "hidden"; // 创建画布 const canvas = document.createElement("canvas"); canvas.style.width = "100%"; canvas.style.height = "100%"; canvas.style.cursor = "crosshair"; container.appendChild(canvas); this.canvas = canvas; this.canvasContainer = container; // 添加鼠标事件监听 this.addCanvasEventListeners(); // 添加DOM组件 this.widgets ||= []; this.widgets_up = true; requestAnimationFrame(() => { if (this.widgets) { this.canvasWidget = this.addDOMWidget("sigmas_canvas", "canvas", container); // 初始化时强制计算画布尺寸 this.updateCanvas(true); this.setDirtyCanvas(true, true); } }); } return result; }; // 添加画布事件监听器 nodeType.prototype.addCanvasEventListeners = function() { const canvas = this.canvas; // 获取缩放后的鼠标坐标 const getScaledMousePos = (e) => { const rect = canvas.getBoundingClientRect(); // 计算缩放比例 const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; // 鼠标相对于canvas显示区域的位置 const displayX = e.clientX - rect.left; const displayY = e.clientY - rect.top; // 转换为实际canvas坐标 const canvasX = displayX * scaleX; const canvasY = displayY * scaleY; return { x: canvasX, y: canvasY }; }; // 鼠标按下 canvas.addEventListener("mousedown", (e) => { const pos = getScaledMousePos(e); const pointIdx = this.findNearestPoint(pos.x, pos.y); if (pointIdx !== -1) { this.dragging_point = pointIdx; this.isAdjusting = true; this.updateCanvas(); // 只重绘,不resize } }); // 鼠标移动 canvas.addEventListener("mousemove", (e) => { if (this.dragging_point !== -1 && this.isAdjusting) { const pos = getScaledMousePos(e); this.updatePointAdjustment(this.dragging_point, pos.y); this.updateAdjustmentsWidget(); // 实时同步更新组件值 this.updateCanvas(); // 只重绘,不resize } }); // 鼠标释放 canvas.addEventListener("mouseup", (e) => { if (this.dragging_point !== -1) { this.dragging_point = -1; this.isAdjusting = false; // 更新adjustments widget this.updateAdjustmentsWidget(); this.updateCanvas(); // 只重绘,不resize } }); // 鼠标离开 canvas.addEventListener("mouseleave", (e) => { if (this.dragging_point !== -1) { this.dragging_point = -1; this.isAdjusting = false; this.updateAdjustmentsWidget(); this.updateCanvas(); // 只重绘,不resize } }); }; // 查找最近的点 nodeType.prototype.findNearestPoint = function(mouseX, mouseY) { if (!this.sigmas_data || this.sigmas_data.length === 0) return -1; const paddingLeft = 60; const paddingTop = 50; const paddingRight = 20; const paddingBottom = 50; const canvas = this.canvas; const chartWidth = canvas.width - paddingLeft - paddingRight; const chartHeight = canvas.height - paddingTop - paddingBottom; const chartX = paddingLeft; const chartY = paddingTop; const steps = this.sigmas_data.length; let closestDist = Infinity; let closestIdx = -1; for (let i = 0; i < steps; i++) { const x = chartX + (chartWidth / (steps - 1)) * i; // adjustments存储的是实际调整后的sigma值 const adjustedValue = this.adjustments[i] !== undefined ? this.adjustments[i] : this.sigmas_data[i]; const y = chartY + chartHeight - (adjustedValue * chartHeight); const dist = Math.sqrt(Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2)); if (dist < 15 && dist < closestDist) { closestDist = dist; closestIdx = i; } } return closestIdx; }; // 更新点的调整值 nodeType.prototype.updatePointAdjustment = function(pointIdx, mouseY) { if (!this.sigmas_data || pointIdx < 0 || pointIdx >= this.sigmas_data.length) return; const paddingTop = 50; const paddingBottom = 50; const canvas = this.canvas; const chartHeight = canvas.height - paddingTop - paddingBottom; const chartY = paddingTop; // 计算新的sigma值(范围0-1) const clampedY = Math.max(chartY, Math.min(chartY + chartHeight, mouseY)); const newSigmaValue = (chartY + chartHeight - clampedY) / chartHeight; // 直接存储调整后的sigma值,限制在0-1范围内 this.adjustments[pointIdx] = Math.max(0.0, Math.min(1.0, newSigmaValue)); }; // 向上取整到指定小数位 nodeType.prototype.ceilToFixed = function(value, decimals) { const multiplier = Math.pow(10, decimals); return Math.ceil(value * multiplier) / multiplier; }; // 更新adjustments widget(向上取整到4位小数) nodeType.prototype.updateAdjustmentsWidget = function() { const widget = this.widgets?.find(w => w.name === "sigmas_adjustments"); if (widget) { // 对所有调整后的值进行向上取整到4位小数,并格式化为字符串保留末尾的0 const formattedValues = this.adjustments.map(v => { const rounded = this.ceilToFixed(v, 4); return rounded.toFixed(4); }); // 手动构建 JSON 数组字符串,保留4位小数格式 widget.value = '[' + formattedValues.join(', ') + ']'; } }; // 更新画布 nodeType.prototype.updateCanvas = function(forceResize = false) { if (!this.canvas) return; requestAnimationFrame(() => { const canvas = this.canvas; const ctx = canvas.getContext("2d"); // 只在必要时重新设置画布尺寸 if (forceResize || !this._canvasInitialized) { const rect = canvas.getBoundingClientRect(); // 确保画布有合理的尺寸 const width = rect.width > 0 ? rect.width : 600; const height = rect.height > 0 ? rect.height : 300; // 只有尺寸真正改变时才重新设置 if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; } this._canvasInitialized = true; } // 增加padding以容纳坐标轴标签 const paddingLeft = 60; const paddingRight = 20; const paddingTop = 50; const paddingBottom = 50; const chartWidth = canvas.width - paddingLeft - paddingRight; const chartHeight = canvas.height - paddingTop - paddingBottom; const chartX = paddingLeft; const chartY = paddingTop; // 清空画布 ctx.fillStyle = "#1e1e1e"; ctx.fillRect(0, 0, canvas.width, canvas.height); // 如果没有数据,显示提示 if (!this.sigmas_data || this.sigmas_data.length === 0) { ctx.fillStyle = "#999"; ctx.font = "14px Arial"; ctx.textAlign = "center"; ctx.fillText("Connect Sigmas Input & Execute Workflow", canvas.width / 2, canvas.height / 2); return; } // 绘制图表区域 ctx.fillStyle = "#2a2a2a"; ctx.fillRect(chartX, chartY, chartWidth, chartHeight); // 绘制网格 ctx.strokeStyle = "#444"; ctx.lineWidth = 1; for (let i = 0; i <= 10; i++) { const y = chartY + (chartHeight / 10) * i; ctx.beginPath(); ctx.moveTo(chartX, y); ctx.lineTo(chartX + chartWidth, y); ctx.stroke(); } const steps = this.sigmas_data.length; // 确保adjustments数组长度正确,初始化为原始sigma值 if (this.adjustments.length !== steps) { this.adjustments = this.sigmas_data.slice(); } // 绘制Y轴刻度和标签(Sigma值 0-1,刻度0.1) ctx.fillStyle = "#999"; ctx.font = "10px Arial"; ctx.textAlign = "right"; for (let i = 0; i <= 10; i++) { const value = 1.0 - (i / 10); const y = chartY + (chartHeight / 10) * i; ctx.fillText(value.toFixed(1), chartX - 5, y + 3); } // Y轴标签 ctx.save(); ctx.translate(15, chartY + chartHeight / 2); ctx.rotate(-Math.PI / 2); ctx.textAlign = "center"; ctx.font = "12px Arial"; ctx.fillStyle = "#ccc"; ctx.fillText("Sigma Value", 0, 0); ctx.restore(); // 绘制X轴刻度和标签 ctx.fillStyle = "#999"; ctx.font = "10px Arial"; ctx.textAlign = "center"; // 根据步数决定显示哪些刻度 let stepInterval = 1; if (steps > 30) stepInterval = 2; if (steps > 50) stepInterval = 5; if (steps > 100) stepInterval = 10; for (let i = 0; i < steps; i += stepInterval) { const x = chartX + (chartWidth / (steps - 1)) * i; ctx.fillText(i.toString(), x, chartY + chartHeight + 15); } // 确保显示最后一步 if ((steps - 1) % stepInterval !== 0) { const x = chartX + chartWidth; ctx.fillText((steps - 1).toString(), x, chartY + chartHeight + 15); } // X轴标签 ctx.font = "12px Arial"; ctx.fillStyle = "#ccc"; ctx.fillText("Steps", chartX + chartWidth / 2, chartY + chartHeight + 30); // 绘制曲线 ctx.strokeStyle = "#4a9eff"; ctx.lineWidth = 2; ctx.beginPath(); for (let i = 0; i < steps; i++) { const x = chartX + (chartWidth / (steps - 1)) * i; // adjustments存储的是实际调整后的sigma值 const adjustedValue = this.adjustments[i] !== undefined ? this.adjustments[i] : this.sigmas_data[i]; // 限制在0-1范围内 const clampedValue = Math.max(0, Math.min(1, adjustedValue)); const y = chartY + chartHeight - (clampedValue * chartHeight); if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } } ctx.stroke(); // 绘制控制点 for (let i = 0; i < steps; i++) { const x = chartX + (chartWidth / (steps - 1)) * i; const adjustedValue = this.adjustments[i] !== undefined ? this.adjustments[i] : this.sigmas_data[i]; const clampedValue = Math.max(0, Math.min(1, adjustedValue)); const y = chartY + chartHeight - (clampedValue * chartHeight); ctx.fillStyle = this.dragging_point === i ? "#ff6b6b" : "#4a9eff"; ctx.beginPath(); ctx.arc(x, y, 5, 0, Math.PI * 2); ctx.fill(); } // 绘制标题 ctx.fillStyle = "#ccc"; ctx.font = "14px Arial"; ctx.textAlign = "center"; ctx.fillText("Sigmas Schedule Editor", canvas.width / 2, 20); // 显示当前拖拽点的信息 if (this.dragging_point !== -1) { const orig = this.sigmas_data[this.dragging_point]; const adjusted = this.adjustments[this.dragging_point]; const multiplier = orig > 0 ? (adjusted / orig) : 1.0; // 向上取整到4位小数显示 const origCeil = this.ceilToFixed(orig, 4); const adjustedCeil = this.ceilToFixed(adjusted, 4); ctx.fillStyle = "#ff6b6b"; ctx.textAlign = "center"; ctx.font = "11px Arial"; ctx.fillText( `Step ${this.dragging_point}: Original=${origCeil.toFixed(4)}, Adjusted=${adjustedCeil.toFixed(4)}, Multiplier=${multiplier.toFixed(2)}x`, canvas.width / 2, 35 ); } }); }; // 监听节点尺寸变化 const onResize = nodeType.prototype.onResize; nodeType.prototype.onResize = function(size) { const result = onResize?.apply(this, arguments); // 节点尺寸改变时,强制重新计算画布尺寸 if (this.canvas) { this._canvasInitialized = false; // 重置标志 this.updateCanvas(true); } return result; }; // 节点移除时的处理 const onRemoved = nodeType.prototype.onRemoved; nodeType.prototype.onRemoved = function() { const result = onRemoved?.apply(this, arguments); // 清理画布 if (this && this.canvas) { const ctx = this.canvas.getContext("2d"); if (ctx) { ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); } this.canvas = null; } if (this) { this.canvasContainer = null; } // 清理事件监听器 if (this._sigmasEditorMessageHandler) { api.removeEventListener("sigmas_editor_update", this._sigmasEditorMessageHandler); this._sigmasEditorMessageHandler = null; } return result; }; } } });