From 297e2eab7a8da66c3c2913bef6c90b979d3bce17 Mon Sep 17 00:00:00 2001
From: Col0ring <1561999073@qq.com>
Date: Fri, 20 Sep 2024 10:08:23 +0800
Subject: [PATCH] Update Manager UI
---
.../js/model-manager.js | 1303 ++++++++---------
1 file changed, 625 insertions(+), 678 deletions(-)
diff --git a/ComfyUI-ModelScope-Manager/js/model-manager.js b/ComfyUI-ModelScope-Manager/js/model-manager.js
index d92b46d..ea64ff8 100644
--- a/ComfyUI-ModelScope-Manager/js/model-manager.js
+++ b/ComfyUI-ModelScope-Manager/js/model-manager.js
@@ -1,11 +1,8 @@
-import { $el } from "../../scripts/ui.js";
-import {
- manager_instance, rebootAPI,
- fetchData, md5, icons
-} from "./common.js";
+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";
+import TG from './turbogrid.esm.js'
const pageCss = `
.cmm-manager {
@@ -50,19 +47,33 @@ const pageCss = `
display: flex;
flex-wrap: wrap;
gap: 5px;
+ justify-content: space-between;
align-items: center;
padding: 0 5px;
}
.cmm-manager-header label {
display: flex;
- gap: 5px;
+ flex: 1;
+ gap: 10px;
align-items: center;
}
-.cmm-manager-type,
-.cmm-manager-base,
-.cmm-manager-filter {
+.cmm-manager-header label[required="true"] span::before {
+ display: inline-block;
+ margin-inline-end: 4px;
+ color: #ff4d4f;
+ font-family: SimSun, sans-serif;
+ line-height: 1;
+ content: "*";
+}
+
+.cmm-manager-model_id,
+.cmm-manager-revision,
+.cmm-manager-filename,
+.cmm-manager-save_path {
+ flex: 1;
+ min-width: 100px;
height: 28px;
line-height: 28px;
}
@@ -74,12 +85,11 @@ const pageCss = `
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"))}");
+ 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;
@@ -87,12 +97,6 @@ const pageCss = `
overflow: hidden;
}
-.cmm-manager-selection {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- align-items: center;
-}
.cmm-manager-message {
@@ -135,6 +139,32 @@ const pageCss = `
top: calc(50% - 10px);
}
+
+
+
+.cmm-status-error-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100%;
+}
+
+.cmm-status-error-container .cmm-icon-conflicts {
+ color: #ff4d4f;
+ position: relative;
+ width: 20px;
+ height: 20px;
+ margin-right: 4px;
+}
+
+
+.cmm-status-error-container button {
+ padding: 2px;
+ font-size: 14px;
+ min-width: 72px;
+}
+
.cmm-manager .cmm-btn-enable {
background-color: blue;
color: white;
@@ -214,678 +244,595 @@ const pageCss = `
background-color: #333;
}
-`;
+.cmm-progress-bar-container {
+ height: 100%;
+ display: flex;
+ align-items: center;
+}
+
+.cmm-progress-bar-container > span {
+ font-size: 14px;
+ color: var(--descrip-text);
+}
+
+.cmm-progress-bar {
+ position: relative;
+ display: inline-block;
+ height: 8px;
+ margin-right: 4px;
+ border-radius: 4px;
+ overflow: hidden;
+ background-color: #888888;
+}
+.cmm-progress-completed {
+ background-color: #90EE90;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+.cmm-tooltip {
+ position: absolute;
+ z-index: 1000;
+ max-width: 300px;
+ background-color: var(--comfy-input-bg);
+ color: var(--input-text);
+ padding: 6px 10px;
+ border-radius: 4px;
+ border-color: var(--border-color);
+ border-style: solid;
+ border-width: 2px;
+ font-size: 14px;
+ opacity: 0;
+ visibility: hidden;
+ word-wrap: break-word;
+}
+.cmm-tooltip-message {
+ position: relative;
+ z-index: 2;
+}
+.cmm-tooltip-message::after {
+ content: "";
+ position: absolute;
+ z-index: -1;
+ left: -10px;
+ right: -10px;
+ top: -6px;
+ bottom: -6px;
+ width: calc(100% + 20px);
+ height: calc(100% + 12px);
+ background-color: var(--comfy-input-bg);
+}
+.cmm-tooltip.cmm-tooltip-visible {
+ opacity: 1;
+ visibility: visible;
+}
+.cmm-tooltip::before {
+ content: "";
+ position: absolute;
+ z-index: 1;
+ top: 100%;
+ width: 8px;
+ height: 8px;
+ left: 50%;
+ background-color: var(--comfy-input-bg);
+ transform: translate(-50%, -6.6px) rotate(45deg);
+ border-color: var(--border-color);
+ border-style: solid;
+ border-width: 2px;
+}
+`
const pageHtml = `
+
-
-`;
+
+`
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 ``
- }).join("");
-
- const $type = this.element.querySelector(".cmm-manager-type");
- $type.innerHTML = this.typeList.map(item => {
- const selected = item.value === this.type ? " selected" : "";
- return ``
- }).join("");
-
- const $base = this.element.querySelector(".cmm-manager-base");
- $base.innerHTML = this.baseList.map(item => {
- const selected = item.value === this.base ? " selected" : "";
- return ``
- }).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-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()
- },
-
- };
- 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('onSelectChanged', (e, changes) => {
- this.renderSelected();
- });
-
- grid.bind('onClick', (e, d) => {
- const { rowItem } = d;
- const target = d.e.target;
- const mode = target.getAttribute("mode");
- if (mode === "install") {
- this.installModels([rowItem], target);
- }
-
- });
-
- grid.setOption({
- theme: 'dark',
-
- selectVisible: true,
- selectMultiple: true,
- selectAllVisible: true,
-
- 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 `${name}`;
- }
- }, {
- id: 'installed',
- name: 'Install',
- width: 130,
- minWidth: 110,
- maxWidth: 200,
- sortable: false,
- align: 'center',
- formatter: (installed, rowItem, columnItem) => {
- if (rowItem.refresh) {
- return `Refresh Required`;
- }
- if (installed === "True") {
- return `${icons.passed}
`;
- }
- return ``;
- }
- }, {
- id: 'url',
- name: '',
- width: 50,
- sortable: false,
- align: 'center',
- formatter: (url, rowItem, columnItem) => {
- return `${icons.download}`;
- }
- }, {
- id: 'size',
- name: 'Size',
- width: 100,
- formatter: (size) => {
- if (typeof size === "number") {
- return this.formatSize(size);
- }
- return size;
- }
- }, {
- id: 'type',
- name: 'Type',
- width: 100
- }, {
- id: 'base',
- name: 'Base'
- }, {
- id: 'description',
- name: 'Description',
- width: 400,
- maxWidth: 5000,
- classMap: 'cmm-node-desc'
- }, {
- id: "save_path",
- name: 'Save Path',
- width: 200
- }, {
- id: 'filename',
- name: 'Filename',
- width: 200
- }];
-
- this.grid.setData({
- options,
- rows,
- columns
- });
-
- this.grid.render();
-
- }
-
- updateGrid() {
- if (this.grid) {
- this.grid.update();
- }
- }
-
- // ===========================================================================================
-
- renderSelected() {
- const selectedList = this.grid.getSelectedRows();
- if (!selectedList.length) {
- this.showSelection("");
- this.selectedModels = [];
- return;
- }
-
- this.selectedModels = selectedList;
- this.showSelection(`Selected ${selectedList.length} models `);
- }
-
- 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");
-
- 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")
- }
-
- }
-
- getModelList(models) {
-
- const typeMap = new Map();
- const baseMap = new Map();
-
- models.forEach((item, i) => {
- const { type, base, name, reference, installed } = item;
- item.originalData = JSON.parse(JSON.stringify(item));
- item.size = this.sizeToBytes(item.size);
- item.hash = md5(name + reference);
- item.id = i + 1;
-
- if (installed === "True") {
- item.selectable = false;
- }
-
- 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();
-
- }
-
- // ===========================================================================================
-
- formatSize(v) {
- const base = 1000;
- const units = ['', 'K', 'M', 'G', 'T', 'P'];
- const space = '';
- const postfix = 'B';
- if (v <= 0) {
- return `0${space}${postfix}`;
- }
- for (let i = 0, l = units.length; i < l; i++) {
- const min = Math.pow(base, i);
- const max = Math.pow(base, i + 1);
- if (v > min && v <= max) {
- const unit = units[i];
- if (unit) {
- const n = v / min;
- const nl = n.toString().split('.')[0].length;
- const fl = Math.max(3 - nl, 1);
- v = n.toFixed(fl);
- }
- v = v + space + unit + postfix;
- break;
- }
- }
- return v;
- }
-
- // for size sort
- sizeToBytes(v) {
- if (typeof v === "number") {
- return v;
- }
- if (typeof v === "string") {
- const n = parseFloat(v);
- const unit = v.replace(/[0-9.B]+/g, "").trim().toUpperCase();
- if (unit === "K") {
- return n * 1000;
- }
- if (unit === "M") {
- return n * 1000 * 1000;
- }
- if (unit === "G") {
- return n * 1000 * 1000 * 1000;
- }
- if (unit === "T") {
- return n * 1000 * 1000 * 1000 * 1000;
- }
- }
- return v;
- }
-
- showSelection(msg) {
- this.element.querySelector(".cmm-manager-selection").innerHTML = msg;
- }
-
- showError(err) {
- this.showMessage(err, "red");
- }
-
- showMessage(msg, color) {
- if (color) {
- msg = `${msg}`;
- }
- this.element.querySelector(".cmm-manager-message").innerHTML = msg;
- }
-
- showStatus(msg, color) {
- if (color) {
- msg = `${msg}`;
- }
- 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",
- ".cmm-manager-selection 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.showSelection("");
- this.showMessage("");
- this.loadData();
- }
-
- close() {
- this.element.style.display = "none";
- }
-}
\ No newline at end of file
+ static instance = null
+
+ constructor(app, manager_dialog) {
+ this.app = app
+ this.manager_dialog = manager_dialog
+ this.id = 'cmm-manager'
+
+ this.form_data = {
+ model_id: '',
+ revision: '',
+ filename: '',
+ save_path: ''
+ }
+ 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)
+ }
+ // 添加 modal
+ this.element = $el('div', {
+ parent: document.body,
+ className: 'comfy-modal cmm-manager'
+ })
+ this.element.innerHTML = pageHtml
+ this.bindEvents()
+ this.initGrid()
+ }
+
+ bindEvents() {
+ const eventsMap = {
+ '.cmm-manager-model_id': {
+ change: (e) => {
+ this.form_data.model_id = e.target.value
+ }
+ },
+ '.cmm-manager-revision': {
+ change: (e) => {
+ this.form_data.revision = e.target.value
+ }
+ },
+ '.cmm-manager-filename': {
+ change: (e) => {
+ this.form_data.filename = e.target.value
+ }
+ },
+ '.cmm-manager-save_path': {
+ change: (e) => {
+ this.form_data.save_path = e.target.value
+ }
+ },
+ '.cmm-btn-install': {
+ click: async (e) => {
+ if (this.installing) {
+ return
+ }
+
+ this.installing = true
+ await this.installModel(this.form_data, e.currentTarget)
+ this.installing = false
+ this.setFormData()
+ await this.loadData(true)
+ }
+ },
+ '.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('onClick', async (e, d) => {
+ const { rowItem } = d
+ const target = d.e.target
+ const mode = target.getAttribute('mode')
+ if (mode === 'retry') {
+ await this.installModel(
+ {
+ model_id: rowItem.model_id,
+ revision: rowItem.revision,
+ filename: rowItem.filename,
+ save_path: rowItem.save_path
+ },
+ target
+ )
+ await this.loadData(true)
+ }
+ })
+
+ const isNodeTruncated = function (node) {
+ if (!node) {
+ return false
+ }
+ if (node.clientWidth < node.scrollWidth) {
+ return true
+ }
+ }
+
+ grid
+ .bind('onMouseOver', (e, d) => {
+ const { rowItem } = d
+ const target = d.e.target
+ const mode = target.getAttribute('mode')
+ if (mode === 'model-error-message') {
+ this.showTooltip(target, rowItem.message)
+ return
+ }
+
+ const value = d.rowItem[d.columnItem.id]
+
+ if (value && isNodeTruncated(target)) {
+ this.showTooltip(target, `${value}`)
+ return
+ }
+ })
+ .bind('onMouseOut', (e, d) => {
+ this.hideTooltip()
+ })
+
+ grid.setOption({
+ theme: 'dark',
+
+ selectVisible: false,
+ selectMultiple: true,
+ selectAllVisible: true,
+
+ 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 = [
+ 'model_id',
+ 'revision',
+ 'save_path',
+ 'filename'
+ ]
+
+ let shouldShown = grid.highlightKeywordsFilter(
+ rowItem,
+ searchableColumns,
+ this.keywords
+ )
+
+ 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: 'model_id',
+ name: 'Model ID',
+ minWidth: 100,
+ width: 200,
+ maxWidth: 500,
+ classMap: 'cmm-node-name',
+ formatter: function (id, rowItem, columnItem, cellNode) {
+ return `${id}`
+ }
+ },
+ {
+ id: 'status',
+ name: 'Status',
+ width: 130,
+ minWidth: 110,
+ width: 200,
+ maxWidth: 500,
+ sortable: false,
+ align: 'center',
+ formatter: (status, rowItem, columnItem) => {
+ switch (status) {
+ case 0:
+ case 1:
+ return `
+
+
${rowItem.progress}%
+
+
+ ${icons.conflicts}
+
+
+ `
+ case 2:
+ return `${icons.passed}
`
+ default:
+ return ''
+ }
+ }
+ },
+ {
+ id: 'revision',
+ name: 'Revision',
+ width: 150,
+ align: 'center'
+ },
+
+ {
+ id: 'filename',
+ name: 'Filename',
+ width: 300
+ },
+ {
+ id: 'save_path',
+ name: 'Save Path',
+ width: 300
+ }
+ ]
+
+ this.grid.setData({
+ options,
+ rows,
+ columns
+ })
+
+ this.grid.render()
+ }
+
+ updateGrid() {
+ if (this.grid) {
+ this.grid.update()
+ }
+ }
+
+ // ===========================================================================================
+ async getModelsList() {
+ if (this.fetching) {
+ return
+ }
+ this.fetching = true
+ const res = await fetchData('/customnode/modelscope/status')
+ this.fetching = false
+ return res
+ }
+
+ async loadData(update, loop) {
+ !loop && this.showLoading()
+ const res = await this.getModelsList()
+ if (!res) {
+ !loop && this.hideLoading()
+ return
+ }
+ if (res.error) {
+ this.showError('Failed to get external model list.')
+ !loop && this.hideLoading()
+ return
+ }
+ const models = res.data
+ this.modelList = models
+ if (update) {
+ this.grid.setRows(this.modelList)
+ this.updateGrid()
+ } else {
+ this.renderGrid()
+ }
+ if (models.some((item) => item.status === 1 || item.status === 0)) {
+ setTimeout(() => {
+ this.loadData(update, true)
+ }, 500)
+ }
+ !loop && this.hideLoading()
+ }
+
+ async installModel(data, btn) {
+ const prop2Label = {
+ model_id: 'Model ID',
+ save_path: 'Save Path',
+ filename: 'Filename'
+ }
+ for (const key in prop2Label) {
+ if (!data[key]) {
+ this.showError(`${prop2Label[key]} is required.`)
+ return
+ }
+ }
+
+ btn.classList.add('cmm-btn-loading')
+ this.showLoading()
+ this.showError('')
+ const res = await fetchData('/customnode/modelscope/install_model', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ })
+ if (res.error) {
+ this.showError(`Install failed: ${data.model_id} ${res.error.message}`)
+ }
+ this.hideLoading()
+ btn.classList.remove('cmm-btn-loading')
+ }
+
+ // ===========================================================================================
+
+ showTooltip(target, message) {
+ if (!message) {
+ this.hideTooltip()
+ return
+ }
+ const containerRect = this.element.getBoundingClientRect()
+ const tooltipElement = document.querySelector('.cmm-tooltip')
+ tooltipElement.innerHTML = `${message}
`
+ // 计算工具提示的位置
+ const targetRect = target.getBoundingClientRect()
+ const tooltipHeight = tooltipElement.offsetHeight
+ const tooltipWidth = tooltipElement.offsetWidth
+
+ tooltipElement.style.top = `${
+ targetRect.top - containerRect.top - tooltipHeight - 10
+ }px` // 在目标元素上方
+ tooltipElement.style.left = `${
+ targetRect.left -
+ containerRect.left +
+ (targetRect.width - tooltipWidth) / 2
+ }px` // 居中对齐
+ tooltipElement.classList.add('cmm-tooltip-visible')
+ }
+
+ hideTooltip() {
+ const tooltipElement = document.querySelector('.cmm-tooltip')
+ tooltipElement.classList.remove('cmm-tooltip-visible')
+ }
+
+ showError(err) {
+ this.showMessage(err, 'red')
+ }
+
+ showMessage(msg, color) {
+ if (color) {
+ msg = `${msg}`
+ }
+ this.element.querySelector('.cmm-manager-message').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
+ }
+
+ setFormData(
+ data = {
+ model_id: '',
+ revision: '',
+ filename: '',
+ save_path: ''
+ }
+ ) {
+ this.form_data = data
+ Object.keys(data).map((key) => {
+ this.element.querySelector(`.cmm-manager-${key}`).value = data[key]
+ })
+ }
+
+ show() {
+ this.element.style.display = 'flex'
+ this.setKeywords('')
+ this.showMessage('')
+ this.loadData()
+ }
+
+ close() {
+ this.element.style.display = 'none'
+ }
+}