Files
LAOGOU-666-ComfyUI-LG_Sampl…/web/sigmas_editor.js
T
2025-12-24 22:16:42 +08:00

484 lines
22 KiB
JavaScript
Raw 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";
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;
};
}
}
});