feat: custom-nodes-manager - background tasks(install/update/fix/disable/enable)

This commit is contained in:
Dr.Lt.Data 2025-02-01 11:22:01 +09:00
parent 0f7b9d02a0
commit 4760deaf9c
7 changed files with 312 additions and 170 deletions

View File

@ -41,7 +41,7 @@ import manager_downloader
from node_package import InstalledNodePackage from node_package import InstalledNodePackage
version_code = [3, 11, 3] version_code = [3, 12]
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '') version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')

View File

@ -18,6 +18,8 @@ import manager_core as core
import manager_util import manager_util
import cm_global import cm_global
import logging import logging
import asyncio
import queue
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})") logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
@ -31,7 +33,6 @@ SECURITY_MESSAGE_GENERAL = "ERROR: This installation is not allowed in this secu
routes = PromptServer.instance.routes routes = PromptServer.instance.routes
def handle_stream(stream, prefix): def handle_stream(stream, prefix):
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace') stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
for msg in stream: for msg in stream:
@ -368,6 +369,147 @@ def nickname_filter(json_obj):
return json_obj return json_obj
install_queue = queue.Queue()
install_result = {}
async def install_worker():
global install_result
global install_queue
async def do_install(item):
ui_id, node_spec_str, channel, mode, skip_post_install = item
try:
node_spec = core.unified_manager.resolve_node_spec(node_spec_str)
if node_spec is None:
logging.error(f"Cannot resolve install target: '{node_spec_str}'")
install_result[ui_id] = f"Cannot resolve install target: '{node_spec_str}'"
return
node_name, version_spec, is_specified = node_spec
res = await core.unified_manager.install_by_id(node_name, version_spec, channel, mode, return_postinstall=skip_post_install)
# discard post install if skip_post_install mode
if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:
logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")
install_result[ui_id] = res.msg
return
elif not res.result:
logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")
install_result[ui_id] = res.msg
return
install_result[ui_id] = 'success'
except Exception:
traceback.print_exc()
install_result[ui_id] = f"Installation failed:\n{node_spec_str}"
async def do_update(item):
ui_id, node_name, node_ver = item
try:
res = core.unified_manager.unified_update(node_name, node_ver)
manager_util.clear_pip_cache()
if res.result:
install_result[ui_id] = 'success'
return
logging.error(f"\nERROR: An error occurred while updating '{node_name}'.")
install_result[ui_id] = f"An error occurred while updating '{node_name}'."
except Exception:
traceback.print_exc()
install_result[ui_id] = f"An error occurred while updating '{node_name}'."
async def do_fix(item):
ui_id, node_name, node_ver = item
try:
res = core.unified_manager.unified_fix(node_name, node_ver)
if res.result:
install_result[ui_id] = 'success'
return
else:
logging.error(res.msg)
logging.error(f"\nERROR: An error occurred while fixing '{node_name}@{node_ver}'.")
install_result[ui_id] = f"An error occurred while fixing '{node_name}@{node_ver}'."
except Exception:
traceback.print_exc()
install_result[ui_id] = f"An error occurred while fixing '{node_name}@{node_ver}'."
async def do_uninstall(item):
ui_id, node_name, is_unknown = item
try:
res = core.unified_manager.unified_uninstall(node_name, is_unknown)
if res.result:
install_result[ui_id] = 'success'
return
logging.error(f"\nERROR: An error occurred while uninstalling '{node_name}'.")
install_result[ui_id] = f"An error occurred while uninstalling '{node_name}'."
except Exception:
traceback.print_exc()
install_result[ui_id] = f"An error occurred while uninstalling '{node_name}'."
async def do_disable(item):
ui_id, node_name, is_unknown = item
try:
res = core.unified_manager.unified_disable(node_name, is_unknown)
if res:
install_result[ui_id] = 'success'
return
install_result[ui_id] = f"Failed to disable: '{node_name}'"
except Exception:
traceback.print_exc()
install_result[ui_id] = f"Failed to disable: '{node_name}'"
stats = {}
while True:
done_count = len(install_result)
total_count = done_count + install_queue.qsize()
if install_queue.empty():
logging.info(f"\n[ComfyUI-Manager] Queued works are completed.\n{stats}")
logging.info("\nAfter restarting ComfyUI, please refresh the browser.")
PromptServer.instance.send_sync("cm-install-status",
{'status': 'done', 'result': install_result,
'total_count': total_count, 'done_count': done_count})
install_result = {}
install_queue = queue.Queue()
return
kind, item = install_queue.get()
if kind == 'install':
await do_install(item)
elif kind == 'update':
await do_update(item)
elif kind == 'fix':
await do_fix(item)
elif kind == 'uninstall':
await do_uninstall(item)
elif kind == 'disable':
await do_disable(item)
stats[kind] = stats.get(kind, 0) + 1
PromptServer.instance.send_sync("cm-install-status",
{'status': 'in_progress', 'target': item[0],
'total_count': total_count, 'done_count': done_count})
@routes.get("/customnode/getmappings") @routes.get("/customnode/getmappings")
async def fetch_customnode_mappings(request): async def fetch_customnode_mappings(request):
""" """
@ -870,7 +1012,24 @@ async def reinstall_custom_node(request):
await install_custom_node(request) await install_custom_node(request)
@routes.post("/customnode/install") @routes.get("/customnode/queue/reset")
async def reset_queue(request):
global install_queue
install_queue = queue.Queue()
return web.Response(status=200)
@routes.get("/customnode/queue/count")
async def reset_queue(request):
global install_queue
done_count = len(install_result)
total_count = done_count + install_queue.qsize()
return web.json_response({'total_count': total_count, 'done_count': done_count})
@routes.post("/customnode/queue/install")
async def install_custom_node(request): async def install_custom_node(request):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
@ -913,26 +1072,23 @@ async def install_custom_node(request):
logging.error(SECURITY_MESSAGE_GENERAL) logging.error(SECURITY_MESSAGE_GENERAL)
return web.Response(status=404, text="A security error has occurred. Please check the terminal logs") return web.Response(status=404, text="A security error has occurred. Please check the terminal logs")
node_spec = core.unified_manager.resolve_node_spec(node_spec_str) install_item = json_data.get('ui_id'), node_spec_str, json_data['channel'], json_data['mode'], skip_post_install
install_queue.put(("install", install_item))
if node_spec is None: return web.Response(status=200)
return web.Response(status=400, text=f"Cannot resolve install target: '{node_spec_str}'")
node_name, version_spec, is_specified = node_spec
res = await core.unified_manager.install_by_id(node_name, version_spec, json_data['channel'], json_data['mode'], return_postinstall=skip_post_install)
# discard post install if skip_post_install mode
if res.action not in ['skip', 'enable', 'install-git', 'install-cnr', 'switch-cnr']:
logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")
return web.Response(status=400, text=res.msg)
elif not res.result:
logging.error(f"[ComfyUI-Manager] Installation failed:\n{res.msg}")
return web.Response(status=400, text=res.msg)
return web.Response(status=200, text="Installation success.")
@routes.post("/customnode/fix") @routes.get("/customnode/queue/start")
async def queue_start(request):
global install_result
install_result = {}
threading.Thread(target=lambda: asyncio.run(install_worker())).start()
return web.Response(status=200)
@routes.post("/customnode/queue/fix")
async def fix_custom_node(request): async def fix_custom_node(request):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_GENERAL) logging.error(SECURITY_MESSAGE_GENERAL)
@ -948,16 +1104,10 @@ async def fix_custom_node(request):
# unknown # unknown
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
res = core.unified_manager.unified_fix(node_name, node_ver) update_item = json_data.get('ui_id'), node_name, json_data['version']
install_queue.put(("fix", update_item))
if res.result: return web.Response(status=200)
logging.info("\nAfter restarting ComfyUI, please refresh the browser.")
return web.json_response({}, content_type='application/json')
else:
logging.error(res.msg)
logging.error(f"\nERROR: An error occurred while fixing '{node_name}@{node_ver}'.")
return web.Response(status=400, text=f"An error occurred while fixing '{node_name}@{node_ver}'.")
@routes.post("/customnode/install/git_url") @routes.post("/customnode/install/git_url")
@ -992,7 +1142,7 @@ async def install_custom_node_pip(request):
return web.Response(status=200) return web.Response(status=200)
@routes.post("/customnode/uninstall") @routes.post("/customnode/queue/uninstall")
async def uninstall_custom_node(request): async def uninstall_custom_node(request):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
@ -1009,17 +1159,13 @@ async def uninstall_custom_node(request):
is_unknown = True is_unknown = True
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
res = core.unified_manager.unified_uninstall(node_name, is_unknown) uninstall_item = json_data.get('ui_id'), node_name, is_unknown
install_queue.put(("uninstall", uninstall_item))
if res.result: return web.Response(status=200)
logging.info("\nAfter restarting ComfyUI, please refresh the browser.")
return web.json_response({}, content_type='application/json')
logging.error(f"\nERROR: An error occurred while uninstalling '{node_name}'.")
return web.Response(status=400, text=f"An error occurred while uninstalling '{node_name}'.")
@routes.post("/customnode/update") @routes.post("/customnode/queue/update")
async def update_custom_node(request): async def update_custom_node(request):
if not is_allowed_security_level('middle'): if not is_allowed_security_level('middle'):
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW) logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
@ -1034,16 +1180,10 @@ async def update_custom_node(request):
# unknown # unknown
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
res = core.unified_manager.unified_update(node_name, json_data['version']) update_item = json_data.get('ui_id'), node_name, json_data['version']
install_queue.put(("update", update_item))
manager_util.clear_pip_cache() return web.Response(status=200)
if res.result:
logging.info("\nAfter restarting ComfyUI, please refresh the browser.")
return web.json_response({}, content_type='application/json')
logging.error(f"\nERROR: An error occurred while updating '{node_name}'.")
return web.Response(status=400, text=f"An error occurred while updating '{node_name}'.")
@routes.get("/comfyui_manager/update_comfyui") @routes.get("/comfyui_manager/update_comfyui")
@ -1092,7 +1232,7 @@ async def comfyui_switch_version(request):
return web.Response(status=400) return web.Response(status=400)
@routes.post("/customnode/disable") @routes.post("/customnode/queue/disable")
async def disable_node(request): async def disable_node(request):
json_data = await request.json() json_data = await request.json()
@ -1105,12 +1245,10 @@ async def disable_node(request):
is_unknown = True is_unknown = True
node_name = os.path.basename(json_data['files'][0]) node_name = os.path.basename(json_data['files'][0])
res = core.unified_manager.unified_disable(node_name, is_unknown) update_item = json_data.get('ui_id'), node_name, is_unknown
install_queue.put(("disable", update_item))
if res: return web.Response(status=200)
return web.json_response({}, content_type='application/json')
return web.Response(status=400, text="Failed to disable")
@routes.get("/manager/migrate_unmanaged_nodes") @routes.get("/manager/migrate_unmanaged_nodes")
@ -1149,37 +1287,44 @@ async def install_model(request):
logging.error(SECURITY_MESSAGE_NORMAL_MINUS) logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
return web.Response(status=403) return web.Response(status=403)
res = False def do_install():
res = False
try: try:
if model_path is not None: if model_path is not None:
model_url = json_data['url'] model_url = json_data['url']
logging.info(f"Install model '{json_data['name']}' from '{model_url}' into '{model_path}'") logging.info(f"Install model '{json_data['name']}' from '{model_url}' into '{model_path}'")
if not core.get_config()['model_download_by_agent'] and ( if not core.get_config()['model_download_by_agent'] and (
model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')): model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')):
model_dir = get_model_dir(json_data, True) model_dir = get_model_dir(json_data, True)
download_url(model_url, model_dir, filename=json_data['filename']) download_url(model_url, model_dir, filename=json_data['filename'])
if model_path.endswith('.zip'): if model_path.endswith('.zip'):
res = core.unzip(model_path) res = core.unzip(model_path)
else:
res = True
if res:
return web.json_response({}, content_type='application/json')
else: else:
res = True res = download_url_with_agent(model_url, model_path)
if res and model_path.endswith('.zip'):
if res: res = core.unzip(model_path)
return web.json_response({}, content_type='application/json')
else: else:
res = download_url_with_agent(model_url, model_path) logging.error(f"Model installation error: invalid model type - {json_data['type']}")
if res and model_path.endswith('.zip'):
res = core.unzip(model_path)
else:
logging.error(f"Model installation error: invalid model type - {json_data['type']}")
if res: if res:
return web.json_response({}, content_type='application/json') return web.json_response({}, content_type='application/json')
except Exception as e: except Exception as e:
logging.error(f"[ERROR] {e}", file=sys.stderr) logging.error(f"[ERROR] {e}", file=sys.stderr)
return web.Response(status=400)
return web.Response(status=400) # Run the installation in a thread pool
with concurrent.futures.ThreadPoolExecutor() as executor:
asyncio.get_event_loop().run_in_executor(executor, do_install)
return web.Response(status=200)
@routes.get("/manager/preview_method") @routes.get("/manager/preview_method")
@ -1408,8 +1553,6 @@ def confirm_try_install(sender, custom_node_url, msg):
cm_global.register_api('cm.try-install-custom-node', confirm_try_install) cm_global.register_api('cm.try-install-custom-node', confirm_try_install)
import asyncio
async def default_cache_update(): async def default_cache_update():
async def get_cache(filename): async def get_cache(filename):

