diff --git a/README.md b/README.md index 5852df2..7787bd2 100644 --- a/README.md +++ b/README.md @@ -223,9 +223,17 @@ NODE_CLASS_MAPPINGS.update({ * `` Ensure that the timestamp is always unique. * "components" should have the same structure as the content of the file stored in ComfyUI-Manager/components. * ``: The name should be in the format `::`. - * ``: In the nodedata of the group node. - - + * ``: In the nodedata of the group node. + * ``: Only two formats are allowed: `major.minor.patch` or `major.minor`. (e.g. `1.0`, `2.2.1`) + * ``: Saved time + * ``: If the packname is not empty, the category becomes packname/workflow, and it is saved in the .pack file in ComfyUI-Manager/components. + * ``: If there is neither a category nor a packname, it is saved in the components category. + ``` + "version":"1.0", + "datetime": 1705390656516, + "packname": "mypack", + "category": "util/pipe", + ``` ## Support of missing nodes installation diff --git a/__init__.py b/__init__.py index b58cab4..83cc099 100644 --- a/__init__.py +++ b/__init__.py @@ -1855,9 +1855,12 @@ async def save_component(request): if not os.path.exists(components_path): os.mkdir(components_path) - sanitized_name = sanitize_filename(name) + if 'packname' in workflow and workflow['packname'] != '': + sanitized_name = sanitize_filename(workflow['packname'])+'.pack' + else: + sanitized_name = sanitize_filename(name)+'.json' - filepath = os.path.join(components_path, sanitized_name+'.json') + filepath = os.path.join(components_path, sanitized_name) components = {} if os.path.exists(filepath): with open(filepath) as f: @@ -1876,12 +1879,14 @@ async def save_component(request): async def load_components(request): try: json_files = [f for f in os.listdir(components_path) if f.endswith('.json')] + pack_files = [f for f in os.listdir(components_path) if f.endswith('.pack')] components = {} - for json_file in json_files: + for json_file in json_files + pack_files: file_path = os.path.join(components_path, json_file) with open(file_path, 'r') as file: try: + # When there is a conflict between the .pack and the .json, the pack takes precedence and overrides. components.update(json.load(file)) except json.JSONDecodeError as e: print(f"[ComfyUI-Manager] Error decoding component file in file {json_file}: {e}") diff --git a/components/.gitignore b/components/.gitignore index a6c57f5..fab7a5e 100644 --- a/components/.gitignore +++ b/components/.gitignore @@ -1 +1,2 @@ *.json +*.pack diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 85f1781..fe1233d 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -16,7 +16,7 @@ import { AlternativesInstaller } from "./a1111-alter-downloader.js"; import { SnapshotManager } from "./snapshot.js"; import { ModelInstaller } from "./model-downloader.js"; import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models } from "./common.js"; -import { load_components, save_as_component } from "./components-manager.js"; +import { ComponentBuilderDialog, load_components } from "./components-manager.js"; var docStyle = document.createElement('style'); docStyle.innerHTML = ` @@ -27,6 +27,35 @@ docStyle.innerHTML = ` z-index: 10000; } +.cb-widget { + width: 400px; + height: 25px; + box-sizing: border-box; + z-index: 10000; + margin-top: 10px; + margin-bottom: 5px; +} + +.cb-widget-input { + width: 305px; + height: 25px; + box-sizing: border-box; +} +.cb-widget-input:disabled { + background-color: #444444; + color: white; +} + +.cb-widget-input-label { + width: 90px; + height: 25px; + box-sizing: border-box; + color: white; + text-align: right; + display: inline-block; + margin-right: 5px; +} + .cm-menu-container { column-gap: 20px; display: flex; @@ -251,6 +280,16 @@ const style = ` box-sizing: border-box; } +.cb-node-label { + width: 400px; + height:28px; + color: black; + background-color: #777777; + font-size: 18px; + text-align: center; + font-weight: bold; +} + #cm-close-button { width: calc(100% - 65px); bottom: 10px; @@ -258,6 +297,16 @@ const style = ` overflow: hidden; } +#cm-save-button { + width: calc(100% - 65px); + bottom:40px; + position: absolute; + overflow: hidden; +} +#cm-save-button:disabled { + background-color: #444444; +} + .pysssss-workflow-arrow-2 { position: absolute; top: 0; @@ -873,7 +922,7 @@ class ManagerMenuDialog extends ComfyDialog { }) ]), ]; -} + } createControlsRight() { const elts = [ @@ -1197,7 +1246,13 @@ app.registerExtension({ if (node.comfyClass.startsWith('workflow/')) { options.push({ content: "Save As Component", - callback: (obj) => { save_as_component(node, app); } + callback: (obj) => { + if (!ComponentBuilderDialog.instance) { + ComponentBuilderDialog.instance = new ComponentBuilderDialog(); + } + ComponentBuilderDialog.instance.target_node = node; + ComponentBuilderDialog.instance.show(); + } }, null); } diff --git a/js/common.js b/js/common.js index 8d0997d..a45bf75 100644 --- a/js/common.js +++ b/js/common.js @@ -48,8 +48,7 @@ export async function install_checked_custom_node(grid_rows, target_i, caller, m }); if(response.status == 400) { - app.ui.dialog.show(`${mode} failed: ${target.title}`); - app.ui.dialog.element.style.zIndex = 10010; + show_message(`${mode} failed: ${target.title}`); continue; } @@ -64,8 +63,7 @@ export async function install_checked_custom_node(grid_rows, target_i, caller, m } if(failed != '') { - app.ui.dialog.show(`${mode} failed: ${failed}`); - app.ui.dialog.element.style.zIndex = 10010; + show_message(`${mode} failed: ${failed}`); } await caller.invalidateControl(); @@ -160,4 +158,9 @@ export async function free_models() { app.ui.dialog.show('Unloading of models failed.

Installed ComfyUI may be an outdated version.') } app.ui.dialog.element.style.zIndex = 10010; +} + +export function show_message(msg) { + app.ui.dialog.show(msg); + app.ui.dialog.element.style.zIndex = 10010; } \ No newline at end of file diff --git a/js/components-manager.js b/js/components-manager.js index 8f90955..d8bd84f 100644 --- a/js/components-manager.js +++ b/js/components-manager.js @@ -1,7 +1,32 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" -import { sleep } from "./common.js"; +import { sleep, show_message } from "./common.js"; import { GroupNodeConfig, GroupNodeHandler } from "../../extensions/core/groupNode.js"; +import { ComfyDialog, $el } from "../../scripts/ui.js"; + +let pack_map = {}; +let rpack_map = {}; + +function isValidVersionString(version) { + const versionPattern = /^(\d+)\.(\d+)(\.(\d+))?$/; + + const match = version.match(versionPattern); + + return match !== null && + parseInt(match[1], 10) >= 0 && + parseInt(match[2], 10) >= 0 && + (!match[3] || parseInt(match[4], 10) >= 0); +} + +function register_pack_map(name, data) { + if(data.packname) { + pack_map[data.packname] = name; + rpack_map[name] = [data.packname, data.category, data.version, data.datetime]; + } + else { + rpack_map[name] = [data.packname, data.category, data.version, data.datetime]; + } +} function storeGroupNode(name, data) { let extra = app.graph.extra; @@ -9,6 +34,8 @@ function storeGroupNode(name, data) { let groupNodes = extra.groupNodes; if (!groupNodes) extra.groupNodes = groupNodes = {}; groupNodes[name] = data; + + register_pack_map(name, data); } export async function load_components() { @@ -21,6 +48,22 @@ export async function load_components() { for(let name in components) { if(app.graph.extra?.groupNodes?.[name]) { + if(data) { + let data = components[name]; + + let category = data.packname; + if(data.category) { + category += "/" + data.category; + } + if(category == '') { + category = 'components'; + } + + const config = new GroupNodeConfig(name, data); + await config.registerType(category); + + register_pack_map(name, data); + } continue; } @@ -32,7 +75,16 @@ export async function load_components() { while(!success) { var success = false; try { - await config.registerType(); + let category = nodeData.packname; + if(nodeData.category) { + category += "/" + nodeData.category; + } + if(category == '') { + category = 'components'; + } + + await config.registerType(category); + register_pack_map(name, nodeData); } catch { let elapsed_time = Date.now() - start_time; @@ -44,8 +96,6 @@ export async function load_components() { } } } - - const groupNode = LiteGraph.createNode(`workflow/${name}`); } // fallback1 @@ -64,7 +114,16 @@ export async function load_components() { while(!success) { var success = false; try { - await config.registerType(); + let category = nodeData.packname; + if(nodeData.workflow.category) { + category += "/" + nodeData.category; + } + if(category == '') { + category = 'components'; + } + + await config.registerType(category); + register_pack_map(name, nodeData); } catch { let elapsed_time = Date.now() - start_time; @@ -76,8 +135,6 @@ export async function load_components() { } } } - - const groupNode = LiteGraph.createNode(`workflow/${name}`); } // fallback2 @@ -91,8 +148,18 @@ export async function load_components() { const config = new GroupNodeConfig(name, nodeData); while(!success) { var success = false; + try { - await config.registerType(); + let category = nodeData.workflow.packname; + if(nodeData.workflow.category) { + category += "/" + nodeData.category; + } + if(category == '') { + category = 'components'; + } + + await config.registerType(category); + register_pack_map(name, nodeData); } catch { let elapsed_time = Date.now() - start_time; @@ -104,72 +171,31 @@ export async function load_components() { } } } - - const groupNode = LiteGraph.createNode(`workflow/${name}`); } } -export async function save_as_component(node, app) { - let pure_name = node.comfyClass.substring(9); - let subgraph = app.graph.extra?.groupNodes?.[pure_name]; +async function save_as_component(node, version, prefix, nodename, packname, category) { + let component_name = `${prefix}::${nodename}`; + let subgraph = app.graph.extra?.groupNodes?.[component_name]; if(!subgraph) { - app.ui.dialog.show(`Failed to retrieve the group node '${pure_name}'.`); - return; - } - - if(node.comfyClass.includes('::')) { - let component_name = node.comfyClass.substring(9); - - if(confirm(`Will you save/overwrite component '${component_name}'?`)) { - let subgraph = app.graph.extra?.groupNodes?.[component_name]; - let body = - { - name: component_name, - workflow: subgraph - }; - - const res = await api.fetchApi('/manager/component/save', { - method: "POST", - headers: { "Content-Type": "application/json", }, - body: JSON.stringify(body) - }); - - if(res.status == 200) { - storeGroupNode(component_name, subgraph); - const config = new GroupNodeConfig(component_name, subgraph); - await config.registerType(); - - let path = await res.text(); - app.ui.dialog.show(`Component '${component_name}' is saved into:\n${path}`); - } - else - app.ui.dialog.show(`Failed to save component.`); - } - - return; + subgraph = app.graph.extra?.groupNodes?.[node.comfyClass.substring(9)]; } - var prefix = prompt("To save as a component, a unique prefix is required. (e.g., the 'Impact' in Impact::MAKE_BASIC_PIPE)", "PREFIX"); + subgraph.version = version; + subgraph.datetime = Date.now(); + subgraph.packname = packname; + subgraph.category = category; - if(!prefix) { - return; - } - - prefix = prefix.trim(); - - if(prefix == 'PREFIX') { - app.ui.dialog.show(`The placeholder 'PREFIX' isn't allowed for component prefix.`); - return; - } - - let component_name = prefix+'::'+pure_name; let body = { name: component_name, workflow: subgraph }; + pack_map[packname] = component_name; + rpack_map[component_name] = [body.pack_name, body.category, body.version, body.datetime]; + const res = await api.fetchApi('/manager/component/save', { method: "POST", headers: { @@ -181,21 +207,30 @@ export async function save_as_component(node, app) { if(res.status == 200) { storeGroupNode(component_name, subgraph); const config = new GroupNodeConfig(component_name, subgraph); - await config.registerType(); + + let category = body.workflow.packname; + if(body.workflow.category) { + category += "/" + body.workflow.category; + } + if(category == '') { + category = 'components'; + } + + await config.registerType(category); let path = await res.text(); - app.ui.dialog.show(`Component '${component_name}' is saved into:\n${path}`); + show_message(`Component '${component_name}' is saved into:\n${path}`); } else - app.ui.dialog.show(`Failed to save component.`); + show_message(`Failed to save component.`); } -async function import_component(component_name, subgraph) { - if(confirm("Will you save component?\n(If canceled, the component won't be saved and can only be used within the current workflow.)")) { +async function import_component(component_name, component, mode) { + if(mode) { let body = { name: component_name, - workflow: subgraph + workflow: component }; const res = await api.fetchApi('/manager/component/save', { @@ -205,14 +240,142 @@ async function import_component(component_name, subgraph) { }); } - storeGroupNode(component_name, subgraph); - const config = new GroupNodeConfig(component_name, subgraph); - await config.registerType(); + let category = component.packname; + if(component.category) { + category += "/" + component.category; + } + if(category == '') { + category = 'components'; + } + + storeGroupNode(component_name, component); + const config = new GroupNodeConfig(component_name, component); + await config.registerType(category); } // Using a timestamp prevents duplicate pastes and ensures the prevention of re-deletion of litegrapheditor_clipboard. let last_paste_timestamp = null; +function versionCompare(v1, v2) { + let ver1; + let ver2; + if(v1 && v1 != '') { + ver1 = v1.split('.'); + ver1[0] = parseInt(ver1[0]); + ver1[1] = parseInt(ver1[1]); + if(ver1.length == 2) + ver1.push(0); + else + ver1[2] = parseInt(ver2[2]); + } + else { + ver1 = [0,0,0]; + } + + if(v2 && v2 != '') { + ver2 = v2.split('.'); + ver2[0] = parseInt(ver2[0]); + ver2[1] = parseInt(ver2[1]); + if(ver2.length == 2) + ver2.push(0); + else + ver2[2] = parseInt(ver2[2]); + } + else { + ver2 = [0,0,0]; + } + + if(ver1[0] > ver2[0]) + return -1; + else if(ver1[0] < ver2[0]) + return 1; + + if(ver1[1] > ver2[1]) + return -1; + else if(ver1[1] < ver2[1]) + return 1; + + if(ver1[2] > ver2[2]) + return -1; + else if(ver1[2] < ver2[2]) + return 1; + + return 0; +} + +function checkVersion(name, component) { + let msg = ''; + if(rpack_map[name]) { + let old_version = rpack_map[name][2]; + if(!old_version || old_version == '') { + msg = ` '${name}' Upgrade (V0.0 -> V${component.version})`; + } + else { + let c = versionCompare(old_version, component.version); + if(c < 0) { + msg = ` '${name}' Downgrade (V${old_version} -> V${component.version})`; + } + else if(c > 0) { + msg = ` '${name}' Upgrade (V${old_version} -> V${component.version})`; + } + else { + msg = ` '${name}' Same version (V${component.version})`; + } + } + } + else { + msg = `'${name}' NEW (V${component.version})`; + } + + return msg; +} + +function handle_import_components(components) { + let msg = 'Components:\n'; + let cnt = 0; + for(let name in components) { + let component = components[name]; + let v = checkVersion(name, component); + + if(cnt < 10) { + msg += v + '\n'; + } + else if (cnt == 10) { + msg += '...\n'; + } + else { + // do nothing + } + + cnt++; + } + + let last_name = null; + msg += '\nWill you load components?\n'; + if(confirm(msg)) { + let mode = confirm('\nWill you save components?\n(cancel=load without save)'); + + for(let name in components) { + let component = components[name]; + import_component(name, component, mode); + last_name = name; + } + + if(mode) { + show_message('Components are saved.'); + } + else { + show_message('Components are loaded.'); + } + } + + if(cnt == 1 && last_name) { + const node = LiteGraph.createNode(`workflow/${last_name}`); + node.pos = [app.canvas.graph_mouse[0], app.canvas.graph_mouse[1]]; + app.canvas.graph.add(node, false); + } +} + function handlePaste(e) { let data = (e.clipboardData || window.clipboardData); const items = data.items; @@ -223,14 +386,7 @@ function handlePaste(e) { let json_data = JSON.parse(data); if(json_data.kind == 'ComfyUI Components' && last_paste_timestamp != json_data.timestamp) { last_paste_timestamp = json_data.timestamp; - - let msg = 'Components are added:\n'; - for(let name in json_data.components) { - let subgraph = json_data.components[name]; - import_component(name, subgraph); - msg += ' - ' + name + '\n'; - } - app.ui.dialog.show(msg); + handle_import_components(json_data.components); // disable paste node localStorage.removeItem("litegrapheditor_clipboard", null); @@ -249,3 +405,263 @@ function handlePaste(e) { document.addEventListener("paste", handlePaste); +export class ComponentBuilderDialog extends ComfyDialog { + constructor() { + super(); + } + + clear() { + while (this.element.children.length) { + this.element.removeChild(this.element.children[0]); + } + } + + show() { + this.invalidateControl(); + + this.element.style.display = "block"; + this.element.style.zIndex = 10001; + this.element.style.width = "500px"; + this.element.style.height = "450px"; + } + + invalidateControl() { + this.clear(); + + let self = this; + + const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => self.close() }); + this.save_button = $el("button", + { id: "cm-save-button", type: "button", textContent: "Save", onclick: () => + { + save_as_component(self.target_node, self.version_string.value.trim(), self.node_prefix.value.trim(), + self.getNodeName(), self.getPackName(), self.category.value.trim()); + } + }); + + let default_nodename = this.target_node.comfyClass.substring(9).trim(); + + let default_packname = ""; + if(rpack_map[default_nodename]) { + default_packname = rpack_map[default_nodename][0]; + } + if(!default_packname) { + default_packname = ''; + } + + let default_category = ""; + if(rpack_map[default_nodename]) { + default_category = rpack_map[default_nodename][1]; + } + if(!default_category) { + default_category = ''; + } + + if(rpack_map[default_nodename]) { + this.default_ver = rpack_map[default_nodename][2]; + } + if(!this.default_ver) { + this.default_ver = '0.0'; + } + + let delimiterIndex = default_nodename.indexOf('::'); + let default_prefix = ""; + if(delimiterIndex != -1) { + default_prefix = default_nodename.substring(0, delimiterIndex); + default_nodename = default_nodename.substring(delimiterIndex + 2); + } + + if(!default_prefix) { + this.save_button.disabled = true; + } + + this.pack_list = this.createPackListCombo(); + + let version_string = this.createLabeledInput('input version (e.g. 1.0)', '*Version : ', this.default_ver); + this.version_string = version_string[1]; + this.version_string.disabled = true; + + let node_prefix = this.createLabeledInput('input node prefix (e.g. mypack)', '*Prefix : ', default_prefix); + this.node_prefix = node_prefix[1]; + + let manual_nodename = this.createLabeledInput('input node name (e.g. MAKE_BASIC_PIPE)', 'Nodename : ', default_nodename); + this.manual_nodename = manual_nodename[1]; + + let manual_packname = this.createLabeledInput('input pack name (e.g. mypack)', 'Packname : ', default_packname); + this.manual_packname = manual_packname[1]; + + let category = this.createLabeledInput('input category (e.g. util/pipe)', 'Category : ', default_category); + this.category = category[1]; + + this.node_label = this.createNodeLabel(); + + let author_mode = this.createAuthorModeCheck(); + this.author_mode = author_mode[0]; + + const content = + $el("div.comfy-modal-content", + [ + $el("tr.cm-title", {}, [ + $el("font", {size:6, color:"white"}, [`ComfyUI-Manager: Component Builder`])] + ), + $el("br", {}, []), + $el("div.cm-menu-container", + [ + author_mode[0], + author_mode[1], + category[0], + node_prefix[0], + manual_nodename[0], + manual_packname[0], + version_string[0], + this.pack_list, + $el("br", {}, []), + this.node_label + ]), + + $el("br", {}, []), + this.save_button, + close_button, + ] + ); + + content.style.width = '100%'; + content.style.height = '100%'; + + this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body }, [ content ]); + } + + validateInput() { + let msg = ""; + + if(!isValidVersionString(this.version_string.value)) { + msg += 'Invalid version string: '+event.value+"\n"; + } + + if(this.node_prefix.value.trim() == '') { + msg += 'Node prefix cannot be empty\n'; + } + + if(this.manual_nodename.value.trim() == '') { + msg += 'Node name cannot be empty\n'; + } + + if(msg != '') { +// alert(msg); + } + + this.save_button.disabled = msg != ""; + } + + getPackName() { + if(this.pack_list.selectedIndex == 0) { + return this.manual_packname.value.trim(); + } + + return this.pack_list.value.trim(); + } + + getNodeName() { + if(this.manual_nodename.value.trim() != '') { + return this.manual_nodename.value.trim(); + } + + return this.target_node.comfyClass.substring(9); + } + + createAuthorModeCheck() { + let check = $el("input",{type:'checkbox', id:"author-mode"},[]) + const check_label = $el("label",{for:"author-mode"},["Enable author mode"]); + check_label.style.color = "var(--fg-color)"; + check_label.style.cursor = "pointer"; + check.checked = false; + + let self = this; + check.onchange = () => { + self.version_string.disabled = !check.checked; + + if(!check.checked) { + self.version_string.value = self.default_ver; + } + else { + alert('If you are not the author, it is not recommended to change the version, as it may cause component update issues.'); + } + }; + + return [check, check_label]; + } + + createNodeLabel() { + let label = $el('p'); + label.className = 'cb-node-label'; + label.textContent = " _::" + this.target_node.comfyClass.substring(9); + return label; + } + + createLabeledInput(placeholder, label, value) { + let textbox = $el('input.cb-widget-input', {type:'text', placeholder:placeholder, value:value}, []); + + let self = this; + textbox.onchange = () => { + this.validateInput.call(self); + this.node_label.textContent = this.node_prefix.value + "::" + this.manual_nodename.value; + } + let row = $el('span.cb-widget', {}, [ $el('span.cb-widget-input-label', label), textbox]); + + return [row, textbox]; + } + + createPackListCombo() { + let combo = document.createElement("select"); + combo.className = "cb-widget"; + let default_packname_option = { value: '##manual', text: 'Packname: Manual' }; + + combo.appendChild($el('option', default_packname_option, [])); + for(let name in pack_map) { + combo.appendChild($el('option', { value: name, text: 'Packname: '+ name }, [])); + } + + let self = this; + combo.onchange = function () { + if(combo.selectedIndex == 0) { + self.manual_packname.disabled = false; + } + else { + self.manual_packname.disabled = true; + } + }; + + return combo; + } +} + +let orig_handleFile = app.handleFile; + +function handleFile(file) { + if (file.name?.endsWith(".json") || file.name?.endsWith(".pack")) { + const reader = new FileReader(); + reader.onload = async () => { + let is_component = false; + const jsonContent = JSON.parse(reader.result); + for(let name in jsonContent) { + let cand = jsonContent[name]; + is_component = cand.datetime && cand.version; + break; + } + + if(is_component) { + handle_import_components(jsonContent); + } + else { + orig_handleFile.call(app, file); + } + }; + reader.readAsText(file); + + return; + } + + orig_handleFile.call(app, file); +} + +app.handleFile = handleFile; diff --git a/misc/Impact.pack b/misc/Impact.pack new file mode 100644 index 0000000..93fd328 --- /dev/null +++ b/misc/Impact.pack @@ -0,0 +1,444 @@ +{ + "Impact::MAKE_BASIC_PIPE": { + "category": "", + "config": { + "1": { + "input": { + "text": { + "name": "Positive prompt" + } + } + }, + "2": { + "input": { + "text": { + "name": "Negative prompt" + } + } + } + }, + "datetime": 1705418802481, + "external": [], + "links": [ + [ + 0, + 1, + 1, + 0, + 1, + "CLIP" + ], + [ + 0, + 1, + 2, + 0, + 1, + "CLIP" + ], + [ + 0, + 0, + 3, + 0, + 1, + "MODEL" + ], + [ + 0, + 1, + 3, + 1, + 1, + "CLIP" + ], + [ + 0, + 2, + 3, + 2, + 1, + "VAE" + ], + [ + 1, + 0, + 3, + 3, + 3, + "CONDITIONING" + ], + [ + 2, + 0, + 3, + 4, + 4, + "CONDITIONING" + ] + ], + "nodes": [ + { + "flags": {}, + "index": 0, + "mode": 0, + "order": 0, + "outputs": [ + { + "links": [], + "name": "MODEL", + "shape": 3, + "slot_index": 0, + "type": "MODEL" + }, + { + "links": [], + "name": "CLIP", + "shape": 3, + "slot_index": 1, + "type": "CLIP" + }, + { + "links": [], + "name": "VAE", + "shape": 3, + "slot_index": 2, + "type": "VAE" + } + ], + "pos": [ + 550, + 360 + ], + "properties": { + "Node name for S&R": "CheckpointLoaderSimple" + }, + "size": { + "0": 315, + "1": 98 + }, + "type": "CheckpointLoaderSimple", + "widgets_values": [ + "SDXL/sd_xl_base_1.0_0.9vae.safetensors" + ] + }, + { + "flags": {}, + "index": 1, + "inputs": [ + { + "link": null, + "name": "clip", + "type": "CLIP" + } + ], + "mode": 0, + "order": 1, + "outputs": [ + { + "links": [], + "name": "CONDITIONING", + "shape": 3, + "slot_index": 0, + "type": "CONDITIONING" + } + ], + "pos": [ + 940, + 480 + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "size": { + "0": 263, + "1": 99 + }, + "title": "Positive", + "type": "CLIPTextEncode", + "widgets_values": [ + "" + ] + }, + { + "flags": {}, + "index": 2, + "inputs": [ + { + "link": null, + "name": "clip", + "type": "CLIP" + } + ], + "mode": 0, + "order": 2, + "outputs": [ + { + "links": [], + "name": "CONDITIONING", + "shape": 3, + "slot_index": 0, + "type": "CONDITIONING" + } + ], + "pos": [ + 940, + 640 + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "size": { + "0": 263, + "1": 99 + }, + "title": "Negative", + "type": "CLIPTextEncode", + "widgets_values": [ + "" + ] + }, + { + "flags": {}, + "index": 3, + "inputs": [ + { + "link": null, + "name": "model", + "type": "MODEL" + }, + { + "link": null, + "name": "clip", + "type": "CLIP" + }, + { + "link": null, + "name": "vae", + "type": "VAE" + }, + { + "link": null, + "name": "positive", + "type": "CONDITIONING" + }, + { + "link": null, + "name": "negative", + "type": "CONDITIONING" + } + ], + "mode": 0, + "order": 3, + "outputs": [ + { + "links": null, + "name": "basic_pipe", + "shape": 3, + "slot_index": 0, + "type": "BASIC_PIPE" + } + ], + "pos": [ + 1320, + 360 + ], + "properties": { + "Node name for S&R": "ToBasicPipe" + }, + "size": { + "0": 241.79998779296875, + "1": 106 + }, + "type": "ToBasicPipe" + } + ], + "packname": "Impact", + "version": "1.0" + }, + "Impact::SIMPLE_DETAILER_PIPE": { + "category": "", + "config": { + "0": { + "output": { + "0": { + "visible": false + }, + "1": { + "visible": false + } + } + }, + "2": { + "input": { + "Select to add LoRA": { + "visible": false + }, + "Select to add Wildcard": { + "visible": false + }, + "wildcard": { + "visible": false + } + } + } + }, + "datetime": 1705419147116, + "external": [], + "links": [ + [ + null, + 0, + 2, + 0, + 6, + "BASIC_PIPE" + ], + [ + 0, + 0, + 2, + 1, + 13, + "BBOX_DETECTOR" + ], + [ + 1, + 0, + 2, + 2, + 15, + "SAM_MODEL" + ] + ], + "nodes": [ + { + "flags": {}, + "index": 0, + "mode": 0, + "order": 2, + "outputs": [ + { + "links": [], + "name": "BBOX_DETECTOR", + "shape": 3, + "type": "BBOX_DETECTOR" + }, + { + "links": null, + "name": "SEGM_DETECTOR", + "shape": 3, + "type": "SEGM_DETECTOR" + } + ], + "pos": [ + 590, + 830 + ], + "properties": { + "Node name for S&R": "UltralyticsDetectorProvider" + }, + "size": { + "0": 315, + "1": 78 + }, + "type": "UltralyticsDetectorProvider", + "widgets_values": [ + "bbox/Eyeful_v1.pt" + ] + }, + { + "flags": {}, + "index": 1, + "mode": 0, + "order": 3, + "outputs": [ + { + "links": [], + "name": "SAM_MODEL", + "shape": 3, + "type": "SAM_MODEL" + } + ], + "pos": [ + 590, + 960 + ], + "properties": { + "Node name for S&R": "SAMLoader" + }, + "size": { + "0": 315, + "1": 82 + }, + "type": "SAMLoader", + "widgets_values": [ + "sam_vit_b_01ec64.pth", + "AUTO" + ] + }, + { + "flags": {}, + "index": 2, + "inputs": [ + { + "link": null, + "name": "basic_pipe", + "type": "BASIC_PIPE" + }, + { + "link": null, + "name": "bbox_detector", + "slot_index": 1, + "type": "BBOX_DETECTOR" + }, + { + "link": null, + "name": "sam_model_opt", + "slot_index": 2, + "type": "SAM_MODEL" + }, + { + "link": null, + "name": "segm_detector_opt", + "type": "SEGM_DETECTOR" + }, + { + "link": null, + "name": "detailer_hook", + "type": "DETAILER_HOOK" + } + ], + "mode": 0, + "order": 5, + "outputs": [ + { + "links": null, + "name": "detailer_pipe", + "shape": 3, + "type": "DETAILER_PIPE" + } + ], + "pos": [ + 1044, + 812 + ], + "properties": { + "Node name for S&R": "BasicPipeToDetailerPipe" + }, + "size": { + "0": 400, + "1": 204 + }, + "type": "BasicPipeToDetailerPipe", + "widgets_values": [ + "", + "Select the LoRA to add to the text", + "Select the Wildcard to add to the text" + ] + } + ], + "packname": "Impact", + "version": "1.0" + } +} \ No newline at end of file