diff --git a/web/seed_history_ui.js b/web/seed_history_ui.js new file mode 100644 index 0000000..5d2cf6d --- /dev/null +++ b/web/seed_history_ui.js @@ -0,0 +1,524 @@ +// ComfyUI-KikoTools - Seed History with Tracking UI +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "comfyassets.SeedHistory", + + async setup() { + // Store reference to all SeedHistory nodes + window.seedHistoryNodes = window.seedHistoryNodes || []; + }, + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "SeedHistory") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + + nodeType.prototype.onNodeCreated = function () { + if (onNodeCreated) { + onNodeCreated.apply(this, arguments); + } + + // Initialize seed history + this.seedHistory = this.loadSeedHistory(); + this.hideTimer = null; + this.mouseOverHistory = false; + + // Register this node in global registry + window.seedHistoryNodes = window.seedHistoryNodes || []; + window.seedHistoryNodes.push(this); + + // Create UI container + const uiContainer = document.createElement("div"); + uiContainer.style.padding = "8px"; + uiContainer.style.backgroundColor = "#1e1e1e"; + uiContainer.style.borderRadius = "6px"; + uiContainer.style.marginTop = "6px"; + uiContainer.style.border = "1px solid #404040"; + + this.buildSeedInterface(uiContainer); + + // Add as widget + this.seedWidget = this.addDOMWidget( + "seed_history_ui", + "div", + uiContainer, + ); + + // Set node size + if (!this.hasBeenResized) { + this.size = [280, 320]; + } + + // Track manual resizing + const originalResize = this.onResize; + this.onResize = function (size) { + this.hasBeenResized = true; + if (originalResize) { + originalResize.call(this, size); + } + }; + + // Hook into seed widget callbacks to track all changes + setTimeout(() => { + this.setupSeedWidgetCallbacks(); + }, 100); + + // Hook directly into widget value changes + const originalOnWidgetChange = this.onWidgetChange; + this.onWidgetChange = function(name, value, oldValue, widget) { + if (name === "seed" && value !== oldValue) { + this.addSeedToHistory(value); + } + + if (originalOnWidgetChange) { + return originalOnWidgetChange.call(this, name, value, oldValue, widget); + } + }; + + // Save/load data + const originalSerialize = this.serialize; + this.serialize = function () { + const data = originalSerialize ? originalSerialize.call(this) : {}; + data.hasBeenResized = this.hasBeenResized; + data.seedHistory = this.seedHistory; + return data; + }; + + const originalConfigure = this.configure; + this.configure = function (data) { + if (originalConfigure) { + originalConfigure.call(this, data); + } + if (data.hasBeenResized) { + this.hasBeenResized = data.hasBeenResized; + } + if (data.seedHistory) { + this.seedHistory = data.seedHistory; + this.refreshHistoryDisplay(); + } + }; + + // Cleanup interval on node removal + const originalOnRemoved = this.onRemoved; + this.onRemoved = function () { + if (this.seedValueWatcher) { + clearInterval(this.seedValueWatcher); + this.seedValueWatcher = null; + } + + // Clean up deduplication tracking + if (this.lastAddedSeed) { + this.lastAddedSeed = null; + } + + // Remove from global registry + if (window.seedHistoryNodes) { + const index = window.seedHistoryNodes.indexOf(this); + if (index !== -1) { + window.seedHistoryNodes.splice(index, 1); + } + } + + if (originalOnRemoved) { + originalOnRemoved.call(this); + } + }; + + this.setDirtyCanvas(true, true); + }; + + // Setup seed widget callbacks to track increment/decrement/randomize + nodeType.prototype.setupSeedWidgetCallbacks = function () { + // Find the seed widget + const seedWidget = this.widgets?.find(w => w.name === "seed"); + if (!seedWidget) { + setTimeout(() => this.setupSeedWidgetCallbacks(), 500); + return; + } + + // Store the last known seed value to detect changes + this.lastSeedValue = seedWidget.value; + + // Monitor for value changes that might not trigger callback + this.seedValueWatcher = setInterval(() => { + if (seedWidget.value !== this.lastSeedValue) { + this.lastSeedValue = seedWidget.value; + this.addSeedToHistory(seedWidget.value); + } + }, 1000); + }; + + // Build the seed interface + nodeType.prototype.buildSeedInterface = function (container) { + container.innerHTML = ""; + + // Header + const headerDiv = document.createElement("div"); + headerDiv.style.marginBottom = "8px"; + + const titleDiv = document.createElement("div"); + titleDiv.style.fontWeight = "bold"; + titleDiv.style.color = "#00d4ff"; + titleDiv.style.textAlign = "center"; + titleDiv.style.padding = "4px"; + titleDiv.style.backgroundColor = "rgba(0, 212, 255, 0.1)"; + titleDiv.style.borderRadius = "4px"; + titleDiv.style.fontSize = "11px"; + titleDiv.innerHTML = "🎲 Seed History"; + headerDiv.appendChild(titleDiv); + + // Action buttons + const buttonDiv = document.createElement("div"); + buttonDiv.style.display = "flex"; + buttonDiv.style.gap = "4px"; + buttonDiv.style.marginTop = "6px"; + + // Generate button + const generateBtn = document.createElement("button"); + generateBtn.textContent = "🎲 Generate"; + generateBtn.style.flex = "1"; + generateBtn.style.padding = "3px 6px"; + generateBtn.style.backgroundColor = "#0088cc"; + generateBtn.style.color = "white"; + generateBtn.style.border = "none"; + generateBtn.style.borderRadius = "3px"; + generateBtn.style.cursor = "pointer"; + generateBtn.style.fontSize = "10px"; + generateBtn.addEventListener("click", () => this.generateRandomSeed()); + buttonDiv.appendChild(generateBtn); + + // Clear button + const clearBtn = document.createElement("button"); + clearBtn.textContent = "🗑️ Clear"; + clearBtn.style.flex = "1"; + clearBtn.style.padding = "3px 6px"; + clearBtn.style.backgroundColor = "#cc4444"; + clearBtn.style.color = "white"; + clearBtn.style.border = "none"; + clearBtn.style.borderRadius = "3px"; + clearBtn.style.cursor = "pointer"; + clearBtn.style.fontSize = "10px"; + clearBtn.addEventListener("click", () => this.clearSeedHistory()); + buttonDiv.appendChild(clearBtn); + + headerDiv.appendChild(buttonDiv); + container.appendChild(headerDiv); + + // History display + const historyDiv = document.createElement("div"); + historyDiv.style.maxHeight = "180px"; + historyDiv.style.overflowY = "auto"; + historyDiv.style.border = "1px solid #333"; + historyDiv.style.borderRadius = "4px"; + historyDiv.style.backgroundColor = "#2a2a2a"; + historyDiv.style.padding = "6px"; + historyDiv.style.fontSize = "10px"; + historyDiv.style.fontFamily = "monospace"; + + // Mouse events for auto-hide + historyDiv.addEventListener("mouseenter", () => { + this.mouseOverHistory = true; + this.cancelAutoHide(); + }); + + historyDiv.addEventListener("mouseleave", () => { + this.mouseOverHistory = false; + this.startAutoHide(); + }); + + this.historyDisplay = historyDiv; + container.appendChild(historyDiv); + + this.refreshHistoryDisplay(); + }; + + // Load history from storage + nodeType.prototype.loadSeedHistory = function () { + try { + const stored = localStorage.getItem('comfyui_kikotools_seed_history'); + return stored ? JSON.parse(stored) : []; + } catch (error) { + return []; + } + }; + + // Save history to storage + nodeType.prototype.saveSeedHistory = function () { + try { + localStorage.setItem('comfyui_kikotools_seed_history', JSON.stringify(this.seedHistory)); + } catch (error) { + // Silently handle storage errors + } + }; + + // Add seed to history + nodeType.prototype.addSeedToHistory = function (seed) { + if (!seed || seed === 0) return; + + const numSeed = typeof seed === 'string' ? parseInt(seed) : seed; + const now = Date.now(); + + // Deduplication: prevent adding the same seed within 500ms window + if (!this.lastAddedSeed) { + this.lastAddedSeed = { seed: null, timestamp: 0 }; + } + + const timeSinceLastAdd = now - this.lastAddedSeed.timestamp; + const isSameSeed = this.lastAddedSeed.seed === numSeed; + const isWithinDupeWindow = timeSinceLastAdd < 500; // 500ms window + + if (isSameSeed && isWithinDupeWindow) { + return; + } + + // Update deduplication tracking + this.lastAddedSeed = { seed: numSeed, timestamp: now }; + + // Remove if already exists in history + this.seedHistory = this.seedHistory.filter(item => item.seed !== numSeed); + + // Add to front + this.seedHistory.unshift({ + seed: numSeed, + timestamp: now, + dateString: new Date().toLocaleString() + }); + + // Keep only last 10 + if (this.seedHistory.length > 10) { + this.seedHistory = this.seedHistory.slice(0, 10); + } + + this.saveSeedHistory(); + this.refreshHistoryDisplay(); + this.startAutoHide(); + }; + + // Generate new random seed + nodeType.prototype.generateRandomSeed = function () { + const newSeed = Math.floor(Math.random() * 0xFFFFFFFFFFFFFFFF); + + const seedWidget = this.widgets?.find(w => w.name === "seed"); + if (seedWidget) { + seedWidget.value = newSeed; + if (seedWidget.callback) { + seedWidget.callback(newSeed, this, seedWidget); + } + } + + this.addSeedToHistory(newSeed); + this.setDirtyCanvas(true, true); + this.showMessage(`Generated: ${newSeed}`, "success"); + }; + + // Use seed from history + nodeType.prototype.useSeedFromHistory = function (historyItem, index) { + const seedWidget = this.widgets?.find(w => w.name === "seed"); + if (seedWidget) { + seedWidget.value = historyItem.seed; + if (seedWidget.callback) { + seedWidget.callback(historyItem.seed, this, seedWidget); + } + } + + this.highlightHistoryEntry(index); + this.setDirtyCanvas(true, true); + this.startAutoHide(); + this.showMessage(`Loaded: ${historyItem.seed}`, "info"); + }; + + // Clear history + nodeType.prototype.clearSeedHistory = function () { + this.seedHistory = []; + this.saveSeedHistory(); + this.refreshHistoryDisplay(); + this.showMessage("History cleared", "info"); + }; + + // Refresh history display + nodeType.prototype.refreshHistoryDisplay = function () { + if (!this.historyDisplay) return; + + if (!this.seedHistory || this.seedHistory.length === 0) { + this.historyDisplay.innerHTML = + '