This commit is contained in:
kk8bit
2025-04-14 21:22:46 +08:00
parent 52464cd09c
commit bf3add7d3f
14 changed files with 1448 additions and 449 deletions
+14
View File
@@ -1,5 +1,19 @@
# Changelog
## [0.70.0] - 2025-04-14
- 设置菜单对UI完全自定义
- 整合notification到gululu统一控制
- 新增GuLuLu
- 新增GuLuLu右键菜单
- 新增GuLuLu设置项
- 新增GuLuLu继承KayTool通知为流式输出
- 修复Settings中设置项的bug
- 修复Monitor的初始位置问题
- 增强Clean VRAM兼容性
- 修复setget颜色设置问题
- 新增KayToolActions适配GuLuLu右键菜单
- UI初始配色调整
## [0.65.0] - 2025-04-9
- 大幅度提升Monitor性能
- 增加Clean VRAM
+11
View File
@@ -14,6 +14,8 @@ This is a custom node utility package developed for ComfyUI. Initially created f
**📌 所有 KayTool 节点包用到的资源都在 `ComfyUI/custom_nodes/kaytool` 文件夹内。| All KayTool node packages use resources in the `ComfyUI/custom_nodes/kaytool` folder.**
**⚙️ 在ComfyUI左下角的设置菜单里,所有 KayTool 的相关功能都支持自定义设置。| All KayTool features support custom settings in the ComfyUI settings menu.**
## 资源监视器 Resource Monitor
@@ -43,6 +45,15 @@ https://github.com/user-attachments/assets/2cd4179c-57fa-42dc-ae50-2feae9708e42
https://github.com/user-attachments/assets/b8d1d3f0-04d1-46c5-968a-e433778b73e6
## 咕噜噜 GuLuLu
- 主打陪伴的咕噜噜,支持调整KayTool的通知位置
GuLuLu, featuring companionship, supports adjusting KayTool notification positions.
- 继承KayTool的通知为流式输出
Inherits KayTool notifications as streaming output.
- 提供强大的右键菜单项目,快速调用KayTool的进阶功能
Provides powerful right-click menu items, quickly calling KayTool's advanced features.
## workflow PNG功能 Workflow Export to PNG
- 支持在右键菜单中`KayTool-workflow PNG`将当前工作流节点地图保存为PNG格式并内嵌工作流信息。
+2 -1
View File
@@ -6,7 +6,8 @@
"Custom Web Logo (BETA)": "自定义网页徽标",
"Workflow PNG": "Workflow PNG",
"NodeAlignment": "节点对齐工具栏",
"Resource Monitor": "资源监视器"
"Resource Monitor": "资源监视器",
"GuLuLu": "咕噜噜"
},
"nodeCategories": {
"Image": "图像",
+7 -1
View File
@@ -40,7 +40,13 @@
"name": "文件必须 < 1MB",
"tooltip": "这是一个测试功能,文件最好 <1MB ,否则可能无法显示"
},
"KayTool_GuLuLuSize": {
"name": "你要一个多大的咕噜噜~啊?"
},
"KayTool_EnableGuLuLu": {
"name": "让咕噜噜陪着你吧~!",
"tooltip": "咕噜噜会继承KayTool的通知项为流式输出,并支持通知框位置调整,以及强大的右键菜单项"
},
"KayTool_EnableAlignmentShortcuts": {
"name": "启用快捷键'Shift+W/A/S/D'",
"tooltip": "W/A/S/D 对应上、左、下、右对齐"
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "kaytool"
description ="KayTool boosts ComfyUI workflow efficiency with powerful tools like: image processing (resize, color, blur, expand, merge, crop), background/mask tasks via BiRefNet/RemBG, wireless data transfer (Get & Out), AI translation (Tencent/Baidu), Professional high-precision resource monitoring, dynamic math operations, flexible text handling, precision sliders, metadata/color profile-supported image saving, batch processing via folder loading, canvas node map export as PNG with metadata, quick Run options via right-click, node alignment toolbar with custom styles, and custom ComfyUI logo integration."
version = "0.65.1"
version = "0.70.0"
license = {file = "LICENSE"}
dependencies = ["rembg, timm, opencv-python, py-cpuinfo, pynvml"]
+64 -53
View File
@@ -1,5 +1,66 @@
import { app } from "../../../scripts/app.js";
import { showNotification, hideNotification } from "./notification.js";
import { showNotification, hideNotification } from "./gululu.js";
// 全局动作注册表
const KayToolActions = window.KayToolActions || {};
window.KayToolActions = KayToolActions;
async function cleanVRAM() {
const notifyElement = showNotification({
message: "🧹 GuLuLu: Working...",
size: "medium",
bgColor: "#fff3cd"
});
try {
const response = await fetch("/kaytool/clean_vram", { method: "POST" });
const message = await response.text();
hideNotification(notifyElement);
await new Promise(resolve => setTimeout(resolve, 350));
if (!response.ok) {
showNotification({
message: message || "🧹 GuLuLu: Failed due to server error",
bgColor: "#f8d7da",
size: "medium",
timeout: 7000
});
return;
}
const match = message.match(/Freed ([\d.]+) GB \(Allocated: ([\d.]+) GB -> ([\d.]+) GB, Total: ([\d.]+) GB -> ([\d.]+) GB\)/);
if (match) {
showNotification({
message: `🧹 GuLuLu: Done!
**${match[2]} GB** ➔ **${match[3]} GB**
(Total: ${match[4]} GB ➔ ${match[5]} GB)`,
bgColor: "#d4edda",
size: "medium",
timeout: 8000
});
} else {
showNotification({
message: `🧹 GuLuLu: ${message}`,
bgColor: "#d4edda",
size: "medium",
timeout: 8000
});
}
} catch (err) {
hideNotification(notifyElement);
await new Promise(resolve => setTimeout(resolve, 350));
showNotification({
message: `🧹 GuLuLu: ${err.message}`,
bgColor: "#f8d7da",
size: "medium",
timeout: 5000
});
}
}
// 注册 Clean VRAM 动作
KayToolActions.cleanVRAM = cleanVRAM;
app.registerExtension({
name: "KayTool.CleanVRAM",
@@ -8,7 +69,7 @@ app.registerExtension({
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
const options = originalGetCanvasMenuOptions.apply(this, args) || [];
const newOptions = [...options];
const showCleanVRAM = app.ui.settings.getSettingValue("KayTool.ShowCleanVRAM");
const showCleanVRAM = app.ui.settings.getSettingValue("KayTool.ShowCleanVRAM") ?? true;
if (showCleanVRAM) {
let kaytoolMenu = newOptions.find(opt => opt?.content === "KayTool") || {
@@ -23,57 +84,7 @@ app.registerExtension({
if (!kaytoolMenu.submenu.options.some(opt => opt?.content === "🧹 Clean VRAM")) {
kaytoolMenu.submenu.options.push({
content: "🧹 Clean VRAM",
async callback() {
const notifyElement = showNotification({
message: "🧹 **Clean:** Working...",
size: "medium",
bgColor: "#fff3cd"
});
try {
const response = await fetch("/kaytool/clean_vram", { method: "POST" });
const message = await response.text();
hideNotification(notifyElement);
if (!response.ok) {
showNotification({
message: message || "🧹 **Clean:** Failed due to server error",
bgColor: "#f8d7da",
size: "medium",
timeout: 7000
});
return;
}
const match = message.match(/Freed ([\d.]+) GB \(Allocated: ([\d.]+) GB -> ([\d.]+) GB, Total: ([\d.]+) GB -> ([\d.]+) GB\)/);
if (match) {
showNotification({
message: `🧹 **Clean:** Done!
**${match[2]} GB** ➔ **${match[3]} GB**
(Total: ${match[4]} GB ➔ ${match[5]} GB)`,
bgColor: "#d4edda",
size: "medium",
timeout: 8000
});
} else {
showNotification({
message: `🧹 **Clean:** ${message}`,
bgColor: "#d4edda",
size: "medium",
timeout: 8000
});
}
} catch (err) {
hideNotification(notifyElement);
showNotification({
message: `🧹 **Clean:** ${err.message}`,
bgColor: "#f8d7da",
size: "medium",
timeout: 5000
});
}
}
callback: cleanVRAM
});
}
}
+1 -4
View File
@@ -18,7 +18,6 @@ function showAlert(message) {
app.registerExtension({
name: "KayTool.SetGet",
registerCustomNodes() {
class KaySetNode extends LGraphNode {
defaultVisibility = true;
serialize_widgets = true;
@@ -27,7 +26,6 @@ app.registerExtension({
constructor(title) {
super(title);
// 从 settings 获取当前前景色和背景色
const fgColor = app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
const bgColor = app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
this.color = "#" + fgColor;
@@ -182,7 +180,6 @@ app.registerExtension({
constructor(title) {
super(title);
// 从 settings 获取当前颜色,默认为 "#000000"
const fgColor = app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
const bgColor = app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
this.color = "#" + fgColor;
@@ -289,5 +286,5 @@ app.registerExtension({
title: "𝙆 🛜Get"
}));
KayGetNode.category = "KayTool";
},
}
});
+868
View File
@@ -0,0 +1,868 @@
import { app } from "../../../scripts/app.js";
import { marked } from "./lib/marked.esm.js";
// 全局动作注册表
const KayToolActions = window.KayToolActions || {};
window.KayToolActions = KayToolActions;
// GuLuLu 管理器
const KayGuLuLuManager = {
container: null,
dragState: { isDragging: false, offsetX: 0, offsetY: 0 },
state: { left: 100, top: 100, transform: "scaleX(1)" },
pixelCanvas: null,
pixelCtx: null,
pixelDataCache: null,
imgSrc: "/extensions/kaytool/resources/GuLuLu.gif",
effectiveBounds: null,
isAnimating: false,
animationTimer: null,
contextMenu: null,
isEnabled: true,
size: 39, // 默认尺寸
// GuLuLu 右键菜单配置
menuConfig: [
{ label: "Say Hello", action: "playAnimation" },
{ label: "🧹 Clean VRAM", action: "cleanVRAM" },
{ label: "📦 Workflow PNG", action: "exportWorkflowPNG" },
{ label: "⭐ Star to me", action: "starToMe" }
],
init() {
this.isEnabled = app.ui.settings.getSettingValue("KayTool.EnableGuLuLu") ?? true;
this.size = app.ui.settings.getSettingValue("KayTool.GuLuLuSize") ?? 39;
this.loadState();
this.injectStyles();
this.setupGuLuLu();
this.updatePosition();
window.KayGuLuLuManager = this;
window.addEventListener("resize", () => this.updatePosition());
if (this.isEnabled) {
this.startRandomAnimation();
}
},
injectStyles() {
document.head.insertAdjacentHTML(
"beforeend",
`<style>
#kay-gululu-container {
position: fixed;
z-index: 10002;
user-select: none;
pointer-events: none;
display: none;
}
#kay-gululu-container.enabled {
display: block;
}
#kay-gululu-container img {
width: ${this.size}px;
height: auto;
display: block;
pointer-events: auto;
}
.gululu-context-menu {
position: fixed;
background: #333;
border: 2px solid #000;
border-radius: 8px;
box-shadow: 4px 4px 0 #999;
padding: 8px 0;
z-index: 10002;
font-family: 'Courier New', monospace;
font-size: 14px;
color: #fff;
white-space: nowrap;
overflow-y: auto;
}
.gululu-context-menu::-webkit-scrollbar {
width: 8px;
}
.gululu-context-menu::-webkit-scrollbar-track {
background: #333;
border-left: 2px solid #000;
}
.gululu-context-menu::-webkit-scrollbar-thumb {
background: #f0ff00;
border: 50px solid #f0ff00;
}
.gululu-context-menu-item {
padding: 4px 12px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
white-space: nowrap;
}
.gululu-context-menu-item:hover {
background: #555;
}
</style>`
);
},
setupGuLuLu() {
if (!this.container) {
this.container = document.createElement("div");
this.container.id = "kay-gululu-container";
const img = document.createElement("img");
img.src = this.imgSrc;
img.alt = "GuLuLu";
img.onload = () => {
this.updatePixelDataCache();
this.updatePosition();
};
this.container.appendChild(img);
document.body.appendChild(this.container);
this.pixelCanvas = document.createElement("canvas");
this.pixelCanvas.style.display = "none";
document.body.appendChild(this.pixelCanvas);
this.pixelCtx = this.pixelCanvas.getContext("2d", { willReadFrequently: true });
}
this.container.classList.toggle("enabled", this.isEnabled);
this.bindEvents();
},
bindEvents() {
if (this.container) {
this.container.removeEventListener("mousemove", this.handleMouseMove);
this.container.removeEventListener("mousedown", this.onDragStart);
this.container.removeEventListener("contextmenu", this.onContextMenu);
}
document.removeEventListener("mousemove", this.onDragging);
document.removeEventListener("mouseup", this.onDragEnd);
document.removeEventListener("click", this.hideContextMenu);
if (this.container && this.isEnabled) {
this.container.addEventListener("mousemove", this.handleMouseMove.bind(this));
this.container.addEventListener("mousedown", this.onDragStart.bind(this));
this.container.addEventListener("contextmenu", this.onContextMenu.bind(this));
}
document.addEventListener("mousemove", this.onDragging.bind(this));
document.addEventListener("mouseup", this.onDragEnd.bind(this));
document.addEventListener("click", this.hideContextMenu.bind(this));
},
updateSize(newSize) {
this.size = newSize;
const img = this.container?.querySelector("img");
if (img) {
img.style.width = `${this.size}px`;
this.updatePixelDataCache();
this.updatePosition();
}
},
playAnimation() {
if (!this.isEnabled || this.isAnimating || !this.container) return;
this.isAnimating = true;
const img = this.container.querySelector("img");
if (img) {
const src = img.src;
img.src = "";
img.src = src;
setTimeout(() => {
this.isAnimating = false;
}, 2000);
}
},
startRandomAnimation() {
const getRandomInterval = () => {
const min = 300 * 1000; // 10 秒
const max = 1200 * 1000; // 30 秒
return Math.floor(Math.random() * (max - min + 1)) + min;
};
const scheduleNextAnimation = () => {
clearTimeout(this.animationTimer);
if (!this.isEnabled) return;
const interval = getRandomInterval();
this.animationTimer = setTimeout(() => {
this.playAnimation();
scheduleNextAnimation();
}, interval);
};
scheduleNextAnimation();
},
updateEnabledState(enabled) {
this.isEnabled = enabled;
if (!this.container) {
this.setupGuLuLu();
} else {
this.container.classList.toggle("enabled", enabled);
this.bindEvents();
}
if (enabled) {
this.updatePosition();
this.startRandomAnimation();
} else {
clearTimeout(this.animationTimer);
}
},
loadState() {
const savedState = localStorage.getItem("kay-gululu-state");
if (savedState) {
this.state = JSON.parse(savedState);
}
},
saveState() {
localStorage.setItem("kay-gululu-state", JSON.stringify(this.state));
},
updatePosition() {
if (!this.container || !this.isEnabled) return;
const { windowRect, containerRect } = this.getRect();
if (!this.pixelDataCache && this.container.querySelector("img").complete) {
this.updatePixelDataCache();
}
const bounds = this.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
this.state.left = Math.max(-bounds.left, Math.min(this.state.left, windowRect.width - (bounds.left + bounds.width)));
this.state.top = Math.max(-bounds.top, Math.min(this.state.top, windowRect.height - (bounds.top + bounds.height)));
this.container.style.left = `${this.state.left}px`;
this.container.style.top = `${this.state.top}px`;
const midPoint = windowRect.width / 2;
this.state.transform = containerRect.left + containerRect.width / 2 > midPoint ? "scaleX(-1)" : "scaleX(1)";
this.container.style.transform = this.state.transform;
},
getRect() {
return {
windowRect: { width: window.innerWidth, height: window.innerHeight },
containerRect: this.container?.getBoundingClientRect() || { left: 0, top: 0, width: 0, height: 0 },
};
},
updatePixelDataCache() {
const { containerRect } = this.getRect();
if (containerRect.width === 0 || containerRect.height === 0) {
return;
}
this.pixelCanvas.width = containerRect.width;
this.pixelCanvas.height = containerRect.height;
this.pixelCtx.clearRect(0, 0, this.pixelCanvas.width, this.pixelCanvas.height);
this.pixelCtx.drawImage(this.container.querySelector("img"), 0, 0, containerRect.width, containerRect.height);
this.pixelDataCache = this.pixelCtx.getImageData(0, 0, this.pixelCanvas.width, this.pixelCanvas.height);
this.calculateEffectiveBounds();
},
calculateEffectiveBounds() {
if (!this.pixelDataCache) return;
const data = this.pixelDataCache.data;
const width = this.pixelCanvas.width;
const height = this.pixelCanvas.height;
let minX = width,
minY = height,
maxX = 0,
maxY = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const index = (y * width + x) * 4;
if (data[index + 3] !== 0) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
}
this.effectiveBounds = {
left: minX,
top: minY,
width: maxX - minX + 1,
height: maxY - minY + 1,
};
},
isPixelTransparent(x, y) {
if (!this.pixelDataCache) return true;
const index = (y * this.pixelDataCache.width + x) * 4;
return this.pixelDataCache.data[index + 3] === 0;
},
handleMouseMove(e) {
const { containerRect } = this.getRect();
const offsetX = e.clientX - containerRect.left;
const offsetY = e.clientY - containerRect.top;
if (offsetX >= 0 && offsetX < containerRect.width && offsetY >= 0 && offsetY < containerRect.height) {
this.container.style.cursor = this.isPixelTransparent(offsetX, offsetY) ? "default" : "move";
}
},
onDragStart(e) {
e.preventDefault();
const { containerRect } = this.getRect();
const offsetX = e.clientX - containerRect.left;
const offsetY = e.clientY - containerRect.top;
if (this.isPixelTransparent(offsetX, offsetY)) return;
this.dragState.isDragging = true;
this.dragState.offsetX = offsetX;
this.dragState.offsetY = offsetY;
},
onDragging(e) {
if (!this.dragState.isDragging) return;
const { windowRect, containerRect } = this.getRect();
this.state.left = e.clientX - this.dragState.offsetX;
this.state.top = e.clientY - this.dragState.offsetY;
const bounds = this.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
this.state.left = Math.max(-bounds.left, Math.min(this.state.left, windowRect.width - (bounds.left + bounds.width)));
this.state.top = Math.max(-bounds.top, Math.min(this.state.top, windowRect.height - (bounds.top + bounds.height)));
this.updatePosition();
},
onDragEnd() {
if (this.dragState.isDragging) {
this.dragState.isDragging = false;
this.saveState();
this.updatePosition();
}
},
onContextMenu(e) {
e.preventDefault();
const { containerRect } = this.getRect();
const offsetX = e.clientX - containerRect.left;
const offsetY = e.clientY - containerRect.top;
if (this.isPixelTransparent(offsetX, offsetY)) return;
this.hideContextMenu();
this.showContextMenu(e.clientX, e.clientY);
},
showContextMenu(x, y) {
const menuItems = [];
// 根据 menuConfig 动态生成菜单项
this.menuConfig.forEach((config) => {
let action;
if (config.action === "playAnimation") {
action = () => this.playAnimation();
} else if (KayToolActions[config.action]) {
action = () => KayToolActions[config.action]();
}
if (action) {
menuItems.push({
label: config.label,
action,
});
}
});
this.contextMenu = document.createElement("div");
this.contextMenu.className = "gululu-context-menu";
document.body.appendChild(this.contextMenu);
// 先创建菜单项以计算宽度
const tempDiv = document.createElement("div");
tempDiv.style.visibility = "hidden";
tempDiv.style.position = "absolute";
tempDiv.style.fontFamily = "'Courier New', monospace";
tempDiv.style.fontSize = "14px";
tempDiv.style.fontWeight = "bold";
tempDiv.style.padding = "4px 12px";
tempDiv.style.whiteSpace = "nowrap";
document.body.appendChild(tempDiv);
let maxWidth = 0;
menuItems.forEach((item) => {
tempDiv.textContent = item.label;
const itemWidth = tempDiv.getBoundingClientRect().width;
maxWidth = Math.max(maxWidth, itemWidth);
});
document.body.removeChild(tempDiv);
// 设置菜单固定宽度(包括 padding)
this.contextMenu.style.width = `${maxWidth + 6}px`; // 12px 左右 padding
menuItems.forEach((item) => {
const menuItem = document.createElement("div");
menuItem.className = "gululu-context-menu-item";
menuItem.textContent = item.label;
menuItem.addEventListener("click", (e) => {
e.stopPropagation();
item.action();
this.hideContextMenu();
});
this.contextMenu.appendChild(menuItem);
});
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const { containerRect } = this.getRect();
const gululuCenterY = containerRect.top + containerRect.height / 2;
const isAboveHalf = gululuCenterY < windowHeight / 2;
let menuLeft = x;
let menuTop = y;
// 计算菜单高度并检查边界
let { height: menuHeight } = this.contextMenu.getBoundingClientRect();
// 垂直方向调整,避免遮挡 GuLuLu,并处理滚动
if (isAboveHalf) {
menuTop = y;
if (menuTop + menuHeight > windowHeight - 10) {
// 接近底部边界,限制高度,启用滚动
this.contextMenu.style.maxHeight = `${windowHeight - 20}px`; // 上下各留10px
menuTop = 10; // 确保顶部不贴边
}
} else {
menuTop = y - menuHeight;
if (menuTop < 10) {
// 接近顶部边界,限制高度,启用滚动
this.contextMenu.style.maxHeight = `${windowHeight - 20}px`; // 上下各留10px
menuTop = 10; // 确保顶部不贴边
}
}
// 水平方向:优先贴近点击点,允许超出右边界
const { width: menuWidth } = this.contextMenu.getBoundingClientRect();
if (menuLeft + menuWidth > windowWidth) {
menuLeft = windowWidth - menuWidth;
}
this.contextMenu.style.left = `${menuLeft - 10}px`;
this.contextMenu.style.top = `${menuTop}px`;
},
hideContextMenu() {
if (this.contextMenu) {
this.contextMenu.remove();
this.contextMenu = null;
}
},
};
// 通知框函数(保持不变)
export function showNotification({
message,
bgColor = "#fff3cd",
timeout = 0,
onYes = null,
onNo = null,
size = "small"
}) {
const sizes = {
small: { maxWidth: "250px", maxHeight: "150px", padding: "12px 16px" },
medium: { maxWidth: "400px", maxHeight: "300px", padding: "12px 16px" },
large: { maxWidth: "600px", maxHeight: "450px", padding: "12px 16px" }
};
const selectedSize = sizes[size] || sizes.small;
const div = document.createElement("div");
Object.assign(div.style, {
position: "fixed",
width: selectedSize.maxWidth,
padding: selectedSize.padding,
backgroundColor: bgColor,
color: "#333",
fontFamily: "'Courier New', monospace",
fontSize: "14px",
wordWrap: "break-word",
zIndex: "10000",
border: "2px solid #000",
borderRadius: "12px",
boxShadow: "4px 4px 0 #999",
opacity: "0",
transition: "opacity 0.3s ease-in-out",
display: "flex",
flexDirection: "column",
gap: "8px",
boxSizing: "border-box"
});
document.body.appendChild(div);
const isGuLuLuEnabled = window.KayGuLuLuManager && window.KayGuLuLuManager.isEnabled;
if (isGuLuLuEnabled) {
window.KayGuLuLuManager.playAnimation();
}
let tail, tailBorder;
if (isGuLuLuEnabled) {
tail = document.createElement("div");
Object.assign(tail.style, {
position: "absolute",
width: "0",
height: "0",
borderLeft: "6px solid transparent",
borderRight: "6px solid transparent",
zIndex: "-1"
});
div.appendChild(tail);
tailBorder = document.createElement("div");
Object.assign(tailBorder.style, {
position: "absolute",
width: "0",
height: "0",
borderLeft: "8px solid transparent",
borderRight: "8px solid transparent",
zIndex: "-2"
});
div.appendChild(tailBorder);
}
const closeButton = document.createElement("div");
closeButton.textContent = "X";
Object.assign(closeButton.style, {
position: "absolute",
top: "12px",
left: "12px",
width: "12px",
height: "12px",
backgroundColor: "#dc3545",
border: "2px solid #000",
borderRadius: "50%",
fontSize: "10px",
lineHeight: "10px",
textAlign: "center",
cursor: "pointer",
boxShadow: "2px 2px 0 #999"
});
closeButton.addEventListener("click", () => hideNotification(div));
closeButton.addEventListener("mousedown", () => {
closeButton.style.transform = "translate(1px, 1px)";
closeButton.style.boxShadow = "0 0 0 #999";
});
document.addEventListener("mouseup", () => {
closeButton.style.transform = "translate(0, 0)";
closeButton.style.boxShadow = "2px 2px 0 #999";
});
div.appendChild(closeButton);
const contentDiv = document.createElement("div");
Object.assign(contentDiv.style, {
marginTop: "20px",
overflowY: "auto",
overflowX: "hidden",
padding: "0 4px 0 0",
boxSizing: "border-box",
wordBreak: "break-word",
fontFamily: "'Courier New', monospace",
whiteSpace: "pre-wrap"
});
contentDiv.style.cssText += `
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #333; border-left: 2px solid #000; }
::-webkit-scrollbar-thumb { background: #999; border: 2px solid #000; }
`;
div.appendChild(contentDiv);
let buttonContainer = null;
if (onYes || onNo) {
buttonContainer = document.createElement("div");
Object.assign(buttonContainer.style, {
display: "flex",
gap: "8px",
justifyContent: "flex-end",
marginTop: "4px",
paddingBottom: "4px"
});
div.appendChild(buttonContainer);
if (onYes) {
const yesButton = document.createElement("button");
yesButton.textContent = "Yes";
Object.assign(yesButton.style, {
padding: "4px 8px",
backgroundColor: "#28a745",
border: "2px solid #000",
borderRadius: "8px",
fontFamily: "'Courier New', monospace",
fontSize: "12px",
fontWeight: "bold",
color: "#fff",
cursor: "pointer",
boxShadow: "2px 2px 0 #999"
});
yesButton.addEventListener("mousedown", () => {
yesButton.style.transform = "translate(1px, 1px)";
yesButton.style.boxShadow = "0 0 0 #999";
});
document.addEventListener("mouseup", () => {
yesButton.style.transform = "translate(0, 0)";
yesButton.style.boxShadow = "2px 2px 0 #999";
});
yesButton.addEventListener("click", () => {
hideNotification(div);
onYes();
});
buttonContainer.appendChild(yesButton);
}
if (onNo) {
const noButton = document.createElement("button");
noButton.textContent = "No";
Object.assign(noButton.style, {
padding: "4px 8px",
backgroundColor: "#dc3545",
border: "2px solid #000",
borderRadius: "8px",
fontFamily: "'Courier New', monospace",
fontSize: "12px",
fontWeight: "bold",
color: "#fff",
cursor: "pointer",
boxShadow: "2px 2px 0 #999"
});
noButton.addEventListener("mousedown", () => {
noButton.style.transform = "translate(1px, 1px)";
noButton.style.boxShadow = "0 0 0 #999";
});
document.addEventListener("mouseup", () => {
noButton.style.transform = "translate(0, 0)";
noButton.style.boxShadow = "2px 2px 0 #999";
});
noButton.addEventListener("click", () => {
hideNotification(div);
onNo();
});
buttonContainer.appendChild(noButton);
}
}
const updatePosition = () => {
if (isGuLuLuEnabled && window.KayGuLuLuManager && window.KayGuLuLuManager.container) {
const gululu = window.KayGuLuLuManager;
const containerRect = gululu.container.getBoundingClientRect();
const windowRect = { width: window.innerWidth, height: window.innerHeight };
const notificationRect = div.getBoundingClientRect();
const bounds = gululu.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
const gululuCenterX = containerRect.left + containerRect.width / 2;
const isLeftSide = gululuCenterX < windowRect.width / 2;
const horizontalOffset = 10;
const gululuCenterY = containerRect.top + containerRect.height / 2;
const isTopHalf = gululuCenterY < windowRect.height / 2;
const verticalOffset = -26;
let left, top;
if (isLeftSide) {
left = containerRect.right + horizontalOffset;
if (tail) {
tail.style.left = "-8px";
tail.style.right = "auto";
tail.style.borderRight = `8px solid ${bgColor}`;
tail.style.borderLeft = "none";
tail.style.borderTop = "6px solid transparent";
tail.style.borderBottom = "6px solid transparent";
}
if (tailBorder) {
tailBorder.style.left = "-10px";
tailBorder.style.right = "auto";
tailBorder.style.borderRight = "10px solid #000";
tailBorder.style.borderLeft = "none";
tailBorder.style.borderTop = "8px solid transparent";
tailBorder.style.borderBottom = "8px solid transparent";
}
} else {
left = containerRect.left - notificationRect.width - horizontalOffset;
if (tail) {
tail.style.right = "-8px";
tail.style.left = "auto";
tail.style.borderLeft = `8px solid ${bgColor}`;
tail.style.borderRight = "none";
tail.style.borderTop = "6px solid transparent";
tail.style.borderBottom = "6px solid transparent";
}
if (tailBorder) {
tailBorder.style.right = "-10px";
tailBorder.style.left = "auto";
tailBorder.style.borderLeft = "10px solid #000";
tailBorder.style.borderRight = "none";
tailBorder.style.borderTop = "8px solid transparent";
tailBorder.style.borderBottom = "8px solid transparent";
}
}
if (isTopHalf) {
top = containerRect.top + bounds.top + bounds.height + verticalOffset;
if (tail) {
tail.style.top = "12px";
tail.style.bottom = "auto";
tail.style.transform = "none";
}
if (tailBorder) {
tailBorder.style.top = "10px";
tailBorder.style.bottom = "auto";
tailBorder.style.transform = "none";
}
} else {
top = containerRect.top + bounds.top - notificationRect.height - verticalOffset;
if (tail) {
tail.style.top = "auto";
tail.style.bottom = "12px";
tail.style.transform = "none";
}
if (tailBorder) {
tailBorder.style.top = "auto";
tailBorder.style.bottom = "10px";
tailBorder.style.transform = "none";
}
}
left = Math.max(0, Math.min(left, windowRect.width - notificationRect.width));
top = Math.max(0, Math.min(top, windowRect.height - notificationRect.height));
div.style.left = `${left}px`;
div.style.top = `${top}px`;
} else {
div.style.left = "20px";
div.style.bottom = "20px";
div.style.top = "auto";
}
};
const updateHeights = () => {
const buttonHeight = buttonContainer ? buttonContainer.offsetHeight : 0;
const totalExtraHeight = buttonHeight + 36;
div.style.maxHeight = `calc(${selectedSize.maxHeight} + ${totalExtraHeight}px)`;
contentDiv.style.maxHeight = `calc(${selectedSize.maxHeight} - ${totalExtraHeight}px)`;
updatePosition();
};
updateHeights();
const throttle = (func, limit) => {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= limit) {
func(...args);
lastCall = now;
}
};
};
const onGuLuLuMove = throttle(updatePosition, 16);
if (isGuLuLuEnabled) {
document.addEventListener("mousemove", onGuLuLuMove);
document.addEventListener("mouseup", onGuLuLuMove);
}
window.addEventListener("resize", updatePosition);
const cleanup = () => {
document.removeEventListener("mousemove", onGuLuLuMove);
document.removeEventListener("mouseup", onGuLuLuMove);
window.removeEventListener("resize", updatePosition);
};
try {
marked.setOptions({ breaks: true, gfm: true });
if (!message || typeof message !== "string" || message.trim() === "") {
contentDiv.textContent = "无消息显示";
updateHeights();
return div;
}
if (isGuLuLuEnabled) {
const chars = message.split("");
let charIndex = 0;
let currentMarkdown = "";
let renderedHtml = "";
function typeWriter() {
if (charIndex >= chars.length) {
if (currentMarkdown.trim()) {
renderedHtml += marked.parse(currentMarkdown);
}
contentDiv.innerHTML = renderedHtml;
updateHeights();
return;
}
currentMarkdown += chars[charIndex];
const isNewline = chars[charIndex] === "\n";
const isDoubleNewline = isNewline && charIndex > 0 && chars[charIndex - 1] === "\n";
if (isDoubleNewline) {
if (currentMarkdown.trim()) {
renderedHtml += marked.parse(currentMarkdown);
currentMarkdown = "";
}
contentDiv.innerHTML = renderedHtml;
} else {
contentDiv.innerHTML = renderedHtml + `<div>${currentMarkdown}</div>`;
}
charIndex++;
setTimeout(typeWriter, 50);
}
setTimeout(typeWriter, 300);
} else {
contentDiv.innerHTML = marked.parse(message);
updateHeights();
}
} catch (e) {
contentDiv.textContent = message || "显示消息出错";
updateHeights();
}
setTimeout(() => (div.style.opacity = "1"), 10);
if (timeout > 0) {
setTimeout(() => {
cleanup();
hideNotification(div);
}, timeout);
}
const originalHide = hideNotification.bind(null, div);
div.hide = () => {
cleanup();
originalHide();
};
return div;
}
export function hideNotification(element) {
if (element?.parentNode) {
element.style.opacity = "0";
setTimeout(() => element.parentNode?.removeChild(element), 300);
}
}
// 注册扩展
app.registerExtension({
name: "KayTool.GuLuLu",
init() {
KayGuLuLuManager.init();
},
cleanup() {
document.removeEventListener("mousemove", KayGuLuLuManager.onDragging);
document.removeEventListener("mouseup", KayGuLuLuManager.onDragEnd);
document.removeEventListener("click", KayGuLuLuManager.hideContextMenu);
KayGuLuLuManager.container?.remove();
KayGuLuLuManager.pixelCanvas?.remove();
KayGuLuLuManager.contextMenu?.remove();
clearTimeout(KayGuLuLuManager.animationTimer);
delete window.KayGuLuLuManager;
},
});
+336 -246
View File
@@ -1,267 +1,357 @@
import { marked } from "./lib/marked.esm.js";
// import { marked } from "./lib/marked.esm.js";
export function showNotification({
message,
bgColor = "#fff3cd",
timeout = 0,
onYes = null,
onNo = null,
size = "small"
}) {
const sizes = {
small: { maxWidth: "250px", maxHeight: "150px", padding: "12px 16px" },
medium: { maxWidth: "400px", maxHeight: "300px", padding: "12px 16px" },
large: { maxWidth: "600px", maxHeight: "450px", padding: "12px 16px" }
};
const selectedSize = sizes[size] || sizes.small;
// export function showNotification({
// message,
// bgColor = "#fff3cd",
// timeout = 0,
// onYes = null,
// onNo = null,
// size = "small"
// }) {
// const sizes = {
// small: { maxWidth: "250px", maxHeight: "150px", padding: "12px 16px" },
// medium: { maxWidth: "400px", maxHeight: "300px", padding: "12px 16px" },
// large: { maxWidth: "600px", maxHeight: "450px", padding: "12px 16px" }
// };
// const selectedSize = sizes[size] || sizes.small;
const div = document.createElement("div");
Object.assign(div.style, {
position: "fixed",
bottom: "20px",
left: "50px",
width: selectedSize.maxWidth,
padding: selectedSize.padding,
backgroundColor: bgColor,
color: "#333",
fontFamily: "'Courier New', monospace",
fontSize: "14px",
// lineHeight: "1.4",
wordWrap: "break-word",
zIndex: "10000",
border: "2px solid #000",
borderRadius: "12px",
boxShadow: "4px 4px 0 #999",
opacity: "0",
transition: "opacity 0.3s ease-in-out",
display: "flex",
flexDirection: "column",
gap: "8px",
boxSizing: "border-box"
});
document.body.appendChild(div);
// const div = document.createElement("div");
// Object.assign(div.style, {
// position: "fixed",
// // Position will be set dynamically below
// width: selectedSize.maxWidth,
// padding: selectedSize.padding,
// backgroundColor: bgColor,
// color: "#333",
// fontFamily: "'Courier New', monospace",
// fontSize: "14px",
// wordWrap: "break-word",
// zIndex: "10000",
// border: "2px solid #000",
// borderRadius: "12px",
// boxShadow: "4px 4px 0 #999",
// opacity: "0",
// transition: "opacity 0.3s ease-in-out",
// display: "flex",
// flexDirection: "column",
// gap: "8px",
// boxSizing: "border-box"
// });
// document.body.appendChild(div);
const tail = document.createElement("div");
Object.assign(tail.style, {
position: "absolute",
bottom: "-8px",
left: "10px",
width: "0",
height: "0",
borderLeft: "6px solid transparent",
borderRight: "6px solid transparent",
borderTop: `8px solid ${bgColor}`,
zIndex: "-1"
});
div.appendChild(tail);
// // Tail for visual connection to GuLuLu
// const tail = document.createElement("div");
// Object.assign(tail.style, {
// position: "absolute",
// width: "0",
// height: "0",
// borderLeft: "6px solid transparent",
// borderRight: "6px solid transparent",
// zIndex: "-1"
// });
// div.appendChild(tail);
const tailBorder = document.createElement("div");
Object.assign(tailBorder.style, {
position: "absolute",
bottom: "-10px",
left: "8px",
width: "0",
height: "0",
borderLeft: "8px solid transparent",
borderRight: "8px solid transparent",
borderTop: "10px solid #000",
zIndex: "-2"
});
div.appendChild(tailBorder);
// const tailBorder = document.createElement("div");
// Object.assign(tailBorder.style, {
// position: "absolute",
// width: "0",
// height: "0",
// borderLeft: "8px solid transparent",
// borderRight: "8px solid transparent",
// zIndex: "-2"
// });
// div.appendChild(tailBorder);
const closeButton = document.createElement("div");
closeButton.textContent = "X";
Object.assign(closeButton.style, {
position: "absolute",
top: "12px",
left: "12px",
width: "12px",
height: "12px",
backgroundColor: "#dc3545",
border: "2px solid #000",
borderRadius: "50%",
fontSize: "10px",
lineHeight: "10px",
textAlign: "center",
cursor: "pointer",
boxShadow: "2px 2px 0 #999"
});
closeButton.addEventListener("click", () => hideNotification(div));
closeButton.addEventListener("mousedown", () => {
closeButton.style.transform = "translate(1px, 1px)";
closeButton.style.boxShadow = "0 0 0 #999";
});
document.addEventListener("mouseup", () => {
closeButton.style.transform = "translate(0, 0)";
closeButton.style.boxShadow = "2px 2px 0 #999";
});
div.appendChild(closeButton);
// const closeButton = document.createElement("div");
// closeButton.textContent = "X";
// Object.assign(closeButton.style, {
// position: "absolute",
// top: "12px",
// left: "12px",
// width: "12px",
// height: "12px",
// backgroundColor: "#dc3545",
// border: "2px solid #000",
// borderRadius: "50%",
// fontSize: "10px",
// lineHeight: "10px",
// textAlign: "center",
// cursor: "pointer",
// boxShadow: "2px 2px 0 #999"
// });
// closeButton.addEventListener("click", () => hideNotification(div));
// closeButton.addEventListener("mousedown", () => {
// closeButton.style.transform = "translate(1px, 1px)";
// closeButton.style.boxShadow = "0 0 0 #999";
// });
// document.addEventListener("mouseup", () => {
// closeButton.style.transform = "translate(0, 0)";
// closeButton.style.boxShadow = "2px 2px 0 #999";
// });
// div.appendChild(closeButton);
const contentDiv = document.createElement("div");
Object.assign(contentDiv.style, {
marginTop: "20px",
overflowY: "auto",
overflowX: "hidden",
padding: "0 4px 0 0",
boxSizing: "border-box",
wordBreak: "break-word",
fontFamily: "'Courier New', monospace",
whiteSpace: "pre-wrap" // 保留换行和空格
});
contentDiv.style.cssText += `
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: #333; border-left: 2px solid #000; }
::-webkit-scrollbar-thumb { background: #999; border: 2px solid #000; }
`;
div.appendChild(contentDiv);
// const contentDiv = document.createElement("div");
// Object.assign(contentDiv.style, {
// marginTop: "20px",
// overflowY: "auto",
// overflowX: "hidden",
// padding: "0 4px 0 0",
// boxSizing: "border-box",
// wordBreak: "break-word",
// fontFamily: "'Courier New', monospace",
// whiteSpace: "pre-wrap"
// });
// contentDiv.style.cssText += `
// ::-webkit-scrollbar { width: 8px; }
// ::-webkit-scrollbar-track { background: #333; border-left: 2px solid #000; }
// ::-webkit-scrollbar-thumb { background: #999; border: 2px solid #000; }
// `;
// div.appendChild(contentDiv);
let buttonContainer = null;
if (onYes || onNo) {
buttonContainer = document.createElement("div");
Object.assign(buttonContainer.style, {
display: "flex",
gap: "8px",
justifyContent: "flex-end",
marginTop: "4px",
paddingBottom: "4px"
});
div.appendChild(buttonContainer);
// let buttonContainer = null;
// if (onYes || onNo) {
// buttonContainer = document.createElement("div");
// Object.assign(buttonContainer.style, {
// display: "flex",
// gap: "8px",
// justifyContent: "flex-end",
// marginTop: "4px",
// paddingBottom: "4px"
// });
// div.appendChild(buttonContainer);
if (onYes) {
const yesButton = document.createElement("button");
yesButton.textContent = "Yes";
Object.assign(yesButton.style, {
padding: "4px 8px",
backgroundColor: "#28a745",
border: "2px solid #000",
borderRadius: "8px",
fontFamily: "'Courier New', monospace",
fontSize: "12px",
fontWeight: "bold",
color: "#fff",
cursor: "pointer",
boxShadow: "2px 2px 0 #999"
});
yesButton.addEventListener("mousedown", () => {
yesButton.style.transform = "translate(1px, 1px)";
yesButton.style.boxShadow = "0 0 0 #999";
});
document.addEventListener("mouseup", () => {
yesButton.style.transform = "translate(0, 0)";
yesButton.style.boxShadow = "2px 2px 0 #999";
});
yesButton.addEventListener("click", () => {
hideNotification(div);
onYes();
});
buttonContainer.appendChild(yesButton);
}
// if (onYes) {
// const yesButton = document.createElement("button");
// yesButton.textContent = "Yes";
// Object.assign(yesButton.style, {
// padding: "4px 8px",
// backgroundColor: "#28a745",
// border: "2px solid #000",
// borderRadius: "8px",
// fontFamily: "'Courier New', monospace",
// fontSize: "12px",
// fontWeight: "bold",
// color: "#fff",
// cursor: "pointer",
// boxShadow: "2px 2px 0 #999"
// });
// yesButton.addEventListener("mousedown", () => {
// yesButton.style.transform = "translate(1px, 1px)";
// yesButton.style.boxShadow = "0 0 0 #999";
// });
// document.addEventListener("mouseup", () => {
// yesButton.style.transform = "translate(0, 0)";
// yesButton.style.boxShadow = "2px 2px 0 #999";
// });
// yesButton.addEventListener("click", () => {
// hideNotification(div);
// onYes();
// });
// buttonContainer.appendChild(yesButton);
// }
if (onNo) {
const noButton = document.createElement("button");
noButton.textContent = "No";
Object.assign(noButton.style, {
padding: "4px 8px",
backgroundColor: "#dc3545",
border: "2px solid #000",
borderRadius: "8px",
fontFamily: "'Courier New', monospace",
fontSize: "12px",
fontWeight: "bold",
color: "#fff",
cursor: "pointer",
boxShadow: "2px 2px 0 #999"
});
noButton.addEventListener("mousedown", () => {
noButton.style.transform = "translate(1px, 1px)";
noButton.style.boxShadow = "0 0 0 #999";
});
document.addEventListener("mouseup", () => {
noButton.style.transform = "translate(0, 0)";
noButton.style.boxShadow = "2px 2px 0 #999";
});
noButton.addEventListener("click", () => {
hideNotification(div);
onNo();
});
buttonContainer.appendChild(noButton);
}
}
// if (onNo) {
// const noButton = document.createElement("button");
// noButton.textContent = "No";
// Object.assign(noButton.style, {
// padding: "4px 8px",
// backgroundColor: "#dc3545",
// border: "2px solid #000",
// borderRadius: "8px",
// fontFamily: "'Courier New', monospace",
// fontSize: "12px",
// fontWeight: "bold",
// color: "#fff",
// cursor: "pointer",
// boxShadow: "2px 2px 0 #999"
// });
// noButton.addEventListener("mousedown", () => {
// noButton.style.transform = "translate(1px, 1px)";
// noButton.style.boxShadow = "0 0 0 #999";
// });
// document.addEventListener("mouseup", () => {
// noButton.style.transform = "translate(0, 0)";
// noButton.style.boxShadow = "2px 2px 0 #999";
// });
// noButton.addEventListener("click", () => {
// hideNotification(div);
// onNo();
// });
// buttonContainer.appendChild(noButton);
// }
// }
const updateHeights = () => {
const buttonHeight = buttonContainer ? buttonContainer.offsetHeight : 0;
const totalExtraHeight = buttonHeight + 36;
div.style.maxHeight = `calc(${selectedSize.maxHeight} + ${totalExtraHeight}px)`;
contentDiv.style.maxHeight = `calc(${selectedSize.maxHeight} - ${totalExtraHeight}px)`;
};
updateHeights();
// // Function to update notification position based on GuLuLu
// const updatePosition = () => {
// if (!window.KayGuLuLuManager || !window.KayGuLuLuManager.container) {
// console.warn("KayGuLuLuManager not found, using fallback position");
// div.style.left = "50px";
// div.style.bottom = "20px";
// return;
// }
// 打字机效果:基于 Markdown 原始文本,仅双换行触发解析
try {
marked.setOptions({ breaks: true, gfm: true });
// const gululu = window.KayGuLuLuManager;
// const containerRect = gululu.container.getBoundingClientRect();
// const windowRect = { width: window.innerWidth, height: window.innerHeight };
// const notificationRect = div.getBoundingClientRect();
// const bounds = gululu.effectiveBounds || { left: 0, top: 0, width: containerRect.width, height: containerRect.height };
if (!message || typeof message !== "string" || message.trim() === "") {
contentDiv.textContent = "No message to display";
updateHeights();
return div;
}
// // Determine side placement
// const gululuCenterX = containerRect.left + containerRect.width / 2;
// const isLeftSide = gululuCenterX < windowRect.width / 2;
// const horizontalOffset = 10; // Distance from GuLuLu
// 分割 Markdown 原始文本为字符数组
const chars = message.split('');
let charIndex = 0;
let currentMarkdown = ''; // 当前累积的 Markdown 文本
let renderedHtml = ''; // 已渲染的 HTML
// // Determine vertical placement
// const gululuCenterY = containerRect.top + containerRect.height / 2;
// const isTopHalf = gululuCenterY < windowRect.height / 2;
// const verticalOffset = 10; // Distance from GuLuLu
function typeWriter() {
if (charIndex >= chars.length) {
// 所有字符打完,解析剩余的 Markdown
if (currentMarkdown.trim()) {
renderedHtml += marked.parse(currentMarkdown);
}
contentDiv.innerHTML = renderedHtml;
updateHeights();
return;
}
// let left, top;
// 逐字符追加到 currentMarkdown
currentMarkdown += chars[charIndex];
const isNewline = chars[charIndex] === '\n';
const isDoubleNewline = isNewline && charIndex > 0 && chars[charIndex - 1] === '\n';
// // Horizontal position
// if (isLeftSide) {
// // Notification to the right of GuLuLu
// left = containerRect.left + bounds.left + bounds.width + horizontalOffset;
// tail.style.left = "10px";
// tail.style.right = "auto";
// tailBorder.style.left = "8px";
// tailBorder.style.right = "auto";
// } else {
// // Notification to the left of GuLuLu
// left = containerRect.left + bounds.left - notificationRect.width - horizontalOffset;
// tail.style.right = "10px";
// tail.style.left = "auto";
// tailBorder.style.right = "8px";
// tailBorder.style.left = "auto";
// }
if (isDoubleNewline) {
// 遇到双换行,解析当前累积的 Markdown
if (currentMarkdown.trim()) {
renderedHtml += marked.parse(currentMarkdown);
currentMarkdown = ''; // 重置当前 Markdown
}
contentDiv.innerHTML = renderedHtml;
} else {
// 显示已渲染的 HTML + 当前未解析的 Markdown
contentDiv.innerHTML = renderedHtml + `<div>${currentMarkdown}</div>`;
}
// // Vertical position
// if (isTopHalf) {
// // Notification below GuLuLu
// top = containerRect.top + bounds.top + bounds.height + verticalOffset;
// tail.style.top = "auto";
// tail.style.bottom = "-8px";
// tail.style.borderTop = `8px solid ${bgColor}`;
// tail.style.borderBottom = "none";
// tailBorder.style.top = "auto";
// tailBorder.style.bottom = "-10px";
// tailBorder.style.borderTop = "10px solid #000";
// tailBorder.style.borderBottom = "none";
// } else {
// // Notification above GuLuLu
// top = containerRect.top + bounds.top - notificationRect.height - verticalOffset;
// tail.style.bottom = "auto";
// tail.style.top = "-8px";
// tail.style.borderBottom = `8px solid ${bgColor}`;
// tail.style.borderTop = "none";
// tailBorder.style.bottom = "auto";
// tailBorder.style.top = "-10px";
// tailBorder.style.borderBottom = "10px solid #000";
// tailBorder.style.borderTop = "none";
// }
charIndex++;
setTimeout(typeWriter, 50); // 字符间间隔 100ms
}
// // Clamp to screen boundaries
// left = Math.max(0, Math.min(left, windowRect.width - notificationRect.width));
// top = Math.max(0, Math.min(top, windowRect.height - notificationRect.height));
setTimeout(typeWriter, 300);
} catch (e) {
console.error("Error in typewriter effect:", e);
contentDiv.textContent = message || "Error displaying message";
updateHeights();
}
// div.style.left = `${left}px`;
// div.style.top = `${top}px`;
// };
setTimeout(() => div.style.opacity = "1", 10);
if (timeout > 0) {
setTimeout(() => hideNotification(div), timeout);
}
// // Update heights and allow vertical adjustments for typewriter effect
// const updateHeights = () => {
// const buttonHeight = buttonContainer ? buttonContainer.offsetHeight : 0;
// const totalExtraHeight = buttonHeight + 36;
// div.style.maxHeight = `calc(${selectedSize.maxHeight} + ${totalExtraHeight}px)`;
// contentDiv.style.maxHeight = `calc(${selectedSize.maxHeight} - ${totalExtraHeight}px)`;
// updatePosition(); // Recompute position after height changes
// };
// updateHeights();
return div;
}
// // Update position when GuLuLu moves
// const onGuLuLuMove = () => updatePosition();
// document.addEventListener('mousemove', onGuLuLuMove);
// document.addEventListener('mouseup', onGuLuLuMove);
// window.addEventListener('resize', updatePosition);
export function hideNotification(element) {
if (element?.parentNode) {
element.style.opacity = "0";
setTimeout(() => element.parentNode?.removeChild(element), 300);
}
}
// // Clean up event listeners when notification is removed
// const cleanup = () => {
// document.removeEventListener('mousemove', onGuLuLuMove);
// document.removeEventListener('mouseup', onGuLuLuMove);
// window.removeEventListener('resize', updatePosition);
// };
// // Typewriter effect
// try {
// marked.setOptions({ breaks: true, gfm: true });
// if (!message || typeof message !== "string" || message.trim() === "") {
// contentDiv.textContent = "No message to display";
// updateHeights();
// return div;
// }
// const chars = message.split('');
// let charIndex = 0;
// let currentMarkdown = '';
// let renderedHtml = '';
// function typeWriter() {
// if (charIndex >= chars.length) {
// if (currentMarkdown.trim()) {
// renderedHtml += marked.parse(currentMarkdown);
// }
// contentDiv.innerHTML = renderedHtml;
// updateHeights();
// return;
// }
// currentMarkdown += chars[charIndex];
// const isNewline = chars[charIndex] === '\n';
// const isDoubleNewline = isNewline && charIndex > 0 && chars[charIndex - 1] === '\n';
// if (isDoubleNewline) {
// if (currentMarkdown.trim()) {
// renderedHtml += marked.parse(currentMarkdown);
// currentMarkdown = '';
// }
// contentDiv.innerHTML = renderedHtml;
// } else {
// contentDiv.innerHTML = renderedHtml + `<div>${currentMarkdown}</div>`;
// }
// charIndex++;
// setTimeout(typeWriter, 50);
// }
// setTimeout(typeWriter, 300);
// } catch (e) {
// console.error("Error in typewriter effect:", e);
// contentDiv.textContent = message || "Error displaying message";
// updateHeights();
// }
// setTimeout(() => div.style.opacity = "1", 10);
// if (timeout > 0) {
// setTimeout(() => {
// cleanup();
// hideNotification(div);
// }, timeout);
// }
// // Override hideNotification to include cleanup
// const originalHide = hideNotification.bind(null, div);
// div.hide = () => {
// cleanup();
// originalHide();
// };
// return div;
// }
// export function hideNotification(element) {
// if (element?.parentNode) {
// element.style.opacity = "0";
// setTimeout(() => element.parentNode?.removeChild(element), 300);
// }
// }
+15 -8
View File
@@ -134,6 +134,11 @@ const KayResourceMonitor = {
isEnabled: true,
isInitialized: false,
edgeFadeEnabled: true,
// 硬编码菜单边界
menuOffsets: {
left: 0, // 左侧菜单宽度
top: 0 // 顶部菜单高度
},
easeOutQuad(t) {
return t * (2 - t);
},
@@ -142,10 +147,11 @@ const KayResourceMonitor = {
this.isInitialized = true;
this.currentWorkflow.percentage = 0;
this.workflowProgress.currentNodePercentage = 0;
this.isEnabled = app.ui.settings.getSettingValue("KayTool.EnableResourceMonitor");
this.edgeFadeEnabled = app.ui.settings.getSettingValue("KayTool.ResourceMonitorEdgeFade");
this.loadVisibility();
this.startMonitoring();
this.bindEvents();
await this.loadSettings();
this.setupUI();
this.setupWorkflowListener();
this.setupWebSocketListener();
@@ -719,8 +725,10 @@ const KayResourceMonitor = {
},
loadPosition() {
const pos = JSON.parse(localStorage.getItem('KayResourceMonitorPosition')) || {};
this.position.left = pos.left || 0;
this.position.top = pos.top || 0;
const leftOffset = this.menuOffsets.left + 32; // 左侧菜单 + 20px
const topOffset = window.innerHeight - this.size.height - 20; // 底部 20px
this.position.left = pos.left != null ? pos.left : leftOffset;
this.position.top = pos.top != null ? pos.top : topOffset;
this.toolbar.style.left = `${this.position.left}px`;
this.toolbar.style.top = `${this.position.top}px`;
},
@@ -739,8 +747,8 @@ const KayResourceMonitor = {
if (!this.toolbar) return;
const winW = window.innerWidth;
const winH = window.innerHeight;
this.position.left = Math.max(0, Math.min(this.position.left, winW - this.size.width));
this.position.top = Math.max(0, Math.min(this.position.top, winH - this.size.height));
this.position.left = Math.max(this.menuOffsets.left, Math.min(this.position.left, winW - this.size.width));
this.position.top = Math.max(this.menuOffsets.top, Math.min(this.position.top, winH - this.size.height));
this.toolbar.style.left = `${this.position.left}px`;
this.toolbar.style.top = `${this.position.top}px`;
this.chartCanvas.width = this.toolbar.clientWidth - 20;
@@ -762,8 +770,8 @@ const KayResourceMonitor = {
if (!this.dragState.isDragging) return;
const winW = window.innerWidth;
const winH = window.innerHeight;
this.position.left = Math.max(0, Math.min(e.clientX - this.dragState.offsetX, winW - this.size.width));
this.position.top = Math.max(0, Math.min(e.clientY - this.dragState.offsetY, winH - this.size.height));
this.position.left = Math.max(this.menuOffsets.left, Math.min(e.clientX - this.dragState.offsetX, winW - this.size.width));
this.position.top = Math.max(this.menuOffsets.top, Math.min(e.clientY - this.dragState.offsetY, winH - this.size.height));
this.toolbar.style.left = `${this.position.left}px`;
this.toolbar.style.top = `${this.position.top}px`;
},
@@ -822,7 +830,6 @@ app.registerExtension({
if (app.menu?.settingsGroup) {
app.menu.settingsGroup.append(showMenuButton);
}
KayResourceMonitor.updateEnabledState(KayResourceMonitor.isEnabled);
window.KayResourceMonitor = KayResourceMonitor;
}
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

+74 -85
View File
@@ -3,6 +3,19 @@ import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "KayTool.Settings",
async setup() {
function updateSetGetNodeColors() {
const fgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
const bgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
const allNodes = app.graph._nodes;
allNodes.forEach(node => {
if (node.type === "KaySetNode" || node.type === "KayGetNode") {
node.color = fgColor;
node.bgcolor = bgColor;
}
});
app.graph.setDirtyCanvas(true);
}
const settings = [
{
id: "KayTool.ShowRunOption",
@@ -17,7 +30,6 @@ app.registerExtension({
type: "boolean",
defaultValue: true,
category: ["KayTool", "🛜 Set/Get", "ShowSetGetOptions"],
tooltip: "Toggles visibility of 'Set/Get' options in node context menus."
},
{
id: "KayTool.ShiftR",
@@ -41,7 +53,7 @@ app.registerExtension({
category: ["KayTool", "Menu", "ShowStarToMe"],
},
{
id: "KayTool.ShowCleanVRAM",
id: "KayTool.ShowCleanVRAM",
name: "Show '🧹 Clean VRAM' option in KayTool menu",
type: "boolean",
defaultValue: true,
@@ -54,19 +66,15 @@ app.registerExtension({
defaultValue: 100,
attrs: { min: 0, max: 200, step: 10 },
category: ["KayTool", "Workflow PNG", "MarginSize"],
tooltip: "Default value: 100."
},
{
id: "KayTool.SetGetForegroundColor",
name: "Set/Get Node Foreground Color",
type: "color",
defaultValue: "000000",
category: ["KayTool", "🛜 Set/Get", "ForegroundColor"],
category: ["KayTool", "🛜 Set/Get", "SetGetForegroundColor"],
onChange: (newVal) => {
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
updateSetGetNodeColors();
if (app.graph) app.graph.setDirtyCanvas(true, true);
}
updateSetGetNodeColors();
}
},
{
@@ -74,12 +82,9 @@ app.registerExtension({
name: "Set/Get Node Background Color",
type: "color",
defaultValue: "000000",
category: ["KayTool", "🛜 Set/Get", "BackgroundColor"],
category: ["KayTool", "🛜 Set/Get", "SetGetBackgroundColor"],
onChange: (newVal) => {
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
updateSetGetNodeColors();
if (app.graph) app.graph.setDirtyCanvas(true, true);
}
updateSetGetNodeColors();
}
},
{
@@ -94,14 +99,9 @@ app.registerExtension({
defaultValue: "permanent",
category: ["KayTool", "NodeAlignment", "DisplayMode"],
onChange: (value) => {
const manager = window.KayNodeAlignmentManager;
const initFn = window.initializeKayNodeAlignment;
if (!manager || !initFn) return;
if (!manager.isInitialized) {
initFn();
if (window.KayNodeAlignmentManager) {
window.KayNodeAlignmentManager.updateDisplayMode(value);
}
manager.updateDisplayMode(value);
}
},
{
@@ -116,19 +116,15 @@ app.registerExtension({
id: "KayTool.NodeAlignBackgroundOpacity",
name: "Node Alignment Toolbar Background Opacity",
type: "slider",
defaultValue: 100,
defaultValue: 0,
attrs: { min: 0, max: 100, step: 1 },
category: ["KayTool", "NodeAlignment", "BackgroundOpacity"],
onChange: (newVal) => {
const toolbar = document.getElementById('kay-node-alignment-toolbar');
if (toolbar) {
const bgColor = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundColor");
const bgColor = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundColor") || "000000";
const opacity = newVal / 100;
if (opacity > 0 && /^[0-9A-Fa-f]{6}$/.test(bgColor)) {
toolbar.style.background = `rgba(${parseInt(bgColor.substr(0, 2), 16)}, ${parseInt(bgColor.substr(2, 2), 16)}, ${parseInt(bgColor.substr(4, 2), 16)}, ${opacity})`;
} else {
toolbar.style.background = '';
}
toolbar.style.background = `rgba(${parseInt(bgColor.substr(0, 2), 16)}, ${parseInt(bgColor.substr(2, 2), 16)}, ${parseInt(bgColor.substr(4, 2), 16)}, ${opacity})`;
}
}
},
@@ -140,13 +136,9 @@ app.registerExtension({
category: ["KayTool", "NodeAlignment", "BackgroundColor"],
onChange: (newVal) => {
const toolbar = document.getElementById('kay-node-alignment-toolbar');
if (toolbar && /^[0-9A-Fa-f]{6}$/.test(newVal)) {
if (toolbar && newVal) {
const opacity = app.ui.settings.getSettingValue("KayTool.NodeAlignBackgroundOpacity") / 100;
if (opacity > 0) {
toolbar.style.background = `rgba(${parseInt(newVal.substr(0, 2), 16)}, ${parseInt(newVal.substr(2, 2), 16)}, ${parseInt(newVal.substr(4, 2), 16)}, ${opacity})`;
} else {
toolbar.style.background = '';
}
toolbar.style.background = `rgba(${parseInt(newVal.substr(0, 2), 16)}, ${parseInt(newVal.substr(2, 2), 16)}, ${parseInt(newVal.substr(4, 2), 16)}, ${opacity})`;
}
}
},
@@ -154,43 +146,37 @@ app.registerExtension({
id: "KayTool.NodeAlignIconBackgroundColor",
name: "Node Alignment Icon Background Color",
type: "color",
defaultValue: "2b2b2b",
defaultValue: "363636",
category: ["KayTool", "NodeAlignment", "IconBackgroundColor"],
tooltip: "Use parameter “363636” to match ComfyUI menu bar color for an icon-free background effect",
onChange: (newVal) => {
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
const buttons = document.querySelectorAll('.kay-align-button');
buttons.forEach(btn => btn.style.backgroundColor = `#${newVal}`);
}
const buttons = document.querySelectorAll('.kay-align-button');
buttons.forEach(btn => btn.style.backgroundColor = `#${newVal}`);
}
},
{
id: "KayTool.NodeAlignIconColor",
name: "Node Alignment Icon Color",
type: "color",
defaultValue: "666666",
defaultValue: "999999",
category: ["KayTool", "NodeAlignment", "IconColor"],
onChange: (newVal) => {
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
const buttons = document.querySelectorAll('.kay-align-button');
buttons.forEach(btn => {
const svg = btn.querySelector('svg');
if (svg) svg.querySelectorAll('path').forEach(path => path.setAttribute('fill', `#${newVal}`));
});
}
const buttons = document.querySelectorAll('.kay-align-button');
buttons.forEach(btn => {
const svg = btn.querySelector('svg');
if (svg) svg.querySelectorAll('path').forEach(path => path.setAttribute('fill', `#${newVal}`));
});
}
},
{
id: "KayTool.NodeAlignDividerColor",
name: "Node Alignment Divider Color",
type: "color",
defaultValue: "1e1e1e",
defaultValue: "f0ff00",
category: ["KayTool", "NodeAlignment", "DividerColor"],
onChange: (newVal) => {
if (/^[0-9A-Fa-f]{6}$/.test(newVal)) {
const dividers = document.querySelectorAll('.kay-toolbar-divider');
dividers.forEach(divider => divider.style.background = `#${newVal}`);
}
const dividers = document.querySelectorAll('.kay-toolbar-divider');
dividers.forEach(divider => divider.style.background = `#${newVal}`);
}
},
{
@@ -214,9 +200,7 @@ app.registerExtension({
onChange: (newVal) => {
if (window.KayResourceMonitor) {
window.KayResourceMonitor.edgeFadeEnabled = newVal;
if (window.KayResourceMonitor.isVisible && window.KayResourceMonitor.isEnabled) {
window.KayResourceMonitor.drawCurves();
}
window.KayResourceMonitor.drawCurves();
}
}
},
@@ -224,7 +208,7 @@ app.registerExtension({
id: "KayTool.ResourceMonitorBackgroundOpacity",
name: "Resource Monitor Background Opacity",
type: "slider",
defaultValue: 80,
defaultValue: 50,
attrs: { min: 0, max: 100, step: 1 },
category: ["KayTool", "Resource Monitor", "BackgroundOpacity"],
onChange: (newVal) => {
@@ -233,38 +217,43 @@ app.registerExtension({
window.KayResourceMonitor.toolbar.style.background = `rgba(0, 0, 0, ${opacity})`;
}
}
},
{
id: "KayTool.GuLuLuSize",
name: "GuLuLu Size (px)",
type: "slider",
defaultValue: 39,
attrs: { min: 36, max: 120, step: 1 },
category: ["KayTool", "GuLuLu", "Size"],
onChange: (newVal) => {
if (window.KayGuLuLuManager) {
window.KayGuLuLuManager.updateSize(newVal);
}
}
},
{
id: "KayTool.EnableGuLuLu",
name: "GuLuLu stay with you!",
tooltip: "GuLuLu will inherit KayTool's notification items for streaming output, support adjustments to the notification box position, and include powerful right-click menu options.",
type: "boolean",
defaultValue: true,
category: ["KayTool", "GuLuLu", "EnableGuLuLu"],
onChange: (newVal) => {
if (window.KayGuLuLuManager) {
window.KayGuLuLuManager.updateEnabledState(newVal);
}
}
}
];
for (const setting of settings) {
app.ui.settings.addSetting(setting);
}
},
init() {
if (!app.graph) {
const checkGraph = setInterval(() => {
if (app.graph) {
clearInterval(checkGraph);
updateSetGetNodeColors();
}
}, 100);
} else {
updateSetGetNodeColors();
}
}
});
function updateSetGetNodeColors() {
const fgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetForegroundColor");
const bgColor = "#" + app.ui.settings.getSettingValue("KayTool.SetGetBackgroundColor");
if (app.graph && app.graph._nodes) {
app.graph._nodes.forEach(node => {
if (node.type === "KaySetNode" || node.type === "KayGetNode" || node.type === "KayGet多元Node") {
node.color = fgColor;
node.bgcolor = bgColor;
node.updateColors();
try {
for (const setting of settings) {
app.ui.settings.addSetting(setting);
}
});
app.graph.setDirtyCanvas(true, true);
updateSetGetNodeColors();
} catch (error) {
console.error("Error in KayTool.Settings setup:", error);
throw error;
}
}
}
});
+24 -24
View File
@@ -1,44 +1,44 @@
import { app } from "../../../scripts/app.js";
// 全局动作注册表
const KayToolActions = window.KayToolActions || {};
window.KayToolActions = KayToolActions;
function starToMe() {
window.open("https://github.com/kk8bit/kaytool", "_blank");
}
// 注册 StarToMe 动作
KayToolActions.starToMe = starToMe;
app.registerExtension({
name: "KayTool.StarToMe",
setup() {
const originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = originalGetCanvasMenuOptions.apply(this, arguments) || [];
const showStarToMe = app.ui.settings.getSettingValue("KayTool.ShowStarToMe");
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
const options = originalGetCanvasMenuOptions.apply(this, args) || [];
const newOptions = [...options];
const showStarToMe = app.ui.settings.getSettingValue("KayTool.ShowStarToMe") ?? true;
if (showStarToMe) {
const newOptions = [...options];
let kaytoolMenu = newOptions.find(opt => opt && opt.content === "KayTool");
let kaytoolMenu = newOptions.find(opt => opt?.content === "KayTool") || {
content: "KayTool",
submenu: { options: [] }
};
if (!kaytoolMenu) {
kaytoolMenu = {
content: "KayTool",
submenu: {
options: []
}
};
newOptions.push(null, kaytoolMenu);
if (!newOptions.includes(kaytoolMenu)) {
newOptions.push(null, kaytoolMenu);
}
kaytoolMenu.submenu.options = kaytoolMenu.submenu.options || [];
const starOptionExists = kaytoolMenu.submenu.options.some(
opt => opt && opt.content === "⭐ Star to me"
);
if (!starOptionExists) {
if (!kaytoolMenu.submenu.options.some(opt => opt?.content === "⭐ Star to me")) {
kaytoolMenu.submenu.options.push({
content: "⭐ Star to me",
callback: () => {
window.open("https://github.com/kk8bit/kaytool", "_blank");
}
callback: starToMe
});
}
return newOptions;
}
return options;
return newOptions;
};
}
});
+31 -26
View File
@@ -1,12 +1,16 @@
import { app } from "../../../scripts/app.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
import { showNotification, hideNotification } from "./notification.js";
import { showNotification, hideNotification } from "./gululu.js";
// 全局动作注册表
const KayToolActions = window.KayToolActions || {};
window.KayToolActions = KayToolActions;
class KayWorkflowImage {
extension = "png";
getBounds() {
const marginSize = app.ui.settings.getSettingValue("KayTool.WorkflowPNG");
const marginSize = app.ui.settings.getSettingValue("KayTool.WorkflowPNG") || 50;
const bounds = app.graph._nodes.reduce(
(p, node) => {
if (node.pos[0] < p[0]) p[0] = node.pos[0];
@@ -33,7 +37,7 @@ class KayWorkflowImage {
width: app.canvas.canvas.width,
height: app.canvas.canvas.height,
offset: app.canvas.ds.offset,
transform: app.canvas.canvas.getContext('2d').getTransform(),
transform: app.canvas.canvas.getContext("2d").getTransform(),
};
}
@@ -157,6 +161,19 @@ class KayWorkflowImage {
}
}
function exportWorkflowPNG() {
showNotification({
message: `GuLuLu: 你需要把工作流信息嵌入到PNG中吗?啊?Do you need to embed Workflow information into PNG? *GuLuLu~Gulu*`,
bgColor: "#fff3cd",
size: "medium",
onYes: () => new KayWorkflowImage().export(true),
onNo: () => new KayWorkflowImage().export(false)
});
}
// 注册 Export Workflow PNG 动作
KayToolActions.exportWorkflowPNG = exportWorkflowPNG;
app.registerExtension({
name: "KayTool.WorkflowPNG",
init() {
@@ -236,37 +253,25 @@ app.registerExtension({
},
setup() {
const originalGetCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = originalGetCanvasMenuOptions.apply(this, arguments) || [];
LGraphCanvas.prototype.getCanvasMenuOptions = function (...args) {
const options = originalGetCanvasMenuOptions.apply(this, args) || [];
const newOptions = [...options];
const showWorkflowPNG = app.ui.settings.getSettingValue("KayTool.ShowWorkflowPNG");
const showWorkflowPNG = app.ui.settings.getSettingValue("KayTool.ShowWorkflowPNG") ?? true;
if (showWorkflowPNG) {
let kaytoolMenu = newOptions.find(opt => opt && opt.content === "KayTool");
if (!kaytoolMenu) {
kaytoolMenu = {
content: "KayTool",
submenu: { options: [] }
};
let kaytoolMenu = newOptions.find(opt => opt?.content === "KayTool") || {
content: "KayTool",
submenu: { options: [] }
};
if (!newOptions.includes(kaytoolMenu)) {
newOptions.push(null, kaytoolMenu);
}
kaytoolMenu.submenu.options = kaytoolMenu.submenu.options || [];
const exportOptionExists = kaytoolMenu.submenu.options.some(
opt => opt && opt.content === "📦 Workflow PNG"
);
if (!exportOptionExists) {
if (!kaytoolMenu.submenu.options.some(opt => opt?.content === "📦 Workflow PNG")) {
kaytoolMenu.submenu.options.push({
content: "📦 Workflow PNG",
callback: () => {
showNotification({
message: `𝙆:你需要把工作流信息嵌入到PNG中吗?Do you need to embed Workflow information into PNG?`,
bgColor: "#fff3cd",
size: "medium",
onYes: () => new KayWorkflowImage().export(true),
onNo: () => new KayWorkflowImage().export(false)
});
}
callback: exportWorkflowPNG
});
}
}