mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-08 21:54:26 +08:00
Model mananger UI enhancement (#808)
* Model download via url * Model download support multiple selection * update Roadmap
This commit is contained in:
parent
5bf70b7ef4
commit
a94516cdb2
@ -392,10 +392,10 @@ When you run the `scan.sh` script:
|
||||
- [ ] Auto migration for custom nodes with changed structures.
|
||||
- [ ] Version control feature for nodes.
|
||||
- [ ] List of currently used custom nodes.
|
||||
- [ ] Download support multiple model download.
|
||||
- [ ] Model download via url.
|
||||
- [x] Download support multiple model download.
|
||||
- [x] Model download via url.
|
||||
- [x] List sorting (custom nodes).
|
||||
- [ ] List sorting (model).
|
||||
- [x] List sorting (model).
|
||||
- [ ] Provides description of node.
|
||||
|
||||
|
||||
|
||||
@ -208,5 +208,6 @@ export const icons = {
|
||||
search: '<svg viewBox="0 0 24 24" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-4.486-4.494M19 10.5a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0"/></svg>',
|
||||
extensions: '<svg viewBox="64 64 896 896" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M843.5 737.4c-12.4-75.2-79.2-129.1-155.3-125.4S550.9 676 546 752c-153.5-4.8-208-40.7-199.1-113.7 3.3-27.3 19.8-41.9 50.1-49 18.4-4.3 38.8-4.9 57.3-3.2 1.7.2 3.5.3 5.2.5 11.3 2.7 22.8 5 34.3 6.8 34.1 5.6 68.8 8.4 101.8 6.6 92.8-5 156-45.9 159.2-132.7 3.1-84.1-54.7-143.7-147.9-183.6-29.9-12.8-61.6-22.7-93.3-30.2-14.3-3.4-26.3-5.7-35.2-7.2-7.9-75.9-71.5-133.8-147.8-134.4S189.7 168 180.5 243.8s40 146.3 114.2 163.9 149.9-23.3 175.7-95.1c9.4 1.7 18.7 3.6 28 5.8 28.2 6.6 56.4 15.4 82.4 26.6 70.7 30.2 109.3 70.1 107.5 119.9-1.6 44.6-33.6 65.2-96.2 68.6-27.5 1.5-57.6-.9-87.3-5.8-8.3-1.4-15.9-2.8-22.6-4.3-3.9-.8-6.6-1.5-7.8-1.8l-3.1-.6c-2.2-.3-5.9-.8-10.7-1.3-25-2.3-52.1-1.5-78.5 4.6-55.2 12.9-93.9 47.2-101.1 105.8-15.7 126.2 78.6 184.7 276 188.9 29.1 70.4 106.4 107.9 179.6 87 73.3-20.9 119.3-93.4 106.9-168.6M329.1 345.2a83.3 83.3 0 1 1 .01-166.61 83.3 83.3 0 0 1-.01 166.61M695.6 845a83.3 83.3 0 1 1 .01-166.61A83.3 83.3 0 0 1 695.6 845"/></svg>',
|
||||
conflicts: '<svg viewBox="0 0 400 400" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m397.2 350.4.2-.2-180-320-.2.2C213.8 24.2 207.4 20 200 20s-13.8 4.2-17.2 10.4l-.2-.2-180 320 .2.2c-1.6 2.8-2.8 6-2.8 9.6 0 11 9 20 20 20h360c11 0 20-9 20-20 0-3.6-1.2-6.8-2.8-9.6M220 340h-40v-40h40zm0-60h-40V120h40z"/></svg>',
|
||||
passed: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426.667 426.667"><path fill="#6AC259" d="M213.333,0C95.518,0,0,95.514,0,213.333s95.518,213.333,213.333,213.333c117.828,0,213.333-95.514,213.333-213.333S331.157,0,213.333,0z M174.199,322.918l-93.935-93.931l31.309-31.309l62.626,62.622l140.894-140.898l31.309,31.309L174.199,322.918z"/></svg>'
|
||||
passed: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426.667 426.667"><path fill="#6AC259" d="M213.333,0C95.518,0,0,95.514,0,213.333s95.518,213.333,213.333,213.333c117.828,0,213.333-95.514,213.333-213.333S331.157,0,213.333,0z M174.199,322.918l-93.935-93.931l31.309-31.309l62.626,62.622l140.894-140.898l31.309,31.309L174.199,322.918z"/></svg>',
|
||||
download: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" width="100%" height="100%" viewBox="0 0 32 32"><path fill="currentColor" d="M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10l10-10z"></path></svg>'
|
||||
}
|
||||
@ -161,6 +161,7 @@ const pageCss = `
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
filter: drop-shadow(2px 5px 5px rgb(0 0 0 / 30%));
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.cn-manager-grid .cn-extensions-list {
|
||||
@ -255,7 +256,7 @@ const pageCss = `
|
||||
left: 0;
|
||||
}
|
||||
100% {
|
||||
left: -100px;
|
||||
left: -105px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,7 +281,7 @@ const pageCss = `
|
||||
transparent 10px,
|
||||
transparent 15px
|
||||
);
|
||||
animation: cn-btn-loading-bg 3s linear infinite;
|
||||
animation: cn-btn-loading-bg 2s linear infinite;
|
||||
}
|
||||
|
||||
.cn-manager-light .cn-node-name a {
|
||||
|
||||
@ -87,6 +87,13 @@ const pageCss = `
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cmm-manager-selection {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cmm-manager-message {
|
||||
|
||||
}
|
||||
@ -126,7 +133,6 @@ const pageCss = `
|
||||
position: absolute;
|
||||
left: calc(50% - 10px);
|
||||
top: calc(50% - 10px);
|
||||
|
||||
}
|
||||
|
||||
.cmm-manager .cmm-btn-enable {
|
||||
@ -144,12 +150,31 @@ const pageCss = `
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cmm-btn-download {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
position: absolute;
|
||||
left: calc(50% - 10px);
|
||||
top: calc(50% - 10px);
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cmm-btn-download:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cmm-manager-light .cmm-btn-download {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
@keyframes cmm-btn-loading-bg {
|
||||
0% {
|
||||
left: 0;
|
||||
}
|
||||
100% {
|
||||
left: -100px;
|
||||
left: -105px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,7 +199,7 @@ const pageCss = `
|
||||
transparent 10px,
|
||||
transparent 15px
|
||||
);
|
||||
animation: cmm-btn-loading-bg 3s linear infinite;
|
||||
animation: cmm-btn-loading-bg 2s linear infinite;
|
||||
}
|
||||
|
||||
.cmm-manager-light .cmm-node-name a {
|
||||
@ -207,6 +232,7 @@ const pageHtml = `
|
||||
<div class="cmm-flex-auto"></div>
|
||||
</div>
|
||||
<div class="cmm-manager-grid"></div>
|
||||
<div class="cmm-manager-selection"></div>
|
||||
<div class="cmm-manager-message"></div>
|
||||
<div class="cmm-manager-footer">
|
||||
<button class="cmm-manager-close">Close</button>
|
||||
@ -329,6 +355,16 @@ export class ModelManager {
|
||||
focus: (e) => e.target.select()
|
||||
},
|
||||
|
||||
".cmm-manager-selection": {
|
||||
click: (e) => {
|
||||
const target = e.target;
|
||||
const mode = target.getAttribute("mode");
|
||||
if (mode === "install") {
|
||||
this.installModels(this.selectedModels, target);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
".cmm-manager-close": {
|
||||
click: (e) => this.close()
|
||||
},
|
||||
@ -358,6 +394,10 @@ export class ModelManager {
|
||||
|
||||
this.showStatus(`${grid.viewRows.length.toLocaleString()} external models`);
|
||||
|
||||
});
|
||||
|
||||
grid.bind('onSelectChanged', (e, changes) => {
|
||||
this.renderSelected();
|
||||
});
|
||||
|
||||
grid.bind('onClick', (e, d) => {
|
||||
@ -365,7 +405,7 @@ export class ModelManager {
|
||||
const target = d.e.target;
|
||||
const mode = target.getAttribute("mode");
|
||||
if (mode === "install") {
|
||||
this.installModel(rowItem, target);
|
||||
this.installModels([rowItem], target);
|
||||
}
|
||||
|
||||
});
|
||||
@ -373,6 +413,10 @@ export class ModelManager {
|
||||
grid.setOption({
|
||||
theme: 'dark',
|
||||
|
||||
selectVisible: true,
|
||||
selectMultiple: true,
|
||||
selectAllVisible: true,
|
||||
|
||||
textSelectable: true,
|
||||
scrollbarRound: true,
|
||||
|
||||
@ -450,7 +494,7 @@ export class ModelManager {
|
||||
}
|
||||
}, {
|
||||
id: 'installed',
|
||||
name: 'Download',
|
||||
name: 'Install',
|
||||
width: 130,
|
||||
minWidth: 110,
|
||||
maxWidth: 200,
|
||||
@ -465,6 +509,15 @@ export class ModelManager {
|
||||
}
|
||||
return `<button class="cmm-btn-install" mode="install">Install</button>`;
|
||||
}
|
||||
}, {
|
||||
id: 'url',
|
||||
name: '',
|
||||
width: 50,
|
||||
sortable: false,
|
||||
align: 'center',
|
||||
formatter: (url, rowItem, columnItem) => {
|
||||
return `<a class="cmm-btn-download" title="Download file" href="${url}" target="_blank">${icons.download}</a>`;
|
||||
}
|
||||
}, {
|
||||
id: 'type',
|
||||
name: 'Type',
|
||||
@ -478,14 +531,14 @@ export class ModelManager {
|
||||
width: 400,
|
||||
maxWidth: 5000,
|
||||
classMap: 'cmm-node-desc'
|
||||
}, {
|
||||
id: 'filename',
|
||||
name: 'Filename',
|
||||
width: 200
|
||||
}, {
|
||||
id: "save_path",
|
||||
name: 'Save Path',
|
||||
width: 200
|
||||
}, {
|
||||
id: 'filename',
|
||||
name: 'Filename',
|
||||
width: 200
|
||||
}];
|
||||
|
||||
this.grid.setData({
|
||||
@ -506,36 +559,88 @@ export class ModelManager {
|
||||
|
||||
// ===========================================================================================
|
||||
|
||||
async installModel(item, btn) {
|
||||
|
||||
this.showLoading();
|
||||
this.showError("");
|
||||
|
||||
this.showStatus(`Install ${item.name} ...`);
|
||||
btn.classList.add("cmm-btn-loading");
|
||||
|
||||
const data = item.originalData;
|
||||
const res = await fetchData('/model/install', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
|
||||
if (res.error) {
|
||||
const errorMsg = `Install failed: ${item.name} ${res.error.message}`;
|
||||
this.showError(errorMsg);
|
||||
this.hideLoading();
|
||||
renderSelected() {
|
||||
const selectedList = this.grid.getSelectedRows();
|
||||
if (!selectedList.length) {
|
||||
this.showSelection("");
|
||||
this.selectedModels = [];
|
||||
return;
|
||||
}
|
||||
|
||||
item.refresh = true;
|
||||
this.grid.updateCell(item, "installed");
|
||||
this.selectedModels = selectedList;
|
||||
this.showSelection(`<span>Selected <b>${selectedList.length}</b> models <button class="cmm-btn-install" mode="install">Install</button>`);
|
||||
}
|
||||
|
||||
focusInstall(item) {
|
||||
const cellNode = this.grid.getCellNode(item, "installed");
|
||||
if (cellNode) {
|
||||
const cellBtn = cellNode.querySelector(`button[mode="install"]`);
|
||||
if (cellBtn) {
|
||||
cellBtn.classList.add("cmm-btn-loading");
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async installModels(list, btn) {
|
||||
|
||||
btn.classList.add("cmm-btn-loading");
|
||||
this.showLoading();
|
||||
this.showError("");
|
||||
|
||||
let needRestart = false;
|
||||
let errorMsg = "";
|
||||
|
||||
for (const item of list) {
|
||||
|
||||
this.grid.scrollRowIntoView(item);
|
||||
|
||||
if (!this.focusInstall(item)) {
|
||||
this.grid.onNextUpdated(() => {
|
||||
this.focusInstall(item);
|
||||
});
|
||||
}
|
||||
|
||||
this.showStatus(`Install ${item.name} ...`);
|
||||
|
||||
const data = item.originalData;
|
||||
const res = await fetchData('/model/install', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
|
||||
if (res.error) {
|
||||
errorMsg = `Install failed: ${item.name} ${res.error.message}`;
|
||||
break;;
|
||||
}
|
||||
|
||||
needRestart = true;
|
||||
|
||||
this.grid.setRowSelected(item, false);
|
||||
|
||||
item.refresh = true;
|
||||
item.selectable = false;
|
||||
this.grid.updateCell(item, "installed");
|
||||
this.grid.updateCell(item, "tg-column-select");
|
||||
|
||||
this.showStatus(`Install ${item.name} successfully`);
|
||||
|
||||
}
|
||||
|
||||
this.hideLoading();
|
||||
btn.classList.remove("cmm-btn-loading");
|
||||
|
||||
this.showStatus(`Install ${item.name} successfully`);
|
||||
this.showMessage(`To apply the installed model, please click the 'Refresh' button on the main menu.`, "red")
|
||||
if (errorMsg) {
|
||||
this.showError(errorMsg);
|
||||
} else {
|
||||
this.showStatus(`Install ${list.length} models successfully`);
|
||||
}
|
||||
|
||||
if (needRestart) {
|
||||
this.showMessage(`To apply the installed model, please click the 'Refresh' button on the main menu.`, "red")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -545,11 +650,15 @@ export class ModelManager {
|
||||
const baseMap = new Map();
|
||||
|
||||
models.forEach((item, i) => {
|
||||
const { type, base, name, reference } = item;
|
||||
const { type, base, name, reference, installed } = item;
|
||||
item.originalData = JSON.parse(JSON.stringify(item));
|
||||
item.hash = md5(name + reference);
|
||||
item.id = i + 1;
|
||||
|
||||
if (installed === "True") {
|
||||
item.selectable = false;
|
||||
}
|
||||
|
||||
typeMap.set(type, type);
|
||||
baseMap.set(base, base);
|
||||
|
||||
@ -631,6 +740,10 @@ export class ModelManager {
|
||||
|
||||
// ===========================================================================================
|
||||
|
||||
showSelection(msg) {
|
||||
this.element.querySelector(".cmm-manager-selection").innerHTML = msg;
|
||||
}
|
||||
|
||||
showError(err) {
|
||||
this.showMessage(err, "red");
|
||||
}
|
||||
@ -674,7 +787,8 @@ export class ModelManager {
|
||||
const list = [
|
||||
".cmm-manager-header input",
|
||||
".cmm-manager-header select",
|
||||
".cmm-manager-footer button"
|
||||
".cmm-manager-footer button",
|
||||
".cmm-manager-selection button"
|
||||
].map(s => {
|
||||
return Array.from(this.element.querySelectorAll(s));
|
||||
})
|
||||
@ -705,6 +819,7 @@ export class ModelManager {
|
||||
show() {
|
||||
this.element.style.display = "flex";
|
||||
this.setKeywords("");
|
||||
this.showSelection("");
|
||||
this.showMessage("");
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user