mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-08 21:54:26 +08:00
Better model manager UI (#802)
* fixed row height with lib API * update grid lib * UI adjustment * move some of api to common utils * added model manager * update install * replace model-downloader with model-manager * replace model-downloader with model-manager * fixed filter * fixed status * apply loading animation for install button * sort type and base
This commit is contained in:
parent
f623e3fb01
commit
651496cae0
@ -12,8 +12,8 @@ import {
|
|||||||
} from "./comfyui-share-common.js";
|
} from "./comfyui-share-common.js";
|
||||||
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
||||||
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
||||||
|
import { ModelManager } from "./model-manager.js";
|
||||||
import { SnapshotManager } from "./snapshot.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, show_message } from "./common.js";
|
import { manager_instance, setManagerInstance, install_via_git_url, install_pip, rebootAPI, free_models, show_message } from "./common.js";
|
||||||
import { ComponentBuilderDialog, load_components, set_component_policy, getPureName } from "./components-manager.js";
|
import { ComponentBuilderDialog, load_components, set_component_policy, getPureName } from "./components-manager.js";
|
||||||
import { set_double_click_policy } from "./node_fixer.js";
|
import { set_double_click_policy } from "./node_fixer.js";
|
||||||
@ -736,14 +736,16 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
||||||
$el("button.cm-button", {
|
$el("button.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Install Models",
|
textContent: "Model Manager",
|
||||||
onclick:
|
onclick:
|
||||||
() => {
|
() => {
|
||||||
if(!ModelInstaller.instance)
|
if(!ModelManager.instance) {
|
||||||
ModelInstaller.instance = new ModelInstaller(app, self);
|
ModelManager.instance = new ModelManager(app, self);
|
||||||
ModelInstaller.instance.show();
|
}
|
||||||
|
ModelManager.instance.show();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
91
js/common.js
91
js/common.js
@ -119,3 +119,94 @@ export async function free_models() {
|
|||||||
show_message('Unloading of models failed.<BR><BR>Installed ComfyUI may be an outdated version.')
|
show_message('Unloading of models failed.<BR><BR>Installed ComfyUI may be an outdated version.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function md5(inputString) {
|
||||||
|
const hc = '0123456789abcdef';
|
||||||
|
const rh = n => {let j,s='';for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
|
||||||
|
const ad = (x,y) => {let l=(x&0xFFFF)+(y&0xFFFF);let m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);}
|
||||||
|
const rl = (n,c) => (n<<c)|(n>>>(32-c));
|
||||||
|
const cm = (q,a,b,x,s,t) => ad(rl(ad(ad(a,q),ad(x,t)),s),b);
|
||||||
|
const ff = (a,b,c,d,x,s,t) => cm((b&c)|((~b)&d),a,b,x,s,t);
|
||||||
|
const gg = (a,b,c,d,x,s,t) => cm((b&d)|(c&(~d)),a,b,x,s,t);
|
||||||
|
const hh = (a,b,c,d,x,s,t) => cm(b^c^d,a,b,x,s,t);
|
||||||
|
const ii = (a,b,c,d,x,s,t) => cm(c^(b|(~d)),a,b,x,s,t);
|
||||||
|
const sb = x => {
|
||||||
|
let i;const nblk=((x.length+8)>>6)+1;const blks=[];for(i=0;i<nblk*16;i++) { blks[i]=0 };
|
||||||
|
for(i=0;i<x.length;i++) {blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8);}
|
||||||
|
blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks;
|
||||||
|
}
|
||||||
|
let i,x=sb(inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd;
|
||||||
|
for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d;
|
||||||
|
a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17, 606105819);
|
||||||
|
b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426);
|
||||||
|
c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22, -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416);
|
||||||
|
d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17, -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162);
|
||||||
|
a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12, -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290);
|
||||||
|
b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632);
|
||||||
|
c=gg(c,d,a,b,x[i+11],14, 643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691);
|
||||||
|
d=gg(d,a,b,c,x[i+10], 9, 38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848);
|
||||||
|
a=gg(a,b,c,d,x[i+ 9], 5, 568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961);
|
||||||
|
b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9, -51403784);
|
||||||
|
c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4, -378558);
|
||||||
|
d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23, -35309556);
|
||||||
|
a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632);
|
||||||
|
b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4, 681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222);
|
||||||
|
c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23, 76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487);
|
||||||
|
d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16, 530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651);
|
||||||
|
a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905);
|
||||||
|
b=ii(b,c,d,a,x[i+ 5],21, -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606);
|
||||||
|
c=ii(c,d,a,b,x[i+10],15, -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359);
|
||||||
|
d=ii(d,a,b,c,x[i+15],10, -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649);
|
||||||
|
a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15, 718787259);
|
||||||
|
b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd);
|
||||||
|
}
|
||||||
|
return rh(a)+rh(b)+rh(c)+rh(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchData(route, options) {
|
||||||
|
let err;
|
||||||
|
const res = await api.fetchApi(route, options).catch(e => {
|
||||||
|
err = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res) {
|
||||||
|
return {
|
||||||
|
status: 400,
|
||||||
|
error: new Error("Unknown Error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { status, statusText } = res;
|
||||||
|
if (err) {
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
error: err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status !== 200) {
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
error: new Error(statusText || "Unknown Error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (!data) {
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
error: new Error(`Failed to load data: ${route}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>'
|
||||||
|
}
|
||||||
@ -1,17 +1,13 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js"
|
|
||||||
import { $el } from "../../scripts/ui.js";
|
import { $el } from "../../scripts/ui.js";
|
||||||
import { manager_instance, rebootAPI, install_via_git_url } from "./common.js";
|
import {
|
||||||
|
manager_instance, rebootAPI, install_via_git_url,
|
||||||
|
fetchData, md5, icons
|
||||||
|
} from "./common.js";
|
||||||
|
|
||||||
// https://cenfun.github.io/turbogrid/api.html
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
import TG from "./turbogrid.esm.js";
|
import TG from "./turbogrid.esm.js";
|
||||||
|
|
||||||
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>'
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageCss = `
|
const pageCss = `
|
||||||
.cn-manager {
|
.cn-manager {
|
||||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
@ -123,6 +119,7 @@ const pageCss = `
|
|||||||
.cn-manager-grid .cn-node-name a {
|
.cn-manager-grid .cn-node-name a {
|
||||||
color: skyblue;
|
color: skyblue;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager-grid .cn-node-desc a {
|
.cn-manager-grid .cn-node-desc a {
|
||||||
@ -305,7 +302,7 @@ const pageHtml = `
|
|||||||
<label>Filter
|
<label>Filter
|
||||||
<select class="cn-manager-filter"></select>
|
<select class="cn-manager-filter"></select>
|
||||||
</label>
|
</label>
|
||||||
<input class="cn-manager-keywords" type="search" placeholder="input search keyword" />
|
<input class="cn-manager-keywords" type="search" placeholder="Search" />
|
||||||
<div class="cn-manager-status"></div>
|
<div class="cn-manager-status"></div>
|
||||||
<div class="cn-flex-auto"></div>
|
<div class="cn-flex-auto"></div>
|
||||||
<div class="cn-manager-channel"></div>
|
<div class="cn-manager-channel"></div>
|
||||||
@ -613,59 +610,16 @@ export class CustomNodesManager {
|
|||||||
const container = this.element.querySelector(".cn-manager-grid");
|
const container = this.element.querySelector(".cn-manager-grid");
|
||||||
const grid = new TG.Grid(container);
|
const grid = new TG.Grid(container);
|
||||||
this.grid = grid;
|
this.grid = grid;
|
||||||
|
|
||||||
const autoHeightColumns = ['description', "alternatives"];
|
|
||||||
|
|
||||||
let prevViewRowsLength = 0;
|
let prevViewRowsLength = -1;
|
||||||
grid.bind('onUpdated', (e, d) => {
|
grid.bind('onUpdated', (e, d) => {
|
||||||
|
|
||||||
const viewRows = grid.viewRows;
|
const viewRows = grid.viewRows;
|
||||||
if (viewRows.length !== prevViewRowsLength) {
|
if (viewRows.length !== prevViewRowsLength) {
|
||||||
prevViewRowsLength = viewRows.length;
|
prevViewRowsLength = viewRows.length;
|
||||||
this.showStatus(`${prevViewRowsLength} custom nodes`);
|
this.showStatus(`${prevViewRowsLength.toLocaleString()} custom nodes`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibleRowList = grid.viewport.rows;
|
|
||||||
const rows = [];
|
|
||||||
const heights = [];
|
|
||||||
|
|
||||||
visibleRowList.forEach(function(viewIndex) {
|
|
||||||
// display index after filter is no equal global index
|
|
||||||
const rowItem = grid.getViewRowItem(viewIndex);
|
|
||||||
if (rowItem.rowHeightFixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = autoHeightColumns.map(k => {
|
|
||||||
const cellNode = grid.getCellNode(rowItem, k);
|
|
||||||
if (cellNode) {
|
|
||||||
const div = cellNode.querySelector('.tg-multiline-fixing');
|
|
||||||
// 10px is padding top and bottom
|
|
||||||
const realHeight = Math.max(TG.$(div).height() + 10, grid.options.rowHeight);
|
|
||||||
return realHeight;
|
|
||||||
}
|
|
||||||
}).filter(n => n);
|
|
||||||
|
|
||||||
if (list.length) {
|
|
||||||
rowItem.rowHeightFixed = true;
|
|
||||||
rows.push(rowItem);
|
|
||||||
heights.push(Math.max.apply(null, list));
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
if (!rows.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
grid.setRowHeight(rows, heights);
|
|
||||||
});
|
|
||||||
|
|
||||||
grid.bind('onColumnWidthChanged', (e, d) => {
|
|
||||||
if (autoHeightColumns.includes(d.id)) {
|
|
||||||
// reset when column width changed
|
|
||||||
grid.forEachRow(function(row) {
|
|
||||||
row.rowHeightFixed = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
grid.bind('onSelectChanged', (e, changes) => {
|
grid.bind('onSelectChanged', (e, changes) => {
|
||||||
@ -691,10 +645,15 @@ export class CustomNodesManager {
|
|||||||
frozenColumn: 1,
|
frozenColumn: 1,
|
||||||
rowNotFound: "No Results",
|
rowNotFound: "No Results",
|
||||||
|
|
||||||
rowHeight: 30 * 3 + 3 * 4,
|
rowHeight: 40,
|
||||||
bindWindowResize: true,
|
bindWindowResize: true,
|
||||||
bindContainerResize: true,
|
bindContainerResize: true,
|
||||||
|
|
||||||
|
cellResizeObserver: (rowItem, columnItem) => {
|
||||||
|
const autoHeightColumns = ['title', 'installed', 'description', "alternatives"];
|
||||||
|
return autoHeightColumns.includes(columnItem.id)
|
||||||
|
},
|
||||||
|
|
||||||
// updateGrid handler for filter and keywords
|
// updateGrid handler for filter and keywords
|
||||||
rowFilter: (rowItem) => {
|
rowFilter: (rowItem) => {
|
||||||
|
|
||||||
@ -765,12 +724,10 @@ export class CustomNodesManager {
|
|||||||
width: 200,
|
width: 200,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
classMap: 'tg-multiline cn-node-name',
|
classMap: 'cn-node-name',
|
||||||
formatter: (title, rowItem, columnItem) => {
|
formatter: (title, rowItem, columnItem) => {
|
||||||
return `<div class="tg-multiline-wrapper">
|
return `${rowItem.installed === 'Fail' ? '<font color="red"><B>(IMPORT FAILED)</B></font>' : ''}
|
||||||
${rowItem.installed === 'Fail' ? '<font color="red"><B>(IMPORT FAILED)</B></font>' : ''}
|
<a href=${rowItem.reference} target="_blank"><b>${title}</b></a>`;
|
||||||
<a href=${rowItem.reference} target="_blank"><b>${title}</b></a>
|
|
||||||
</div>`;
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'installed',
|
id: 'installed',
|
||||||
@ -793,19 +750,13 @@ export class CustomNodesManager {
|
|||||||
width: 400,
|
width: 400,
|
||||||
maxWidth: 5000,
|
maxWidth: 5000,
|
||||||
invisible: !this.hasAlternatives(),
|
invisible: !this.hasAlternatives(),
|
||||||
classMap: 'tg-multiline cn-node-desc',
|
classMap: 'cn-node-desc'
|
||||||
formatter: (alternatives, rowItem, columnItem) => {
|
|
||||||
return `<div class="tg-multiline-fixing">${alternatives}</div>`;
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
id: 'description',
|
id: 'description',
|
||||||
name: 'Description',
|
name: 'Description',
|
||||||
width: 400,
|
width: 400,
|
||||||
maxWidth: 5000,
|
maxWidth: 5000,
|
||||||
classMap: 'tg-multiline cn-node-desc',
|
classMap: 'cn-node-desc'
|
||||||
formatter: (description, rowItem, columnItem) => {
|
|
||||||
return `<div class="tg-multiline-fixing">${description}</div>`;
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
id: "extensions",
|
id: "extensions",
|
||||||
name: "Extensions",
|
name: "Extensions",
|
||||||
@ -856,7 +807,7 @@ export class CustomNodesManager {
|
|||||||
}, {
|
}, {
|
||||||
id: 'author',
|
id: 'author',
|
||||||
name: 'Author',
|
name: 'Author',
|
||||||
width: 100,
|
width: 120,
|
||||||
classMap: "cn-node-author",
|
classMap: "cn-node-author",
|
||||||
formatter: (author, rowItem, columnItem) => {
|
formatter: (author, rowItem, columnItem) => {
|
||||||
if (rowItem.trust) {
|
if (rowItem.trust) {
|
||||||
@ -907,9 +858,6 @@ export class CustomNodesManager {
|
|||||||
if (this.grid) {
|
if (this.grid) {
|
||||||
this.grid.update();
|
this.grid.update();
|
||||||
if (this.hasAlternatives()) {
|
if (this.hasAlternatives()) {
|
||||||
this.grid.forEachRow(function(row) {
|
|
||||||
row.rowHeightFixed = false;
|
|
||||||
});
|
|
||||||
this.grid.showColumn("alternatives");
|
this.grid.showColumn("alternatives");
|
||||||
} else {
|
} else {
|
||||||
this.grid.hideColumn("alternatives");
|
this.grid.hideColumn("alternatives");
|
||||||
@ -1000,7 +948,7 @@ export class CustomNodesManager {
|
|||||||
this.showStatus(`${label} ${item.title} ...`);
|
this.showStatus(`${label} ${item.title} ...`);
|
||||||
|
|
||||||
const data = item.originalData;
|
const data = item.originalData;
|
||||||
const res = await this.fetchData(`/customnode/${mode}`, {
|
const res = await fetchData(`/customnode/${mode}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
@ -1049,53 +997,10 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
// ===========================================================================================
|
// ===========================================================================================
|
||||||
|
|
||||||
async fetchData(route, options) {
|
|
||||||
let err;
|
|
||||||
const res = await api.fetchApi(route, options).catch(e => {
|
|
||||||
err = e;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res) {
|
|
||||||
return {
|
|
||||||
status: 400,
|
|
||||||
error: new Error("Unknown Error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { status, statusText } = res;
|
|
||||||
if (err) {
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
error: err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status !== 200) {
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
error: new Error(statusText || "Unknown Error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
if (!data) {
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
error: new Error(`Failed to load data: ${route}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
status,
|
|
||||||
data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===========================================================================================
|
|
||||||
|
|
||||||
async getExtensionMappings() {
|
async getExtensionMappings() {
|
||||||
const mode = manager_instance.datasrc_combo.value;
|
const mode = manager_instance.datasrc_combo.value;
|
||||||
this.showStatus(`Loading extension mappings (${mode}) ...`);
|
this.showStatus(`Loading extension mappings (${mode}) ...`);
|
||||||
const res = await this.fetchData(`/customnode/getmappings?mode=${mode}`);
|
const res = await fetchData(`/customnode/getmappings?mode=${mode}`);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
console.log(res.error);
|
console.log(res.error);
|
||||||
return {}
|
return {}
|
||||||
@ -1149,7 +1054,7 @@ export class CustomNodesManager {
|
|||||||
async getMissingNodes() {
|
async getMissingNodes() {
|
||||||
const mode = manager_instance.datasrc_combo.value;
|
const mode = manager_instance.datasrc_combo.value;
|
||||||
this.showStatus(`Loading missing nodes (${mode}) ...`);
|
this.showStatus(`Loading missing nodes (${mode}) ...`);
|
||||||
const res = await this.fetchData(`/customnode/getmappings?mode=${mode}`);
|
const res = await fetchData(`/customnode/getmappings?mode=${mode}`);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.showError(`Failed to get custom node mappings: ${res.error}`);
|
this.showError(`Failed to get custom node mappings: ${res.error}`);
|
||||||
return;
|
return;
|
||||||
@ -1219,7 +1124,7 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resUnresolved = await this.fetchData(`/component/get_unresolved`);
|
const resUnresolved = await fetchData(`/component/get_unresolved`);
|
||||||
const unresolved = resUnresolved.data;
|
const unresolved = resUnresolved.data;
|
||||||
if (unresolved && unresolved.nodes) {
|
if (unresolved && unresolved.nodes) {
|
||||||
unresolved.nodes.forEach(node_type => {
|
unresolved.nodes.forEach(node_type => {
|
||||||
@ -1243,7 +1148,7 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
const mode = manager_instance.datasrc_combo.value;
|
const mode = manager_instance.datasrc_combo.value;
|
||||||
this.showStatus(`Loading alternatives (${mode}) ...`);
|
this.showStatus(`Loading alternatives (${mode}) ...`);
|
||||||
const res = await this.fetchData(`/customnode/alternatives?mode=${mode}`);
|
const res = await fetchData(`/customnode/alternatives?mode=${mode}`);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.showError(`Failed to get alternatives: ${res.error}`);
|
this.showError(`Failed to get alternatives: ${res.error}`);
|
||||||
return [];
|
return [];
|
||||||
@ -1285,7 +1190,7 @@ export class CustomNodesManager {
|
|||||||
this.showStatus(`Loading custom nodes (${mode}) ...`);
|
this.showStatus(`Loading custom nodes (${mode}) ...`);
|
||||||
|
|
||||||
const skip_update = this.show_mode === ShowMode.UPDATE ? "" : "&skip_update=true";
|
const skip_update = this.show_mode === ShowMode.UPDATE ? "" : "&skip_update=true";
|
||||||
const res = await this.fetchData(`/customnode/getlist?mode=${mode}${skip_update}`);
|
const res = await fetchData(`/customnode/getlist?mode=${mode}${skip_update}`);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.showError("Failed to get custom node list.");
|
this.showError("Failed to get custom node list.");
|
||||||
this.hideLoading();
|
this.hideLoading();
|
||||||
@ -1302,7 +1207,8 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
for (const item of custom_nodes) {
|
for (const item of custom_nodes) {
|
||||||
item.originalData = JSON.parse(JSON.stringify(item));
|
item.originalData = JSON.parse(JSON.stringify(item));
|
||||||
item.hash = await calculateHash(item);
|
const message = item.title + item.files[0];
|
||||||
|
item.hash = md5(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterItem = this.getFilterItem(this.show_mode);
|
const filterItem = this.getFilterItem(this.show_mode);
|
||||||
@ -1471,54 +1377,4 @@ export class CustomNodesManager {
|
|||||||
close() {
|
close() {
|
||||||
this.element.style.display = "none";
|
this.element.style.display = "none";
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ===========================================================================================
|
|
||||||
|
|
||||||
async function calculateHash(item) {
|
|
||||||
const message = item.title + item.files[0];
|
|
||||||
return md5(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function md5(inputString) {
|
|
||||||
const hc = '0123456789abcdef';
|
|
||||||
const rh = n => {let j,s='';for(j=0;j<=3;j++) s+=hc.charAt((n>>(j*8+4))&0x0F)+hc.charAt((n>>(j*8))&0x0F);return s;}
|
|
||||||
const ad = (x,y) => {let l=(x&0xFFFF)+(y&0xFFFF);let m=(x>>16)+(y>>16)+(l>>16);return (m<<16)|(l&0xFFFF);}
|
|
||||||
const rl = (n,c) => (n<<c)|(n>>>(32-c));
|
|
||||||
const cm = (q,a,b,x,s,t) => ad(rl(ad(ad(a,q),ad(x,t)),s),b);
|
|
||||||
const ff = (a,b,c,d,x,s,t) => cm((b&c)|((~b)&d),a,b,x,s,t);
|
|
||||||
const gg = (a,b,c,d,x,s,t) => cm((b&d)|(c&(~d)),a,b,x,s,t);
|
|
||||||
const hh = (a,b,c,d,x,s,t) => cm(b^c^d,a,b,x,s,t);
|
|
||||||
const ii = (a,b,c,d,x,s,t) => cm(c^(b|(~d)),a,b,x,s,t);
|
|
||||||
const sb = x => {
|
|
||||||
let i;const nblk=((x.length+8)>>6)+1;const blks=[];for(i=0;i<nblk*16;i++) { blks[i]=0 };
|
|
||||||
for(i=0;i<x.length;i++) {blks[i>>2]|=x.charCodeAt(i)<<((i%4)*8);}
|
|
||||||
blks[i>>2]|=0x80<<((i%4)*8);blks[nblk*16-2]=x.length*8;return blks;
|
|
||||||
}
|
|
||||||
let i,x=sb(inputString),a=1732584193,b=-271733879,c=-1732584194,d=271733878,olda,oldb,oldc,oldd;
|
|
||||||
for(i=0;i<x.length;i+=16) {olda=a;oldb=b;oldc=c;oldd=d;
|
|
||||||
a=ff(a,b,c,d,x[i+ 0], 7, -680876936);d=ff(d,a,b,c,x[i+ 1],12, -389564586);c=ff(c,d,a,b,x[i+ 2],17, 606105819);
|
|
||||||
b=ff(b,c,d,a,x[i+ 3],22,-1044525330);a=ff(a,b,c,d,x[i+ 4], 7, -176418897);d=ff(d,a,b,c,x[i+ 5],12, 1200080426);
|
|
||||||
c=ff(c,d,a,b,x[i+ 6],17,-1473231341);b=ff(b,c,d,a,x[i+ 7],22, -45705983);a=ff(a,b,c,d,x[i+ 8], 7, 1770035416);
|
|
||||||
d=ff(d,a,b,c,x[i+ 9],12,-1958414417);c=ff(c,d,a,b,x[i+10],17, -42063);b=ff(b,c,d,a,x[i+11],22,-1990404162);
|
|
||||||
a=ff(a,b,c,d,x[i+12], 7, 1804603682);d=ff(d,a,b,c,x[i+13],12, -40341101);c=ff(c,d,a,b,x[i+14],17,-1502002290);
|
|
||||||
b=ff(b,c,d,a,x[i+15],22, 1236535329);a=gg(a,b,c,d,x[i+ 1], 5, -165796510);d=gg(d,a,b,c,x[i+ 6], 9,-1069501632);
|
|
||||||
c=gg(c,d,a,b,x[i+11],14, 643717713);b=gg(b,c,d,a,x[i+ 0],20, -373897302);a=gg(a,b,c,d,x[i+ 5], 5, -701558691);
|
|
||||||
d=gg(d,a,b,c,x[i+10], 9, 38016083);c=gg(c,d,a,b,x[i+15],14, -660478335);b=gg(b,c,d,a,x[i+ 4],20, -405537848);
|
|
||||||
a=gg(a,b,c,d,x[i+ 9], 5, 568446438);d=gg(d,a,b,c,x[i+14], 9,-1019803690);c=gg(c,d,a,b,x[i+ 3],14, -187363961);
|
|
||||||
b=gg(b,c,d,a,x[i+ 8],20, 1163531501);a=gg(a,b,c,d,x[i+13], 5,-1444681467);d=gg(d,a,b,c,x[i+ 2], 9, -51403784);
|
|
||||||
c=gg(c,d,a,b,x[i+ 7],14, 1735328473);b=gg(b,c,d,a,x[i+12],20,-1926607734);a=hh(a,b,c,d,x[i+ 5], 4, -378558);
|
|
||||||
d=hh(d,a,b,c,x[i+ 8],11,-2022574463);c=hh(c,d,a,b,x[i+11],16, 1839030562);b=hh(b,c,d,a,x[i+14],23, -35309556);
|
|
||||||
a=hh(a,b,c,d,x[i+ 1], 4,-1530992060);d=hh(d,a,b,c,x[i+ 4],11, 1272893353);c=hh(c,d,a,b,x[i+ 7],16, -155497632);
|
|
||||||
b=hh(b,c,d,a,x[i+10],23,-1094730640);a=hh(a,b,c,d,x[i+13], 4, 681279174);d=hh(d,a,b,c,x[i+ 0],11, -358537222);
|
|
||||||
c=hh(c,d,a,b,x[i+ 3],16, -722521979);b=hh(b,c,d,a,x[i+ 6],23, 76029189);a=hh(a,b,c,d,x[i+ 9], 4, -640364487);
|
|
||||||
d=hh(d,a,b,c,x[i+12],11, -421815835);c=hh(c,d,a,b,x[i+15],16, 530742520);b=hh(b,c,d,a,x[i+ 2],23, -995338651);
|
|
||||||
a=ii(a,b,c,d,x[i+ 0], 6, -198630844);d=ii(d,a,b,c,x[i+ 7],10, 1126891415);c=ii(c,d,a,b,x[i+14],15,-1416354905);
|
|
||||||
b=ii(b,c,d,a,x[i+ 5],21, -57434055);a=ii(a,b,c,d,x[i+12], 6, 1700485571);d=ii(d,a,b,c,x[i+ 3],10,-1894986606);
|
|
||||||
c=ii(c,d,a,b,x[i+10],15, -1051523);b=ii(b,c,d,a,x[i+ 1],21,-2054922799);a=ii(a,b,c,d,x[i+ 8], 6, 1873313359);
|
|
||||||
d=ii(d,a,b,c,x[i+15],10, -30611744);c=ii(c,d,a,b,x[i+ 6],15,-1560198380);b=ii(b,c,d,a,x[i+13],21, 1309151649);
|
|
||||||
a=ii(a,b,c,d,x[i+ 4], 6, -145523070);d=ii(d,a,b,c,x[i+11],10,-1120210379);c=ii(c,d,a,b,x[i+ 2],15, 718787259);
|
|
||||||
b=ii(b,c,d,a,x[i+ 9],21, -343485551);a=ad(a,olda);b=ad(b,oldb);c=ad(c,oldc);d=ad(d,oldd);
|
|
||||||
}
|
|
||||||
return rh(a)+rh(b)+rh(c)+rh(d);
|
|
||||||
}
|
}
|
||||||
@ -1,388 +0,0 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
|
||||||
import { api } from "../../scripts/api.js"
|
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
|
||||||
import { manager_instance, rebootAPI, show_message } from "./common.js";
|
|
||||||
|
|
||||||
async function install_model(target) {
|
|
||||||
if(ModelInstaller.instance) {
|
|
||||||
ModelInstaller.instance.startInstall(target);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await api.fetchApi('/model/install', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(target)
|
|
||||||
});
|
|
||||||
|
|
||||||
const status = await response.json();
|
|
||||||
app.ui.dialog.close();
|
|
||||||
target.installed = 'True';
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch(exception) {
|
|
||||||
show_message(`Install failed: ${target.title} / ${exception}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
await ModelInstaller.instance.invalidateControl();
|
|
||||||
ModelInstaller.instance.updateMessage("<BR>To apply the installed model, please click the 'Refresh' button on the main menu.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getModelList() {
|
|
||||||
var mode = manager_instance.datasrc_combo.value;
|
|
||||||
|
|
||||||
const response = await api.fetchApi(`/externalmodel/getlist?mode=${mode}`);
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ModelInstaller extends ComfyDialog {
|
|
||||||
static instance = null;
|
|
||||||
|
|
||||||
install_buttons = [];
|
|
||||||
message_box = null;
|
|
||||||
data = null;
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.install_buttons = [];
|
|
||||||
this.message_box = null;
|
|
||||||
this.data = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(app, manager_dialog) {
|
|
||||||
super();
|
|
||||||
this.manager_dialog = manager_dialog;
|
|
||||||
this.search_keyword = '';
|
|
||||||
this.element = $el("div.comfy-modal", { parent: document.body }, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
createControls() {
|
|
||||||
return [
|
|
||||||
$el("button.cm-small-button", {
|
|
||||||
type: "button",
|
|
||||||
textContent: "Close",
|
|
||||||
onclick: () => { this.close(); }
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
startInstall(target) {
|
|
||||||
const self = ModelInstaller.instance;
|
|
||||||
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_searchbox(data) {
|
|
||||||
let keyword = this.search_box.value.toLowerCase();
|
|
||||||
for(let i in this.grid_rows) {
|
|
||||||
let data = this.grid_rows[i].data;
|
|
||||||
let content = data.name.toLowerCase() + data.type.toLowerCase() + data.base.toLowerCase() + data.filename.toLowerCase() + data.description.toLowerCase();
|
|
||||||
|
|
||||||
if(this.filter && this.filter != '*') {
|
|
||||||
if(this.filter != data.installed) {
|
|
||||||
this.grid_rows[i].control.style.display = 'none';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(keyword == "")
|
|
||||||
this.grid_rows[i].control.style.display = null;
|
|
||||||
else if(content.includes(keyword)) {
|
|
||||||
this.grid_rows[i].control.style.display = null;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.grid_rows[i].control.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async invalidateControl() {
|
|
||||||
this.clear();
|
|
||||||
this.data = (await getModelList()).models;
|
|
||||||
|
|
||||||
while (this.element.children.length) {
|
|
||||||
this.element.removeChild(this.element.children[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.createHeaderControls();
|
|
||||||
|
|
||||||
if(this.search_keyword) {
|
|
||||||
this.search_box.value = this.search_keyword;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.createGrid();
|
|
||||||
await this.createBottomControls();
|
|
||||||
|
|
||||||
this.apply_searchbox(this.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMessage(msg, btn_id) {
|
|
||||||
this.message_box.innerHTML = msg;
|
|
||||||
if(btn_id) {
|
|
||||||
const rebootButton = document.getElementById(btn_id);
|
|
||||||
const self = this;
|
|
||||||
rebootButton.addEventListener("click",
|
|
||||||
function() {
|
|
||||||
if(rebootAPI()) {
|
|
||||||
self.close();
|
|
||||||
self.manager_dialog.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createGrid(models_json) {
|
|
||||||
var grid = document.createElement('table');
|
|
||||||
grid.setAttribute('id', 'external-models-grid');
|
|
||||||
|
|
||||||
var thead = document.createElement('thead');
|
|
||||||
var tbody = document.createElement('tbody');
|
|
||||||
|
|
||||||
var headerRow = document.createElement('tr');
|
|
||||||
thead.style.position = "sticky";
|
|
||||||
thead.style.top = "0px";
|
|
||||||
thead.style.borderCollapse = "collapse";
|
|
||||||
thead.style.tableLayout = "fixed";
|
|
||||||
|
|
||||||
var header1 = document.createElement('th');
|
|
||||||
header1.innerHTML = ' ID ';
|
|
||||||
header1.style.width = "20px";
|
|
||||||
var header2 = document.createElement('th');
|
|
||||||
header2.innerHTML = 'Type';
|
|
||||||
header2.style.width = "100px";
|
|
||||||
var header3 = document.createElement('th');
|
|
||||||
header3.innerHTML = 'Base';
|
|
||||||
header3.style.width = "100px";
|
|
||||||
var header4 = document.createElement('th');
|
|
||||||
header4.innerHTML = 'Name';
|
|
||||||
header4.style.width = "30%";
|
|
||||||
var header5 = document.createElement('th');
|
|
||||||
header5.innerHTML = 'Filename';
|
|
||||||
header5.style.width = "20%";
|
|
||||||
header5.style.tableLayout = "fixed";
|
|
||||||
var header6 = document.createElement('th');
|
|
||||||
header6.innerHTML = 'Description';
|
|
||||||
header6.style.width = "50%";
|
|
||||||
var header_down = document.createElement('th');
|
|
||||||
header_down.innerHTML = 'Download';
|
|
||||||
header_down.style.width = "50px";
|
|
||||||
|
|
||||||
thead.appendChild(headerRow);
|
|
||||||
headerRow.appendChild(header1);
|
|
||||||
headerRow.appendChild(header2);
|
|
||||||
headerRow.appendChild(header3);
|
|
||||||
headerRow.appendChild(header4);
|
|
||||||
headerRow.appendChild(header5);
|
|
||||||
headerRow.appendChild(header6);
|
|
||||||
headerRow.appendChild(header_down);
|
|
||||||
|
|
||||||
headerRow.style.backgroundColor = "Black";
|
|
||||||
headerRow.style.color = "White";
|
|
||||||
headerRow.style.textAlign = "center";
|
|
||||||
headerRow.style.width = "100%";
|
|
||||||
headerRow.style.padding = "0";
|
|
||||||
|
|
||||||
grid.appendChild(thead);
|
|
||||||
grid.appendChild(tbody);
|
|
||||||
|
|
||||||
this.grid_rows = {};
|
|
||||||
|
|
||||||
if(this.data)
|
|
||||||
for (var i = 0; i < this.data.length; i++) {
|
|
||||||
const data = this.data[i];
|
|
||||||
var dataRow = document.createElement('tr');
|
|
||||||
var data1 = document.createElement('td');
|
|
||||||
data1.style.textAlign = "center";
|
|
||||||
data1.innerHTML = i+1;
|
|
||||||
var data2 = document.createElement('td');
|
|
||||||
data2.innerHTML = ` ${data.type}`;
|
|
||||||
var data3 = document.createElement('td');
|
|
||||||
data3.innerHTML = ` ${data.base}`;
|
|
||||||
var data4 = document.createElement('td');
|
|
||||||
data4.className = "cm-node-name";
|
|
||||||
data4.innerHTML = ` <a href=${data.reference} target="_blank"><font color="skyblue"><b>${data.name}</b></font></a>`;
|
|
||||||
var data5 = document.createElement('td');
|
|
||||||
data5.className = "cm-node-filename";
|
|
||||||
data5.innerHTML = ` ${data.filename}`;
|
|
||||||
data5.style.wordBreak = "break-all";
|
|
||||||
var data6 = document.createElement('td');
|
|
||||||
data6.className = "cm-node-desc";
|
|
||||||
data6.innerHTML = data.description;
|
|
||||||
data6.style.wordBreak = "break-all";
|
|
||||||
var data_install = document.createElement('td');
|
|
||||||
var installBtn = document.createElement('button');
|
|
||||||
data_install.style.textAlign = "center";
|
|
||||||
|
|
||||||
installBtn.innerHTML = 'Install';
|
|
||||||
this.install_buttons.push(installBtn);
|
|
||||||
|
|
||||||
switch(data.installed) {
|
|
||||||
case 'True':
|
|
||||||
installBtn.innerHTML = 'Installed';
|
|
||||||
installBtn.style.backgroundColor = 'green';
|
|
||||||
installBtn.style.color = 'white';
|
|
||||||
installBtn.disabled = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
installBtn.innerHTML = 'Install';
|
|
||||||
installBtn.style.backgroundColor = 'black';
|
|
||||||
installBtn.style.color = 'white';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
installBtn.style.width = "100px";
|
|
||||||
|
|
||||||
installBtn.addEventListener('click', function() {
|
|
||||||
install_model(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
data_install.appendChild(installBtn);
|
|
||||||
|
|
||||||
dataRow.style.backgroundColor = "var(--bg-color)";
|
|
||||||
dataRow.style.color = "var(--fg-color)";
|
|
||||||
dataRow.style.textAlign = "left";
|
|
||||||
|
|
||||||
dataRow.appendChild(data1);
|
|
||||||
dataRow.appendChild(data2);
|
|
||||||
dataRow.appendChild(data3);
|
|
||||||
dataRow.appendChild(data4);
|
|
||||||
dataRow.appendChild(data5);
|
|
||||||
dataRow.appendChild(data6);
|
|
||||||
dataRow.appendChild(data_install);
|
|
||||||
tbody.appendChild(dataRow);
|
|
||||||
|
|
||||||
this.grid_rows[i] = {data:data, control:dataRow};
|
|
||||||
}
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
const panel = document.createElement('div');
|
|
||||||
panel.style.width = "100%";
|
|
||||||
panel.appendChild(grid);
|
|
||||||
|
|
||||||
function handleResize() {
|
|
||||||
const parentHeight = self.element.clientHeight;
|
|
||||||
const gridHeight = parentHeight - 200;
|
|
||||||
|
|
||||||
grid.style.height = gridHeight + "px";
|
|
||||||
}
|
|
||||||
window.addEventListener("resize", handleResize);
|
|
||||||
|
|
||||||
grid.style.position = "relative";
|
|
||||||
grid.style.display = "inline-block";
|
|
||||||
grid.style.width = "100%";
|
|
||||||
grid.style.height = "100%";
|
|
||||||
grid.style.overflowY = "scroll";
|
|
||||||
this.element.style.height = "85%";
|
|
||||||
this.element.style.width = "80%";
|
|
||||||
this.element.appendChild(panel);
|
|
||||||
|
|
||||||
handleResize();
|
|
||||||
}
|
|
||||||
|
|
||||||
createFilterCombo() {
|
|
||||||
let combo = document.createElement("select");
|
|
||||||
|
|
||||||
combo.style.cssFloat = "left";
|
|
||||||
combo.style.fontSize = "14px";
|
|
||||||
combo.style.padding = "4px";
|
|
||||||
combo.style.background = "black";
|
|
||||||
combo.style.marginLeft = "2px";
|
|
||||||
combo.style.width = "199px";
|
|
||||||
combo.id = `combo-manger-filter`;
|
|
||||||
combo.style.borderRadius = "15px";
|
|
||||||
|
|
||||||
let items =
|
|
||||||
[
|
|
||||||
{ value:'*', text:'Filter: all' },
|
|
||||||
{ value:'True', text:'Filter: installed' },
|
|
||||||
{ value:'False', text:'Filter: not-installed' },
|
|
||||||
];
|
|
||||||
|
|
||||||
items.forEach(item => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = item.value;
|
|
||||||
option.text = item.text;
|
|
||||||
combo.appendChild(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
combo.addEventListener('change', function(event) {
|
|
||||||
self.filter = event.target.value;
|
|
||||||
self.apply_searchbox();
|
|
||||||
});
|
|
||||||
|
|
||||||
return combo;
|
|
||||||
}
|
|
||||||
|
|
||||||
createHeaderControls() {
|
|
||||||
let self = this;
|
|
||||||
this.search_box = $el('input.cm-search-filter', {type:'text', id:'manager-model-search-box', placeholder:'input search keyword', value:this.search_keyword}, []);
|
|
||||||
this.search_box.style.height = "25px";
|
|
||||||
this.search_box.onkeydown = (event) => {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
self.search_keyword = self.search_box.value;
|
|
||||||
self.apply_searchbox();
|
|
||||||
}
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
self.search_keyword = self.search_box.value;
|
|
||||||
self.apply_searchbox();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let search_button = document.createElement("button");
|
|
||||||
search_button.className = "cm-small-button";
|
|
||||||
search_button.innerHTML = "Search";
|
|
||||||
search_button.onclick = () => {
|
|
||||||
self.search_keyword = self.search_box.value;
|
|
||||||
self.apply_searchbox();
|
|
||||||
};
|
|
||||||
search_button.style.display = "inline-block";
|
|
||||||
|
|
||||||
let filter_control = this.createFilterCombo();
|
|
||||||
filter_control.style.display = "inline-block";
|
|
||||||
|
|
||||||
let cell = $el('td', {width:'100%'}, [filter_control, this.search_box, ' ', search_button]);
|
|
||||||
let search_control = $el('table', {width:'100%'},
|
|
||||||
[
|
|
||||||
$el('tr', {}, [cell])
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
cell.style.textAlign = "right";
|
|
||||||
this.element.appendChild(search_control);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createBottomControls() {
|
|
||||||
var close_button = document.createElement("button");
|
|
||||||
close_button.className = "cm-small-button";
|
|
||||||
close_button.innerHTML = "Close";
|
|
||||||
close_button.onclick = () => { this.close(); }
|
|
||||||
close_button.style.display = "inline-block";
|
|
||||||
|
|
||||||
this.message_box = $el('div', {id:'custom-download-message'}, [$el('br'), '']);
|
|
||||||
this.message_box.style.height = '60px';
|
|
||||||
this.message_box.style.verticalAlign = 'middle';
|
|
||||||
|
|
||||||
this.element.appendChild(this.message_box);
|
|
||||||
this.element.appendChild(close_button);
|
|
||||||
}
|
|
||||||
|
|
||||||
async show() {
|
|
||||||
try {
|
|
||||||
this.invalidateControl();
|
|
||||||
this.element.style.display = "block";
|
|
||||||
this.element.style.zIndex = 10001;
|
|
||||||
}
|
|
||||||
catch(exception) {
|
|
||||||
app.ui.dialog.show(`Failed to get external model list. / ${exception}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
715
js/model-manager.js
Normal file
715
js/model-manager.js
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
import { $el } from "../../scripts/ui.js";
|
||||||
|
import {
|
||||||
|
manager_instance, rebootAPI,
|
||||||
|
fetchData, md5, icons
|
||||||
|
} from "./common.js";
|
||||||
|
|
||||||
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
|
import TG from "./turbogrid.esm.js";
|
||||||
|
|
||||||
|
const pageCss = `
|
||||||
|
.cmm-manager {
|
||||||
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
z-index: 10001;
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--fg-color);
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-flex-auto {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--input-text);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
border-style: solid;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button:disabled,
|
||||||
|
.cmm-manager input:disabled,
|
||||||
|
.cmm-manager select:disabled {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button:disabled {
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-header label {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-type,
|
||||||
|
.cmm-manager-base,
|
||||||
|
.cmm-manager-filter {
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-keywords {
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
padding: 0 5px 0 26px;
|
||||||
|
background-size: 16px;
|
||||||
|
background-position: 5px center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf8,${encodeURIComponent(icons.search.replace("currentColor", "#888"))}");
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-status {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid {
|
||||||
|
flex: auto;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-message {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .tg-turbogrid {
|
||||||
|
font-family: var(--grid-font);
|
||||||
|
font-size: 15px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .cmm-node-name a {
|
||||||
|
color: skyblue;
|
||||||
|
text-decoration: none;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .cmm-node-desc a {
|
||||||
|
color: #5555FF;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .tg-cell a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-icon-passed {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - 10px);
|
||||||
|
top: calc(50% - 10px);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-btn-enable {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-btn-disable {
|
||||||
|
background-color: MediumSlateBlue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-btn-install {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cmm-btn-loading-bg {
|
||||||
|
0% {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: -100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button.cmm-btn-loading {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: rgb(0 119 207 / 80%);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button.cmm-btn-loading::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
content: "";
|
||||||
|
width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
rgb(0 119 207 / 30%),
|
||||||
|
rgb(0 119 207 / 30%) 10px,
|
||||||
|
transparent 10px,
|
||||||
|
transparent 15px
|
||||||
|
);
|
||||||
|
animation: cmm-btn-loading-bg 3s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cmm-node-name a {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cm-warn-note {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cmm-btn-install {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
const pageHtml = `
|
||||||
|
<div class="cmm-manager-header">
|
||||||
|
<label>Filter
|
||||||
|
<select class="cmm-manager-filter"></select>
|
||||||
|
</label>
|
||||||
|
<label>Type
|
||||||
|
<select class="cmm-manager-type"></select>
|
||||||
|
</label>
|
||||||
|
<label>Base
|
||||||
|
<select class="cmm-manager-base"></select>
|
||||||
|
</label>
|
||||||
|
<input class="cmm-manager-keywords" type="search" placeholder="Search" />
|
||||||
|
<div class="cmm-manager-status"></div>
|
||||||
|
<div class="cmm-flex-auto"></div>
|
||||||
|
</div>
|
||||||
|
<div class="cmm-manager-grid"></div>
|
||||||
|
<div class="cmm-manager-message"></div>
|
||||||
|
<div class="cmm-manager-footer">
|
||||||
|
<button class="cmm-manager-close">Close</button>
|
||||||
|
<div class="cmm-flex-auto"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export class ModelManager {
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor(app, manager_dialog) {
|
||||||
|
this.app = app;
|
||||||
|
this.manager_dialog = manager_dialog;
|
||||||
|
this.id = "cmm-manager";
|
||||||
|
|
||||||
|
this.filter = '';
|
||||||
|
this.type = '';
|
||||||
|
this.base = '';
|
||||||
|
this.keywords = '';
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
|
||||||
|
if (!document.querySelector(`style[context="${this.id}"]`)) {
|
||||||
|
const $style = document.createElement("style");
|
||||||
|
$style.setAttribute("context", this.id);
|
||||||
|
$style.innerHTML = pageCss;
|
||||||
|
document.head.appendChild($style);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element = $el("div", {
|
||||||
|
parent: document.body,
|
||||||
|
className: "comfy-modal cmm-manager"
|
||||||
|
});
|
||||||
|
this.element.innerHTML = pageHtml;
|
||||||
|
this.initFilter();
|
||||||
|
this.bindEvents();
|
||||||
|
this.initGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
initFilter() {
|
||||||
|
|
||||||
|
this.filterList = [{
|
||||||
|
label: "All",
|
||||||
|
value: ""
|
||||||
|
}, {
|
||||||
|
label: "Installed",
|
||||||
|
value: "True"
|
||||||
|
}, {
|
||||||
|
label: "Not Installed",
|
||||||
|
value: "False"
|
||||||
|
}];
|
||||||
|
|
||||||
|
this.typeList = [{
|
||||||
|
label: "All",
|
||||||
|
value: ""
|
||||||
|
}];
|
||||||
|
|
||||||
|
this.baseList = [{
|
||||||
|
label: "All",
|
||||||
|
value: ""
|
||||||
|
}];
|
||||||
|
|
||||||
|
this.updateFilter();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilter() {
|
||||||
|
const $filter = this.element.querySelector(".cmm-manager-filter");
|
||||||
|
$filter.innerHTML = this.filterList.map(item => {
|
||||||
|
const selected = item.value === this.filter ? " selected" : "";
|
||||||
|
return `<option value="${item.value}"${selected}>${item.label}</option>`
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
const $type = this.element.querySelector(".cmm-manager-type");
|
||||||
|
$type.innerHTML = this.typeList.map(item => {
|
||||||
|
const selected = item.value === this.type ? " selected" : "";
|
||||||
|
return `<option value="${item.value}"${selected}>${item.label}</option>`
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
const $base = this.element.querySelector(".cmm-manager-base");
|
||||||
|
$base.innerHTML = this.baseList.map(item => {
|
||||||
|
const selected = item.value === this.base ? " selected" : "";
|
||||||
|
return `<option value="${item.value}"${selected}>${item.label}</option>`
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bindEvents() {
|
||||||
|
const eventsMap = {
|
||||||
|
".cmm-manager-filter": {
|
||||||
|
change: (e) => {
|
||||||
|
this.filter = e.target.value;
|
||||||
|
this.updateGrid();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
".cmm-manager-type": {
|
||||||
|
change: (e) => {
|
||||||
|
this.type = e.target.value;
|
||||||
|
this.updateGrid();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
".cmm-manager-base": {
|
||||||
|
change: (e) => {
|
||||||
|
this.base = e.target.value;
|
||||||
|
this.updateGrid();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
".cmm-manager-keywords": {
|
||||||
|
input: (e) => {
|
||||||
|
const keywords = `${e.target.value}`.trim();
|
||||||
|
if (keywords !== this.keywords) {
|
||||||
|
this.keywords = keywords;
|
||||||
|
this.updateGrid();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focus: (e) => e.target.select()
|
||||||
|
},
|
||||||
|
|
||||||
|
".cmm-manager-close": {
|
||||||
|
click: (e) => this.close()
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
Object.keys(eventsMap).forEach(selector => {
|
||||||
|
const target = this.element.querySelector(selector);
|
||||||
|
if (target) {
|
||||||
|
const events = eventsMap[selector];
|
||||||
|
if (events) {
|
||||||
|
Object.keys(events).forEach(type => {
|
||||||
|
target.addEventListener(type, events[type]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
initGrid() {
|
||||||
|
const container = this.element.querySelector(".cmm-manager-grid");
|
||||||
|
const grid = new TG.Grid(container);
|
||||||
|
this.grid = grid;
|
||||||
|
|
||||||
|
grid.bind('onUpdated', (e, d) => {
|
||||||
|
|
||||||
|
this.showStatus(`${grid.viewRows.length.toLocaleString()} external models`);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.bind('onClick', (e, d) => {
|
||||||
|
const { rowItem } = d;
|
||||||
|
const target = d.e.target;
|
||||||
|
const mode = target.getAttribute("mode");
|
||||||
|
if (mode === "install") {
|
||||||
|
this.installModel(rowItem, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.setOption({
|
||||||
|
theme: 'dark',
|
||||||
|
|
||||||
|
textSelectable: true,
|
||||||
|
scrollbarRound: true,
|
||||||
|
|
||||||
|
frozenColumn: 1,
|
||||||
|
rowNotFound: "No Results",
|
||||||
|
|
||||||
|
rowHeight: 40,
|
||||||
|
bindWindowResize: true,
|
||||||
|
bindContainerResize: true,
|
||||||
|
|
||||||
|
cellResizeObserver: (rowItem, columnItem) => {
|
||||||
|
const autoHeightColumns = ['name', 'description'];
|
||||||
|
return autoHeightColumns.includes(columnItem.id)
|
||||||
|
},
|
||||||
|
|
||||||
|
// updateGrid handler for filter and keywords
|
||||||
|
rowFilter: (rowItem) => {
|
||||||
|
|
||||||
|
const searchableColumns = ["name", "type", "base", "description", "filename", "save_path"];
|
||||||
|
|
||||||
|
let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords);
|
||||||
|
|
||||||
|
if (shouldShown) {
|
||||||
|
if(this.filter && rowItem.installed !== this.filter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.type && rowItem.type !== this.type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.base && rowItem.base !== this.base) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldShown;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
renderGrid() {
|
||||||
|
|
||||||
|
// update theme
|
||||||
|
const colorPalette = this.app.ui.settings.settingsValues['Comfy.ColorPalette'];
|
||||||
|
Array.from(this.element.classList).forEach(cn => {
|
||||||
|
if (cn.startsWith("cmm-manager-")) {
|
||||||
|
this.element.classList.remove(cn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.element.classList.add(`cmm-manager-${colorPalette}`);
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
theme: colorPalette === "light" ? "" : "dark"
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows = this.modelList || [];
|
||||||
|
|
||||||
|
const columns = [{
|
||||||
|
id: 'id',
|
||||||
|
name: 'ID',
|
||||||
|
width: 50,
|
||||||
|
align: 'center'
|
||||||
|
}, {
|
||||||
|
id: 'name',
|
||||||
|
name: 'Name',
|
||||||
|
width: 200,
|
||||||
|
minWidth: 100,
|
||||||
|
maxWidth: 500,
|
||||||
|
classMap: 'cmm-node-name',
|
||||||
|
formatter: function(name, rowItem, columnItem, cellNode) {
|
||||||
|
return `<a href=${rowItem.reference} target="_blank"><b>${name}</b></a>`;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
id: 'installed',
|
||||||
|
name: 'Download',
|
||||||
|
width: 130,
|
||||||
|
minWidth: 110,
|
||||||
|
maxWidth: 200,
|
||||||
|
sortable: false,
|
||||||
|
align: 'center',
|
||||||
|
formatter: (installed, rowItem, columnItem) => {
|
||||||
|
if (rowItem.refresh) {
|
||||||
|
return `<font color="red">Refresh Required</span>`;
|
||||||
|
}
|
||||||
|
if (installed === "True") {
|
||||||
|
return `<div class="cmm-icon-passed">${icons.passed}</div>`;
|
||||||
|
}
|
||||||
|
return `<button class="cmm-btn-install" mode="install">Install</button>`;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
id: 'type',
|
||||||
|
name: 'Type',
|
||||||
|
width: 100
|
||||||
|
}, {
|
||||||
|
id: 'base',
|
||||||
|
name: 'Base'
|
||||||
|
}, {
|
||||||
|
id: 'description',
|
||||||
|
name: 'Description',
|
||||||
|
width: 400,
|
||||||
|
maxWidth: 5000,
|
||||||
|
classMap: 'cmm-node-desc'
|
||||||
|
}, {
|
||||||
|
id: 'filename',
|
||||||
|
name: 'Filename',
|
||||||
|
width: 200
|
||||||
|
}, {
|
||||||
|
id: "save_path",
|
||||||
|
name: 'Save Path',
|
||||||
|
width: 200
|
||||||
|
}];
|
||||||
|
|
||||||
|
this.grid.setData({
|
||||||
|
options,
|
||||||
|
rows,
|
||||||
|
columns
|
||||||
|
});
|
||||||
|
|
||||||
|
this.grid.render();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGrid() {
|
||||||
|
if (this.grid) {
|
||||||
|
this.grid.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
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();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.refresh = true;
|
||||||
|
this.grid.updateCell(item, "installed");
|
||||||
|
|
||||||
|
this.hideLoading();
|
||||||
|
|
||||||
|
this.showStatus(`Install ${item.name} successfully`);
|
||||||
|
this.showMessage(`To apply the installed model, please click the 'Refresh' button on the main menu.`, "red")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getModelList(models) {
|
||||||
|
|
||||||
|
const typeMap = new Map();
|
||||||
|
const baseMap = new Map();
|
||||||
|
|
||||||
|
models.forEach((item, i) => {
|
||||||
|
const { type, base, name, reference } = item;
|
||||||
|
item.originalData = JSON.parse(JSON.stringify(item));
|
||||||
|
item.hash = md5(name + reference);
|
||||||
|
item.id = i + 1;
|
||||||
|
|
||||||
|
typeMap.set(type, type);
|
||||||
|
baseMap.set(base, base);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const typeList = [];
|
||||||
|
typeMap.forEach(type => {
|
||||||
|
typeList.push({
|
||||||
|
label: type,
|
||||||
|
value: type
|
||||||
|
});
|
||||||
|
});
|
||||||
|
typeList.sort((a,b)=> {
|
||||||
|
const au = a.label.toUpperCase();
|
||||||
|
const bu = b.label.toUpperCase();
|
||||||
|
if (au !== bu) {
|
||||||
|
return au > bu ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
this.typeList = [{
|
||||||
|
label: "All",
|
||||||
|
value: ""
|
||||||
|
}].concat(typeList);
|
||||||
|
|
||||||
|
|
||||||
|
const baseList = [];
|
||||||
|
baseMap.forEach(base => {
|
||||||
|
baseList.push({
|
||||||
|
label: base,
|
||||||
|
value: base
|
||||||
|
});
|
||||||
|
});
|
||||||
|
baseList.sort((a,b)=> {
|
||||||
|
const au = a.label.toUpperCase();
|
||||||
|
const bu = b.label.toUpperCase();
|
||||||
|
if (au !== bu) {
|
||||||
|
return au > bu ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
this.baseList = [{
|
||||||
|
label: "All",
|
||||||
|
value: ""
|
||||||
|
}].concat(baseList);
|
||||||
|
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
async loadData() {
|
||||||
|
|
||||||
|
this.showLoading();
|
||||||
|
|
||||||
|
this.showStatus(`Loading external model list ...`);
|
||||||
|
|
||||||
|
const mode = manager_instance.datasrc_combo.value;
|
||||||
|
|
||||||
|
const res = await fetchData(`/externalmodel/getlist?mode=${mode}`);
|
||||||
|
if (res.error) {
|
||||||
|
this.showError("Failed to get external model list.");
|
||||||
|
this.hideLoading();
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { models } = res.data;
|
||||||
|
|
||||||
|
this.modelList = this.getModelList(models);
|
||||||
|
// console.log("models", this.modelList);
|
||||||
|
|
||||||
|
this.updateFilter();
|
||||||
|
|
||||||
|
this.renderGrid();
|
||||||
|
|
||||||
|
this.hideLoading();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
showError(err) {
|
||||||
|
this.showMessage(err, "red");
|
||||||
|
}
|
||||||
|
|
||||||
|
showMessage(msg, color) {
|
||||||
|
if (color) {
|
||||||
|
msg = `<font color="${color}">${msg}</font>`;
|
||||||
|
}
|
||||||
|
this.element.querySelector(".cmm-manager-message").innerHTML = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus(msg, color) {
|
||||||
|
if (color) {
|
||||||
|
msg = `<font color="${color}">${msg}</font>`;
|
||||||
|
}
|
||||||
|
this.element.querySelector(".cmm-manager-status").innerHTML = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
this.setDisabled(true);
|
||||||
|
if (this.grid) {
|
||||||
|
this.grid.showLoading();
|
||||||
|
this.grid.showMask({
|
||||||
|
opacity: 0.05
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading() {
|
||||||
|
this.setDisabled(false);
|
||||||
|
if (this.grid) {
|
||||||
|
this.grid.hideLoading();
|
||||||
|
this.grid.hideMask();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabled(disabled) {
|
||||||
|
|
||||||
|
const $close = this.element.querySelector(".cmm-manager-close");
|
||||||
|
|
||||||
|
const list = [
|
||||||
|
".cmm-manager-header input",
|
||||||
|
".cmm-manager-header select",
|
||||||
|
".cmm-manager-footer button"
|
||||||
|
].map(s => {
|
||||||
|
return Array.from(this.element.querySelectorAll(s));
|
||||||
|
})
|
||||||
|
.flat()
|
||||||
|
.filter(it => {
|
||||||
|
return it !== $close;
|
||||||
|
});
|
||||||
|
|
||||||
|
list.forEach($elem => {
|
||||||
|
if (disabled) {
|
||||||
|
$elem.setAttribute("disabled", "disabled");
|
||||||
|
} else {
|
||||||
|
$elem.removeAttribute("disabled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.from(this.element.querySelectorAll(".cmm-btn-loading")).forEach($elem => {
|
||||||
|
$elem.classList.remove("cmm-btn-loading");
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
setKeywords(keywords = "") {
|
||||||
|
this.keywords = keywords;
|
||||||
|
this.element.querySelector(".cmm-manager-keywords").value = keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
show() {
|
||||||
|
this.element.style.display = "flex";
|
||||||
|
this.setKeywords("");
|
||||||
|
this.showMessage("");
|
||||||
|
this.loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.element.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user