import { app } from "../../../scripts/app.js"; app.registerExtension({ name: "Mudknight Utils.NodeProfiles", async setup() { app.ui.settings.addSetting({ id: "Mudknight Utils.NodeProfiles.ShowButton", name: "Show Node Profiles Button", type: "boolean", defaultValue: false, tooltip: "Show/hide the Node Profiles button in the toolbar" }); const style = document.createElement('style'); style.textContent = ` .profile-modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 10000; align-items: center; justify-content: center; } .profile-modal.show { display: flex; } .profile-modal-content { background: var(--comfy-menu-bg); border: 2px solid var(--border-color); border-radius: 10px; padding: 20px; max-width: 500px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); } #profileList { display: flex; flex-direction: column; gap: 10px; } .profile-node-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; } .node-tag-container { display: flex; flex-direction: column; background: var(--comfy-menu-bg); border: 1px solid var(--border-color); padding: 4px 8px; border-radius: 4px; font-size: 11px; } .node-tag-type { font-weight: bold; color: var(--fg-color); } .node-tag-model { color: var(--descrip-text); font-size: 10px; border-top: 1px solid var(--border-color); margin-top: 2px; padding-top: 2px; } .profile-modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid var(--border-color); } .profile-close-btn { background: none; border: none; font-size: 24px; cursor: pointer; color: var(--fg-color); } .profile-item { display: flex; align-items: center; gap: 10px; padding: 12px; background: var(--comfy-input-bg); border: 1px solid var(--border-color); border-radius: 4px; } .profile-name { flex: 1; font-weight: 500; color: var(--fg-color); cursor: pointer; padding: 4px 8px; border-radius: 4px; } .profile-input-field { border-radius: 5px; } .profile-btn { padding: 6px 12px; border: 1px solid var(--border-color); border-radius: 4px; background: var(--comfy-menu-bg); cursor: pointer; font-size: 12px; color: var(--fg-color); } .profile-btn.delete { color: var(--error-text); } `; document.head.appendChild(style); const modal = document.createElement('div'); modal.className = 'profile-modal'; modal.id = 'profileModal'; modal.innerHTML = `
`; document.body.appendChild(modal); const WHITELIST = [ "BaseNode", "UpscaleNode", "DetailerNode", "DetailerPipeNode", "NestedDetailerNode", "NestedDetailerPipeNode"]; const formatModelName = (path) => { if (!path) return ""; const filename = path.split('/').pop(); return filename.split('.')[0]; }; window.saveNodeParams = async (name) => { const profileData = {}; for (const node of app.graph._nodes) { if (!node.widgets || !WHITELIST.includes(node.type)) continue; let identifier = node.type; let modelName = null; if (node.type.includes("Detailer")) { const bbox = node.widgets.find(w => w.name === "bbox_model"); if (bbox?.value) { modelName = bbox.value; identifier = `${node.type}:${modelName}`; } } profileData[identifier] = { type: node.type, model: modelName, widgets: node.widgets.map(w => ({ name: w.name, value: w.value })) }; } await fetch(`/node_profiles/${encodeURIComponent(name)}`, { method: 'POST', body: JSON.stringify(profileData) }); }; window.restoreNodeParams = async (name) => { const response = await fetch(`/node_profiles/${encodeURIComponent(name)}`); if (!response.ok) return; const profile = await response.json(); for (const [id, data] of Object.entries(profile)) { let target = null; if (data.model) { target = app.graph._nodes.find(n => n.type === data.type && n.widgets?.find(w => w.name === "bbox_model")?.value === data.model ); } else { target = app.graph._nodes.find(n => n.type === data.type); } if (target) { data.widgets.forEach(sw => { const w = target.widgets.find(nodeW => nodeW.name === sw.name); if (w) { w.value = sw.value; if (w.callback) w.callback(w.value, app.canvas, target); } }); } } app.graph.setDirtyCanvas(true, true); }; const renderProfiles = async () => { const list = document.getElementById('profileList'); const response = await fetch('/node_profiles'); const profiles = await response.json(); list.innerHTML = ''; Object.keys(profiles).sort().forEach(name => { const item = document.createElement('div'); item.className = 'profile-item'; const tags = Object.entries(profiles[name]).map(([id, data]) => { // Strip "Node" from the display name const typeLabel = data.type.replace("Node", ""); const modelLabel = data.model ? formatModelName(data.model) : null; return `