diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34606a0..04e64db 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index 877c257..342f004 100644
--- a/README.md
+++ b/README.md
@@ -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格式并内嵌工作流信息。
diff --git a/locales/zh/main.json b/locales/zh/main.json
index 80193ff..99bc3bf 100644
--- a/locales/zh/main.json
+++ b/locales/zh/main.json
@@ -6,7 +6,8 @@
"Custom Web Logo (BETA)": "自定义网页徽标",
"Workflow PNG": "Workflow PNG",
"NodeAlignment": "节点对齐工具栏",
- "Resource Monitor": "资源监视器"
+ "Resource Monitor": "资源监视器",
+ "GuLuLu": "咕噜噜"
},
"nodeCategories": {
"Image": "图像",
diff --git a/locales/zh/settings.json b/locales/zh/settings.json
index 1c1d96c..a40edd6 100644
--- a/locales/zh/settings.json
+++ b/locales/zh/settings.json
@@ -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 对应上、左、下、右对齐"
diff --git a/pyproject.toml b/pyproject.toml
index 5621b50..e55e43c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -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"]
diff --git a/web/cleanvram.js b/web/cleanvram.js
index 8ab83d7..a875476 100644
--- a/web/cleanvram.js
+++ b/web/cleanvram.js
@@ -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
});
}
}
diff --git a/web/getset.js b/web/getset.js
index 7a62bbe..dfefb07 100644
--- a/web/getset.js
+++ b/web/getset.js
@@ -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";
- },
+ }
});
\ No newline at end of file
diff --git a/web/gululu.js b/web/gululu.js
new file mode 100644
index 0000000..585d300
--- /dev/null
+++ b/web/gululu.js
@@ -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",
+ ``
+ );
+ },
+
+ 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 + `
${currentMarkdown}
`;
+ }
+
+ 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;
+ },
+});
\ No newline at end of file
diff --git a/web/notification.js b/web/notification.js
index 9fa4dcf..f9059fb 100644
--- a/web/notification.js
+++ b/web/notification.js
@@ -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 + `${currentMarkdown}
`;
- }
+// // 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);
- }
-}
\ No newline at end of file
+// // 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 + `${currentMarkdown}
`;
+// }
+
+// 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);
+// }
+// }
\ No newline at end of file
diff --git a/web/resourcemonitor.js b/web/resourcemonitor.js
index c014229..18ab55f 100644
--- a/web/resourcemonitor.js
+++ b/web/resourcemonitor.js
@@ -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;
}
});
\ No newline at end of file
diff --git a/web/resources/GuLuLu.gif b/web/resources/GuLuLu.gif
new file mode 100644
index 0000000..3b26a2f
Binary files /dev/null and b/web/resources/GuLuLu.gif differ
diff --git a/web/settings.js b/web/settings.js
index eaced8d..e0744b1 100644
--- a/web/settings.js
+++ b/web/settings.js
@@ -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;
+ }
}
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/web/startome.js b/web/startome.js
index bab65a5..946a63c 100644
--- a/web/startome.js
+++ b/web/startome.js
@@ -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;
};
}
});
\ No newline at end of file
diff --git a/web/workflowpng.js b/web/workflowpng.js
index 72e49a4..b07c30d 100644
--- a/web/workflowpng.js
+++ b/web/workflowpng.js
@@ -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
});
}
}