Model mananger UI enhancement (#808)

* Model download via url

* Model download support multiple selection

* update Roadmap
This commit is contained in:
CenFun 2024-06-22 18:29:22 +08:00 committed by GitHub
parent 5bf70b7ef4
commit a94516cdb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 158 additions and 41 deletions

View File

@ -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.

View File

@ -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>'
}

View File

@ -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 {

View File

@ -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();
}