mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-22 12:50:50 +08:00
feat: update, uninstall for custom nodes
This commit is contained in:
parent
c920b10aa8
commit
4d54ef7738
151
__init__.py
151
__init__.py
@ -9,7 +9,7 @@ sys.path.append('../..')
|
||||
from torchvision.datasets.utils import download_url
|
||||
|
||||
# ensure .js
|
||||
print("### Loading: ComfyUI-Manager (V0.1)")
|
||||
print("### Loading: ComfyUI-Manager (V0.2)")
|
||||
|
||||
comfy_path = os.path.dirname(folder_paths.__file__)
|
||||
custom_nodes_path = os.path.join(comfy_path, 'custom_nodes')
|
||||
@ -21,6 +21,33 @@ local_db_alter = os.path.join(comfyui_manager_path, "alter-list.json")
|
||||
local_db_custom_node_list = os.path.join(comfyui_manager_path, "custom-node-list.json")
|
||||
|
||||
|
||||
def git_repo_has_updates(path):
|
||||
# Check if the path is a git repository
|
||||
if not os.path.exists(os.path.join(path, '.git')):
|
||||
raise ValueError('Not a git repository')
|
||||
|
||||
# Fetch the latest commits from the remote repository
|
||||
subprocess.run(['git', 'fetch'], check=True, cwd=path)
|
||||
|
||||
# Get the current commit hash and the commit hash of the remote branch
|
||||
commit_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], encoding='utf-8', cwd=path).strip()
|
||||
remote_commit_hash = subprocess.check_output(['git', 'rev-parse', '@{u}'], encoding='utf-8', cwd=path).strip()
|
||||
|
||||
# Compare the commit hashes to determine if the local repository is behind the remote repository
|
||||
if commit_hash != remote_commit_hash:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def git_pull(path):
|
||||
print(f"path: {path}")
|
||||
if not os.path.exists(os.path.join(path, '.git')):
|
||||
raise ValueError('Not a git repository')
|
||||
|
||||
subprocess.run(['git', 'pull'], check=True, cwd=path)
|
||||
|
||||
|
||||
async def get_data(uri):
|
||||
print(f"FECTH DATA from: {uri}")
|
||||
if uri.startswith("http"):
|
||||
@ -101,7 +128,14 @@ def check_a_custom_node_installed(item):
|
||||
dir_name = os.path.splitext(os.path.basename(item['files'][0]))[0].replace(".git", "")
|
||||
dir_path = os.path.join(custom_nodes_path, dir_name)
|
||||
if os.path.exists(dir_path):
|
||||
item['installed'] = 'True'
|
||||
try:
|
||||
if git_repo_has_updates(dir_path):
|
||||
item['installed'] = 'Update'
|
||||
else:
|
||||
item['installed'] = 'True'
|
||||
except:
|
||||
item['installed'] = 'True'
|
||||
|
||||
else:
|
||||
item['installed'] = 'False'
|
||||
|
||||
@ -208,7 +242,7 @@ def unzip_install(files):
|
||||
print(f"Install(unzip) error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Installation successful.")
|
||||
print("Installation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
@ -228,7 +262,7 @@ def download_url_with_agent(url, save_path):
|
||||
print(f"Download error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Installation successful.")
|
||||
print("Installation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
@ -247,7 +281,23 @@ def copy_install(files, js_path_name=None):
|
||||
print(f"Install(copy) error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Installation successful.")
|
||||
print("Installation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
def copy_uninstall(files, js_path_name=None):
|
||||
for url in files:
|
||||
dir_name = os.path.basename(url)
|
||||
base_path = custom_nodes_path if url.endswith('.py') else js_path
|
||||
file_path = os.path.join(base_path, dir_name)
|
||||
|
||||
try:
|
||||
os.remove(file_path)
|
||||
except Exception as e:
|
||||
print(f"UnInstall(copy) error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Uninstallation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
@ -291,7 +341,50 @@ def gitclone_install(files):
|
||||
print(f"Install(git-clone) error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Installation successful.")
|
||||
print("Installation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
def gitclone_uninstall(files):
|
||||
import shutil
|
||||
import os
|
||||
|
||||
print(f"uninstall: {files}")
|
||||
for url in files:
|
||||
try:
|
||||
dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
|
||||
dir_path = os.path.join(custom_nodes_path, dir_name)
|
||||
|
||||
# safey check
|
||||
if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '':
|
||||
print(f"Uninstall(git-clone) error: invalid path '{dir_path}' for '{url}'")
|
||||
return False
|
||||
|
||||
shutil.rmtree(dir_path)
|
||||
except Exception as e:
|
||||
print(f"Uninstall(git-clone) error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Uninstallation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
def gitclone_update(files):
|
||||
import shutil
|
||||
import os
|
||||
|
||||
print(f"uninstall: {files}")
|
||||
for url in files:
|
||||
try:
|
||||
dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
|
||||
dir_path = os.path.join(custom_nodes_path, dir_name)
|
||||
git_pull(dir_path)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Update(git-clone) error: {url} / {e}")
|
||||
return False
|
||||
|
||||
print("Uninstallation was successful.")
|
||||
return True
|
||||
|
||||
|
||||
@ -321,6 +414,48 @@ async def install_custom_node(request):
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/customnode/uninstall")
|
||||
async def install_custom_node(request):
|
||||
json_data = await request.json()
|
||||
|
||||
install_type = json_data['install_type']
|
||||
|
||||
print(f"Uninstall custom node '{json_data['title']}'")
|
||||
|
||||
res = False
|
||||
|
||||
if install_type == "copy":
|
||||
js_path_name = json_data['js_path'] if 'js_path' in json_data else None
|
||||
res = copy_uninstall(json_data['files'], js_path_name)
|
||||
|
||||
elif install_type == "git-clone":
|
||||
res = gitclone_uninstall(json_data['files'])
|
||||
|
||||
if res:
|
||||
return web.json_response({}, content_type='application/json')
|
||||
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/customnode/update")
|
||||
async def install_custom_node(request):
|
||||
json_data = await request.json()
|
||||
|
||||
install_type = json_data['install_type']
|
||||
|
||||
print(f"Update custom node '{json_data['title']}'")
|
||||
|
||||
res = False
|
||||
|
||||
if install_type == "git-clone":
|
||||
res = gitclone_update(json_data['files'])
|
||||
|
||||
if res:
|
||||
return web.json_response({}, content_type='application/json')
|
||||
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/model/install")
|
||||
async def install_model(request):
|
||||
json_data = await request.json()
|
||||
@ -339,3 +474,7 @@ async def install_model(request):
|
||||
return web.json_response({}, content_type='application/json')
|
||||
|
||||
return web.Response(status=400)
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
__all__ = ['NODE_CLASS_MAPPINGS']
|
||||
@ -12,8 +12,8 @@
|
||||
},
|
||||
{
|
||||
"id":"https://github.com/ltdrdata/ComfyUI-Impact-Pack",
|
||||
"tags":"ddetailer, adetailer, ddsd, DD",
|
||||
"description": "To implement the feature of automatically detecting faces and enhancing details, various detection nodes and detailers provided by the Impact Pack can be applied."
|
||||
"tags":"ddetailer, adetailer, ddsd, DD, loopback scaler",
|
||||
"description": "To implement the feature of automatically detecting faces and enhancing details, various detection nodes and detailers provided by the Impact Pack can be applied. Similarly to Loopback Scaler, it also provides various custom workflows that can apply Ksampler while gradually scaling up."
|
||||
},
|
||||
{
|
||||
"id":"https://github.com/biegert/ComfyUI-CLIPSeg/raw/main/custom_nodes/clipseg.py",
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"https://github.com/ltdrdata/ComfyUI-Impact-Pack"
|
||||
],
|
||||
"install_type": "git-clone",
|
||||
"description": "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details."
|
||||
"description": "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
|
||||
},
|
||||
{
|
||||
"author": "comfyanonymous",
|
||||
@ -82,7 +82,7 @@
|
||||
},
|
||||
{
|
||||
"author": "BlenderNeko",
|
||||
"title": "(WIP) Tiled sampling for ComfyUI",
|
||||
"title": "Tiled sampling for ComfyUI",
|
||||
"reference": "https://github.com/BlenderNeko/ComfyUI_TiledKSampler",
|
||||
"files": [
|
||||
"https://github.com/BlenderNeko/ComfyUI_TiledKSampler"
|
||||
|
||||
@ -35,24 +35,30 @@ async function getModelList() {
|
||||
return data;
|
||||
}
|
||||
|
||||
async function install_custom_node(target, caller) {
|
||||
async function install_custom_node(target, caller, mode) {
|
||||
if(caller) {
|
||||
caller.startInstall(target);
|
||||
|
||||
try {
|
||||
const response = await fetch('/customnode/install', {
|
||||
const response = await fetch(`/customnode/${mode}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(target)
|
||||
});
|
||||
|
||||
if(response.status == 400) {
|
||||
app.ui.dialog.show(`${mode} failed: ${target.title}`);
|
||||
app.ui.dialog.element.style.zIndex = 9999;
|
||||
return false;
|
||||
}
|
||||
|
||||
const status = await response.json();
|
||||
app.ui.dialog.close();
|
||||
target.installed = 'True';
|
||||
return true;
|
||||
}
|
||||
catch(exception) {
|
||||
app.ui.dialog.show(`Install failed: ${target.title} / ${exception}`);
|
||||
app.ui.dialog.show(`${mode} failed: ${target.title} / ${exception}`);
|
||||
app.ui.dialog.element.style.zIndex = 9999;
|
||||
return false;
|
||||
}
|
||||
@ -112,18 +118,37 @@ class CustomNodesInstaller extends ComfyDialog {
|
||||
}
|
||||
|
||||
startInstall(target) {
|
||||
this.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
|
||||
const self = CustomNodesInstaller.instance;
|
||||
|
||||
self.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
|
||||
|
||||
for(let i in this.install_buttons) {
|
||||
this.install_buttons[i].disabled = true;
|
||||
this.install_buttons[i].style.backgroundColor = 'gray';
|
||||
for(let i in self.install_buttons) {
|
||||
self.install_buttons[i].disabled = true;
|
||||
self.install_buttons[i].style.backgroundColor = 'gray';
|
||||
}
|
||||
}
|
||||
|
||||
async invalidateControl() {
|
||||
this.clear();
|
||||
|
||||
// splash
|
||||
while (this.element.children.length) {
|
||||
this.element.removeChild(this.element.children[0]);
|
||||
}
|
||||
|
||||
const msg = $el('div', {id:'custom-message'},
|
||||
[$el('br'),
|
||||
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
|
||||
$el('br')]);
|
||||
msg.style.height = '100px';
|
||||
msg.style.verticalAlign = 'middle';
|
||||
this.element.appendChild(msg);
|
||||
|
||||
// invalidate
|
||||
this.data = (await getCustomNodes()).custom_nodes;
|
||||
|
||||
this.element.removeChild(msg);
|
||||
|
||||
while (this.element.children.length) {
|
||||
this.element.removeChild(this.element.children[0]);
|
||||
}
|
||||
@ -190,14 +215,23 @@ class CustomNodesInstaller extends ComfyDialog {
|
||||
data5.style.textAlign = "center";
|
||||
|
||||
var installBtn = document.createElement('button');
|
||||
var installBtn2 = null;
|
||||
|
||||
this.install_buttons.push(installBtn);
|
||||
|
||||
switch(data.installed) {
|
||||
case 'Update':
|
||||
installBtn2 = document.createElement('button');
|
||||
installBtn2.innerHTML = 'Update';
|
||||
installBtn2.style.backgroundColor = 'blue';
|
||||
this.install_buttons.push(installBtn2);
|
||||
|
||||
installBtn.innerHTML = 'Uninstall';
|
||||
installBtn.style.backgroundColor = 'red';
|
||||
break;
|
||||
case 'True':
|
||||
installBtn.innerHTML = 'Installed';
|
||||
installBtn.style.backgroundColor = 'green';
|
||||
installBtn.disabled = true;
|
||||
installBtn.innerHTML = 'Uninstall';
|
||||
installBtn.style.backgroundColor = 'red';
|
||||
break;
|
||||
case 'False':
|
||||
installBtn.innerHTML = 'Install';
|
||||
@ -205,11 +239,26 @@ class CustomNodesInstaller extends ComfyDialog {
|
||||
break;
|
||||
default:
|
||||
installBtn.innerHTML = 'Try Install';
|
||||
installBtn.style.backgroundColor = 'brown';
|
||||
installBtn.style.backgroundColor = 'silver';
|
||||
}
|
||||
|
||||
if(installBtn2 != null) {
|
||||
installBtn2.addEventListener('click', function() {
|
||||
install_custom_node(data, CustomNodesInstaller.instance, 'update');
|
||||
});
|
||||
|
||||
data5.appendChild(installBtn2);
|
||||
}
|
||||
|
||||
installBtn.addEventListener('click', function() {
|
||||
install_custom_node(data, CustomNodesInstaller.instance);
|
||||
if(this.innerHTML == 'Uninstall') {
|
||||
if (confirm(`Are you sure uninstall ${data.title}?`)) {
|
||||
install_custom_node(data, CustomNodesInstaller.instance, 'uninstall');
|
||||
}
|
||||
}
|
||||
else {
|
||||
install_custom_node(data, CustomNodesInstaller.instance, 'install');
|
||||
}
|
||||
});
|
||||
|
||||
data5.appendChild(installBtn);
|
||||
@ -281,18 +330,37 @@ class AlternativesInstaller extends ComfyDialog {
|
||||
}
|
||||
|
||||
startInstall(target) {
|
||||
this.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
|
||||
const self = AlternativesInstaller.instance;
|
||||
|
||||
for(let i in this.install_buttons) {
|
||||
this.install_buttons[i].disabled = true;
|
||||
this.install_buttons[i].style.backgroundColor = 'gray';
|
||||
self.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
|
||||
|
||||
for(let i in self.install_buttons) {
|
||||
self.install_buttons[i].disabled = true;
|
||||
self.install_buttons[i].style.backgroundColor = 'gray';
|
||||
}
|
||||
}
|
||||
|
||||
async invalidateControl() {
|
||||
this.clear();
|
||||
|
||||
// splash
|
||||
while (this.element.children.length) {
|
||||
this.element.removeChild(this.element.children[0]);
|
||||
}
|
||||
|
||||
const msg = $el('div', {id:'custom-message'},
|
||||
[$el('br'),
|
||||
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
|
||||
$el('br')]);
|
||||
msg.style.height = '100px';
|
||||
msg.style.verticalAlign = 'middle';
|
||||
this.element.appendChild(msg);
|
||||
|
||||
// invalidate
|
||||
this.data = (await getAlterList()).items;
|
||||
|
||||
this.element.removeChild(msg);
|
||||
|
||||
while (this.element.children.length) {
|
||||
this.element.removeChild(this.element.children[0]);
|
||||
}
|
||||
@ -372,14 +440,23 @@ class AlternativesInstaller extends ComfyDialog {
|
||||
|
||||
if(data.custom_node) {
|
||||
var installBtn = document.createElement('button');
|
||||
var installBtn2 = null;
|
||||
|
||||
this.install_buttons.push(installBtn);
|
||||
|
||||
switch(data.custom_node.installed) {
|
||||
case 'Update':
|
||||
installBtn2 = document.createElement('button');
|
||||
installBtn2.innerHTML = 'Update';
|
||||
installBtn2.style.backgroundColor = 'blue';
|
||||
this.install_buttons.push(installBtn2);
|
||||
|
||||
installBtn.innerHTML = 'Uninstall';
|
||||
installBtn.style.backgroundColor = 'red';
|
||||
break;
|
||||
case 'True':
|
||||
installBtn.innerHTML = 'Installed';
|
||||
installBtn.style.backgroundColor = 'green';
|
||||
installBtn.disabled = true;
|
||||
installBtn.innerHTML = 'Uninstall';
|
||||
installBtn.style.backgroundColor = 'red';
|
||||
break;
|
||||
case 'False':
|
||||
installBtn.innerHTML = 'Install';
|
||||
@ -387,11 +464,26 @@ class AlternativesInstaller extends ComfyDialog {
|
||||
break;
|
||||
default:
|
||||
installBtn.innerHTML = 'Try Install';
|
||||
installBtn.style.backgroundColor = 'brown';
|
||||
installBtn.style.backgroundColor = 'silver';
|
||||
}
|
||||
|
||||
if(installBtn2 != null) {
|
||||
installBtn2.addEventListener('click', function() {
|
||||
install_custom_node(data.custom_node, AlternativesInstaller.instance, 'update');
|
||||
});
|
||||
|
||||
data6.appendChild(installBtn2);
|
||||
}
|
||||
|
||||
installBtn.addEventListener('click', function() {
|
||||
install_custom_node(data.custom_node, AlternativesInstaller.instance);
|
||||
if(this.innerHTML == 'Uninstall') {
|
||||
if (confirm(`Are you sure uninstall ${data.title}?`)) {
|
||||
install_custom_node(data.custom_node, AlternativesInstaller.instance, 'uninstall');
|
||||
}
|
||||
}
|
||||
else {
|
||||
install_custom_node(data.custom_node, AlternativesInstaller.instance, 'install');
|
||||
}
|
||||
});
|
||||
|
||||
data6.appendChild(installBtn);
|
||||
@ -476,11 +568,13 @@ class ModelInstaller extends ComfyDialog {
|
||||
}
|
||||
|
||||
startInstall(target) {
|
||||
this.updateMessage(`<BR><font color="green">Installing '${target.name}'</font>`);
|
||||
const self = ModelInstaller.instance;
|
||||
|
||||
for(let i in this.install_buttons) {
|
||||
this.install_buttons[i].disabled = true;
|
||||
this.install_buttons[i].style.backgroundColor = 'gray';
|
||||
self.updateMessage(`<BR><font color="green">Installing '${target.name}'</font>`);
|
||||
|
||||
for(let i in self.install_buttons) {
|
||||
self.install_buttons[i].disabled = true;
|
||||
self.install_buttons[i].style.backgroundColor = 'gray';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user