View File

@ -17,7 +17,6 @@ import {
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js"; import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
import { CustomNodesManager } from "./custom-nodes-manager.js"; import { CustomNodesManager } from "./custom-nodes-manager.js";
import { ModelManager } from "./model-manager.js"; import { ModelManager } from "./model-manager.js";
import { set_double_click_policy } from "./node_fixer.js";
import { SnapshotManager } from "./snapshot.js"; import { SnapshotManager } from "./snapshot.js";
var docStyle = document.createElement('style'); var docStyle = document.createElement('style');

View File

@ -130,6 +130,20 @@ export function customAlert(message) {
} }
} }
export function infoToast(summary, message) {
try {
app.extensionManager.toast.add({
severity: 'info',
summary: summary,
detail: message,
life: 3000
})
}
catch {
// do nothing
}
}
export async function customPrompt(title, message) { export async function customPrompt(title, message) {
try { try {

View File

@ -4,7 +4,7 @@ import { api } from "../../scripts/api.js";
import { import {
manager_instance, rebootAPI, install_via_git_url, manager_instance, rebootAPI, install_via_git_url,
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt, sanitizeHTML fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt, sanitizeHTML, infoToast
} from "./common.js"; } from "./common.js";
// https://cenfun.github.io/turbogrid/api.html // https://cenfun.github.io/turbogrid/api.html
@ -391,6 +391,8 @@ export class CustomNodesManager {
this.restartMap = {}; this.restartMap = {};
this.init(); this.init();
api.addEventListener("cm-install-status", this.onInstallStatus);
} }
init() { init() {
@ -1204,7 +1206,7 @@ export class CustomNodesManager {
} }
focusInstall(item, mode) { focusInstall(item, mode) {
const cellNode = this.grid.getCellNode(item, "installed"); const cellNode = this.grid.getCellNode(item, "action");
if (cellNode) { if (cellNode) {
const cellBtn = cellNode.querySelector(`button[mode="${mode}"]`); const cellBtn = cellNode.querySelector(`button[mode="${mode}"]`);
if (cellBtn) { if (cellBtn) {
@ -1269,6 +1271,13 @@ export class CustomNodesManager {
} }
async installNodes(list, btn, title, selected_version) { async installNodes(list, btn, title, selected_version) {
let stats = await api.fetchApi('/customnode/queue/count');
stats = await stats.json();
if(stats.total_count > 0) {
customAlert(`[ComfyUI-Manager] There are already tasks in progress. Please try again after it is completed. (${stats.done_count}/${stats.total_count})`);
return;
}
const { target, label, mode} = btn; const { target, label, mode} = btn;
if(mode === "uninstall") { if(mode === "uninstall") {
@ -1294,8 +1303,13 @@ export class CustomNodesManager {
let needRestart = false; let needRestart = false;
let errorMsg = ""; let errorMsg = "";
await api.fetchApi('/customnode/queue/reset');
this.install_context = btn;
for (const hash of list) { for (const hash of list) {
const item = this.grid.getRowItemBy("hash", hash); const item = this.grid.getRowItemBy("hash", hash);
if (!item) { if (!item) {
errorMsg = `Not found custom node: ${hash}`; errorMsg = `Not found custom node: ${hash}`;
break; break;
@ -1315,6 +1329,7 @@ export class CustomNodesManager {
data.selected_version = selected_version; data.selected_version = selected_version;
data.channel = this.channel; data.channel = this.channel;
data.mode = this.mode; data.mode = this.mode;
data.ui_id = hash;
let install_mode = mode; let install_mode = mode;
if(mode == 'switch') { if(mode == 'switch') {
@ -1332,14 +1347,14 @@ export class CustomNodesManager {
api_mode = 'reinstall'; api_mode = 'reinstall';
} }
const res = await api.fetchApi(`/customnode/${api_mode}`, { const res = await api.fetchApi(`/customnode/queue/${api_mode}`, {
method: 'POST', method: 'POST',
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
if (res.status != 200) { if (res.status != 200) {
errorMsg = `${item.title} ${mode} failed: `; errorMsg = `${item.title} ${mode} failed: `;
if(res.status == 403) { if(res.status == 403) {
errorMsg += `This action is not allowed with this security level configuration.`; errorMsg += `This action is not allowed with this security level configuration.`;
} else if(res.status == 404) { } else if(res.status == 404) {
@ -1350,32 +1365,69 @@ export class CustomNodesManager {
break; break;
} }
needRestart = true;
this.grid.setRowSelected(item, false);
item.restart = true;
this.restartMap[item.hash] = true;
this.grid.updateCell(item, "action");
//console.log(res.data);
} }
target.classList.remove("cn-btn-loading"); if(errorMsg) {
if (errorMsg) {
this.showError(errorMsg); this.showError(errorMsg);
show_message("Installation Error:\n"+errorMsg); show_message("Installation Error:\n"+errorMsg);
}
else {
await api.fetchApi('/customnode/queue/start');
}
}
async onInstallStatus(event) {
let self = CustomNodesManager.instance;
if(event.detail.status == 'in_progress') {
const hash = event.detail.target;
const item = self.grid.getRowItemBy("hash", hash);
item.restart = true;
self.restartMap[item.hash] = true;
self.grid.updateCell(item, "action");
}
else if(event.detail.status == 'done') {
self.onInstallCompleted(event.detail);
}
}
async onInstallCompleted(info) {
let result = info.result;
let self = CustomNodesManager.instance;
if(!self.install_context) {
return;
}
const { target, label, mode } = self.install_context;
target.classList.remove("cn-btn-loading");
let errorMsg = "";
for(let hash in result){
let v = result[hash];
const item = self.grid.getRowItemBy("hash", hash);
self.grid.setRowSelected(item, false);
if(v != 'success')
errorMsg += v;
}
if (errorMsg) {
self.showError(errorMsg);
show_message("Installation Error:\n"+errorMsg);
} else { } else {
this.showStatus(`${label} ${list.length} custom node(s) successfully`); self.showStatus(`${label} ${result.length} custom node(s) successfully`);
} }
if (needRestart) { self.showRestart();
this.showRestart(); self.showMessage(`To apply the installed/updated/disabled/enabled custom node, please restart ComfyUI. And refresh browser.`, "red");
this.showMessage(`To apply the installed/updated/disabled/enabled custom node, please restart ComfyUI. And refresh browser.`, "red")
}
infoToast(`[ComfyUI-Manager] All tasks in the queue have been completed.\n${info.done_count}/${info.total_count}`);
self.install_context = undefined;
} }
// =========================================================================================== // ===========================================================================================

View File

@ -1,16 +1,6 @@
import { app } from "../../scripts/app.js"; import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"; import { api } from "../../scripts/api.js";
let double_click_policy = "copy-all";
api.fetchApi('/manager/dbl_click/policy')
.then(response => response.text())
.then(data => set_double_click_policy(data));
export function set_double_click_policy(mode) {
double_click_policy = mode;
}
function addMenuHandler(nodeType, cb) { function addMenuHandler(nodeType, cb) {
const getOpts = nodeType.prototype.getExtraMenuOptions; const getOpts = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = function () { nodeType.prototype.getExtraMenuOptions = function () {
@ -153,62 +143,6 @@ function node_info_copy(src, dest, connect_both, copy_shape) {
app.registerExtension({ app.registerExtension({
name: "Comfy.Manager.NodeFixer", name: "Comfy.Manager.NodeFixer",
async nodeCreated(node, app) {
let orig_dblClick = node.onDblClick;
node.onDblClick = function (e, pos, self) {
orig_dblClick?.apply?.(this, arguments);
if((!node.inputs && !node.outputs) || pos[1] > 0)
return;
switch(double_click_policy) {
case "copy-all":
case "copy-full":
case "copy-input":
{
if(node.inputs?.some(x => x.link != null) || node.outputs?.some(x => x.links != null && x.links.length > 0) )
return;
let src_node = lookup_nearest_nodes(node);
if(src_node)
{
let both_connection = double_click_policy != "copy-input";
let copy_shape = double_click_policy == "copy-full";
node_info_copy(src_node, node, both_connection, copy_shape);
}
}
break;
case "possible-input":
{
let nearest_inputs = lookup_nearest_inputs(node);
if(nearest_inputs)
connect_inputs(nearest_inputs, node);
}
break;
case "dual":
{
if(pos[0] < node.size[0]/2) {
// left: possible-input
let nearest_inputs = lookup_nearest_inputs(node);
if(nearest_inputs)
connect_inputs(nearest_inputs, node);
}
else {
// right: copy-all
if(node.inputs?.some(x => x.link != null) || node.outputs?.some(x => x.links != null && x.links.length > 0) )
return;
let src_node = lookup_nearest_nodes(node);
if(src_node)
node_info_copy(src_node, node, true);
}
}
break;
}
}
},
beforeRegisterNodeDef(nodeType, nodeData, app) { beforeRegisterNodeDef(nodeType, nodeData, app) {
addMenuHandler(nodeType, function (_, options) { addMenuHandler(nodeType, function (_, options) {
options.push({ options.push({

View File

@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-manager" name = "comfyui-manager"
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI." description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
version = "3.11.3" version = "3.12"
license = { file = "LICENSE.txt" } license = { file = "LICENSE.txt" }
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"] dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]