diff --git a/.gitignore b/.gitignore index 9b46b97c..a4d28639 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,10 @@ Thumbs.db browser_tests/test-results/ browser_tests/playwright-report/ browser_tests/blob-report/ + +#packaged product +bizyui.egg-info/ +bizyui/js/bizyair_frontend.js +dist/ + +*.tsbuildinfo diff --git a/README.md b/README.md index c8755f46..f096cd80 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,11 @@ npm run format npx eslint src # release frontend npm run dev +#重新打包前端得到.whl包 +npm run build:py +#不重新生成forntend_bizyair.js只重新打包bizyui目录得到.whl包 +npm run build:bizyui ``` + ### Installation and Setup diff --git a/bizyui/__init__.py b/bizyui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bizyui/js/__init__.py b/bizyui/js/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bizyui/js/apis.js b/bizyui/js/apis.js new file mode 100644 index 00000000..af3ea156 --- /dev/null +++ b/bizyui/js/apis.js @@ -0,0 +1,153 @@ +import { dialog } from './subassembly/dialog.js'; + +const fetchCache = new Map(); + +function customFetch(url, options = {}) { + const now = Date.now(); + if (fetchCache.has(url)) { + const lastFetchTime = fetchCache.get(url); + if (now - lastFetchTime < 1200) { + return Promise.resolve(null); + } + } + fetchCache.set(url, now); + const host = `${window.location.origin}${window.location.pathname === '/' ? '' : window.location.pathname}` + return window.fetch(`${host}${url}`, options) + .then(response => { + if (response.status === 404) { + dialog({ + content: "You may be missing dependencies at the moment. For details, please refer to the ComfyUI logs.", + type: 'error', + noText: 'Close' + }) + } + return response.json(); + }) + .then(data => { + const { code, message } = data; + if (code !== 20000) { + dialog({ + type: 'warning', + content: message, + noText: 'Close', + onNo: () => { + if (code === 401000) { + document.querySelector('.menus-item-key').click() + } + } + }) + + return; + } + return data; + }) + .catch(error => { + console.error('Fetch error:', error); + throw error; + }); +} + + +export function check_model_exists ( type, name ) { + return customFetch('/bizyair/modelhost/check_model_exists', { + method: 'POST', + body: JSON.stringify({ type, name }) + }) +} + +export function model_upload ( data ) { + return customFetch('/bizyair/modelhost/model_upload', { + method: 'POST', + body: JSON.stringify(data) + }) +} + +export function file_upload ( data ) { + return customFetch('/bizyair/modelhost/file_upload', { + method: 'POST', + body: data + }) +} + +export function set_api_key ( data ) { + return customFetch('/bizyair/set_api_key', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: data + }) +} + +export function models_files ( params, data ) { + let actualParams = '' + for (const i in params) { + actualParams += `${i}=${params[i]}&` + } + return customFetch(`/bizyair/community/models/query?${actualParams}`, { + method: 'POST', + body: JSON.stringify(data) + }) +} + +export function change_public ( data ) { + return customFetch('/bizyair/modelhost/models/change_public', {method: 'PUT', body: JSON.stringify(data)}) +} + +export function model_types () { + return customFetch('/bizyair/community/model_types', {method: 'GET'}) +} + +export function check_folder (url) { + return customFetch(`/bizyair/modelhost/check_folder?absolute_path=${encodeURIComponent(url)}`, {method: 'GET'}) +} + +export function submit_upload (data) { + return customFetch(`/bizyair/community/submit_upload?clientId=${sessionStorage.getItem('clientId')}`, { + method: 'POST', + body: JSON.stringify(data) + }) +} + +export function delModels ( data ) { + return customFetch('/bizyair/modelhost/models', { + method: 'DELETE', + body: JSON.stringify({ + type: data.type, + name: data.name, + }), + }) +} + +export function getUserInfo () { + return customFetch('/bizyair/user/info', { method: 'get' }) +} + +export function putShareId (data) { + return customFetch('/bizyair/user/share_id', { + method: 'put', + body: JSON.stringify(data) + }) +} + +export function getDescription (data) { + return customFetch(`/bizyair/modelhost/models/description?${new URLSearchParams(data).toString()}`, { + method: 'get' + }) +} + +export function putDescription (data) { + return customFetch('/bizyair/modelhost/models/description', { + method: 'put', + body: JSON.stringify(data) + }) +} + +export function uploadImage (file) { + const formData = new FormData(); + formData.append('file', file); + return customFetch('/bizyair/community/files/upload', { + method: 'POST', + body: formData + }) +} diff --git a/bizyui/js/bizyair_tools.js b/bizyui/js/bizyair_tools.js new file mode 100644 index 00000000..0a5f1ace --- /dev/null +++ b/bizyui/js/bizyair_tools.js @@ -0,0 +1,54 @@ +import { app, ComfyApp } from "../../scripts/app.js"; +import { api } from "../../../scripts/api.js"; + +app.registerExtension({ + name: "bizyair.tool", + setup() { + + async function handleFile(json_data) { + const jsonContent = json_data + + await app.loadGraphData( + jsonContent, + true, + false, + "convert_test" + ); + + } + async function convert(){ + const p2 = await app.graphToPrompt(); + const json = JSON.stringify(p2["workflow"], null, 2); + + await api.fetchApi("/bizyair/node_converter", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: json + }).then(response => response.json()) + .then(data => handleFile(data)) + .catch(error => console.error("Error:", error)); + } + // Add canvas menu options + const orig = LGraphCanvas.prototype.getCanvasMenuOptions; + LGraphCanvas.prototype.getCanvasMenuOptions = function () { + const options = orig.apply(this, arguments); + options.push(null, { + content: "BizyAir Tools", + submenu: { + options: [ + { + content: "convert to bizyair node", + callback: async () => { + await convert() + }, + }, + ], + }, + }); + return options; + }; + + }, +}); diff --git a/bizyui/js/connection_color.js b/bizyui/js/connection_color.js new file mode 100644 index 00000000..56fc8738 --- /dev/null +++ b/bizyui/js/connection_color.js @@ -0,0 +1,13 @@ +import { app } from "../../scripts/app.js"; + +app.registerExtension({ + name: "bizyair.custom.connectionColorByType", + async setup() { + Object.assign(app.canvas.default_connection_color_byType, { + BIZYAIR_MODEL: '#7C5AEC', + BIZYAIR_CLIP: '#FFF4C4', + BIZYAIR_VAE: '#FF5959', + BIZYAIR_CONDITIONING: '#967117', + }) + }, +}); diff --git a/bizyui/js/dataset_apply.js b/bizyui/js/dataset_apply.js new file mode 100644 index 00000000..a7f224c4 --- /dev/null +++ b/bizyui/js/dataset_apply.js @@ -0,0 +1,138 @@ +import { app } from "../../scripts/app.js"; + +import './bizyair_frontend.js' +import { hideWidget } from './subassembly/tools.js' + +function createSetWidgetCallback() { + return function setWidgetCallback() { + const targetWidget = this.widgets.find(widget => widget.name == "dataset_path"); + if (targetWidget) { + targetWidget.value = targetWidget.value || "to choose" + targetWidget.mouse = function(e, pos, canvas) { + try { + if (e.type === "pointerdown" || e.type === "mousedown" || e.type === "click" || e.type === "pointerup") { + e.preventDefault(); + e.stopPropagation(); + e.widgetClick = true; + const currentNode = this.node; + + if (!currentNode || !currentNode.widgets) { + console.warn("Node or widgets not available"); + return false; + } + + if (typeof bizyAirLib !== 'undefined' && typeof bizyAirLib.showDatasetSelect === 'function') { + bizyAirLib.showDatasetSelect({ + showDatasetSelect: true, + isNodeSelect: true, + onApply: (versionId, name) => { + console.log("onApply", versionId, name) + if (!currentNode || !currentNode.widgets) return; + + const datasetPath = currentNode.widgets.find(widget => widget.name == "dataset_path"); + const datasetVersionId = currentNode.widgets.find(w => w.name === "dataset_version_id"); + if (datasetPath && datasetVersionId) { + datasetPath.value = name; + datasetVersionId.value = versionId; + currentNode.setDirtyCanvas(true); + } + } + }) + } else { + console.error("bizyAirLib not available"); + } + return false; + } + } catch (error) { + console.error("Error handling mouse event:", error); + } + }; + + targetWidget.node = this; + targetWidget.options = targetWidget.options || {}; + targetWidget.options.values = () => []; + targetWidget.options.editable = false; + targetWidget.clickable = true; + targetWidget.processMouse = true; + } + } +} + +function setupNodeMouseBehavior(node) { + hideWidget(node, "dataset_version_id"); + + if (!node._bizyairState) { + node._bizyairState = { + lastClickTime: 0, + DEBOUNCE_DELAY: 300, + original_onMouseDown: node.onMouseDown + }; + } + + node.onMouseDown = function(e, pos, canvas) { + if (e.widgetClick) { + return this._bizyairState.original_onMouseDown?.apply(this, arguments); + } + const targetWidget = this.widgets.find(widget => widget.name == "dataset_path"); + if (targetWidget && pos[1] - targetWidget.last_y > 0 && pos[1] - targetWidget.last_y < 20) { + const litecontextmenu = document.querySelector('.litegraph.litecontextmenu') + if (litecontextmenu) { + litecontextmenu.style.display = 'none' + } + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.button !== 0) { + return false; + } + + const currentTime = new Date().getTime(); + if (currentTime - this._bizyairState.lastClickTime < this._bizyairState.DEBOUNCE_DELAY) { + return false; + } + this._bizyairState.lastClickTime = currentTime; + bizyAirLib.showDatasetSelect({ + showDatasetSelect: true, + isNodeSelect: true, + onApply: (versionId, name) => { + console.log("onApply", versionId, name) + if (!currentNode || !currentNode.widgets) return; + + const datasetPath = currentNode.widgets.find(widget => widget.name == "dataset_path"); + const datasetVersionId = currentNode.widgets.find(w => w.name === "dataset_version_id"); + if (datasetPath && datasetVersionId) { + datasetPath.value = name; + datasetVersionId.value = versionId; + currentNode.setDirtyCanvas(true); + } + } + }) + return false; + } else { + return this._bizyairState.original_onMouseDown?.apply(this, arguments); + } + } +} + +app.registerExtension({ + name: "bizyair.siliconcloud.share.dataset.loader.train", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "BizyAir_TrainDatasetAdd") { + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + try { + const result = onNodeCreated?.apply(this, arguments); + createSetWidgetCallback().call(this); + return result; + } catch (error) { + console.error("Error in node creation:", error); + } + }; + } + }, + + async nodeCreated(node) { + if (node?.comfyClass === "BizyAir_TrainDatasetAdd") { + setupNodeMouseBehavior(node); + } + } +}) diff --git a/bizyui/js/deprecated.js b/bizyui/js/deprecated.js new file mode 100644 index 00000000..492ba209 --- /dev/null +++ b/bizyui/js/deprecated.js @@ -0,0 +1,22 @@ +import { app } from "../../scripts/app.js"; + + +app.registerExtension({ + name: "bizyair.deprecated.nodes", + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + const warning_msg = { + // "BizyAirJoyCaption": "It will be available until 2024/11/16. Please use \"☁️BizyAir Joy Caption2\" node instead.", + } + if (Object.keys(warning_msg).includes(nodeData.name)) { + async function alert_deprecated(node_name, display_name) { + alert(`${display_name}: ${warning_msg[node_name]}`); + } + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function (message) { + onNodeCreated?.apply(this, arguments); + alert_deprecated.call(this, nodeData.name, nodeData.display_name); + }; + } + }, +}); diff --git a/bizyui/js/dialog/sam_edit.js b/bizyui/js/dialog/sam_edit.js new file mode 100644 index 00000000..02629337 --- /dev/null +++ b/bizyui/js/dialog/sam_edit.js @@ -0,0 +1,1022 @@ +import { api } from "../../../scripts/api.js"; +import { ComfyApp } from "../../../scripts/app.js"; +import { $el, ComfyDialog, } from "../../../scripts/ui.js"; + +function loadImage(imagePath) { + return new Promise((resolve, reject) => { + const image = new Image(); + image.onload = function() { + resolve(image); + }; + image.src = imagePath; + }); + } +function prepare_mask(image, maskCanvas, maskCtx, maskColor) { + maskCtx.drawImage(image, 0, 0, maskCanvas.width, maskCanvas.height); + const maskData = maskCtx.getImageData( + 0, + 0, + maskCanvas.width, + maskCanvas.height + ); + for (let i = 0; i < maskData.data.length; i += 4) { + if (maskData.data[i + 3] == 255) maskData.data[i + 3] = 0; + else if(maskData.data[i + 3] == 0){ //对应黑色点 + maskData.data[i + 3] = 255; + maskData.data[i] = 0; + maskData.data[i + 1] = 0; + maskData.data[i + 2] = 0; + } + else{ //对应红色点254 + maskData.data[i + 3] = 255; + maskData.data[i] = 255; + maskData.data[i + 1] = 0; + maskData.data[i + 2] = 0; + } + } + maskCtx.globalCompositeOperation = "source-over"; + maskCtx.putImageData(maskData, 0, 0); +} + +function dataURLToBlob(dataURL) { + const parts = dataURL.split(";base64,"); + const contentType = parts[0].split(":")[1]; + const byteString = atob(parts[1]); + const arrayBuffer = new ArrayBuffer(byteString.length); + const uint8Array = new Uint8Array(arrayBuffer); + for (let i = 0; i < byteString.length; i++) { + uint8Array[i] = byteString.charCodeAt(i); + } + return new Blob([arrayBuffer], { type: contentType }); +} +async function uploadMask(filepath, formData) { + await api.fetchApi("/upload/mask", { + method: "POST", + body: formData + }).then((response) => { + }).catch((error) => { + console.error("Error:", error); + }); + ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]] = new Image(); + ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]].src = api.apiURL( + "/view?" + new URLSearchParams(filepath).toString() + app.getPreviewFormatParam() + app.getRandParam() + ); + if (ComfyApp.clipspace.images) + ComfyApp.clipspace.images[ComfyApp.clipspace["selectedIndex"]] = filepath; + if (ComfyApp.clipspace && ComfyApp.clipspace.imgs && ComfyApp.clipspace.imgs.length > 0) { + const img_preview = document.getElementById( + "clipspace_preview" + ); + if (img_preview) { + img_preview.src = ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]].src; + img_preview.style.maxHeight = "100%"; + img_preview.style.maxWidth = "100%"; + } + } +} +async function uploadSamPrompt(formData){ + await api.fetchApi("/bizyair/postsam", { + method: "POST", + body: formData + }).then((response) => { + }).catch((error) => { + console.error("Error:", error); + }); +} + +class SAMEditorDialog extends ComfyDialog { + static instance = null; + static mousedown_x = null; + static mousedown_y = null; + static dragging = false; + static mousedown = null; + brush; + maskCtx; + maskCanvas; + brush_size_slider; + brush_opacity_slider; + pointButton; + saveButton; + zoom_ratio; + pan_x; + pan_y; + imgCanvas; + last_display_style; + is_visible; + image; + handler_registered; + brush_slider_input; + cursorX; + cursorY; + mousedown_pan_x; + mousedown_pan_y; + last_pressure; + history = []; + coords = []; + filename = " "; + static getInstance() { + if (!SAMEditorDialog.instance) { + SAMEditorDialog.instance = new SAMEditorDialog(); + } + return SAMEditorDialog.instance; + } + + is_layout_created = false; + constructor() { + super(); + this.element = $el("div.comfy-modal", { parent: document.body }, [ + $el("div.comfy-modal-content", [...this.createButtons()]) + ]); + } + createButtons() { + return []; + } + createButton(name, callback) { + var button = document.createElement("button"); + button.style.pointerEvents = "auto"; + button.innerText = name; + button.addEventListener("click", callback); + return button; + } + createLeftButton(name, callback) { + var button = this.createButton(name, callback); + button.style.cssFloat = "left"; + button.style.marginRight = "4px"; + return button; + } + createRightButton(name, callback) { + var button = this.createButton(name, callback); + button.style.cssFloat = "right"; + button.style.marginLeft = "4px"; + return button; + } + createLeftSlider(self, name, callback) { + const divElement = document.createElement("div"); + divElement.id = "maskeditor-slider"; + divElement.style.cssFloat = "left"; + divElement.style.fontFamily = "sans-serif"; + divElement.style.marginRight = "4px"; + divElement.style.color = "var(--input-text)"; + divElement.style.backgroundColor = "var(--comfy-input-bg)"; + divElement.style.borderRadius = "8px"; + divElement.style.borderColor = "var(--border-color)"; + divElement.style.borderStyle = "solid"; + divElement.style.fontSize = "15px"; + divElement.style.height = "21px"; + divElement.style.padding = "1px 6px"; + divElement.style.display = "flex"; + divElement.style.position = "relative"; + divElement.style.top = "2px"; + divElement.style.pointerEvents = "auto"; + self.brush_slider_input = document.createElement("input"); + self.brush_slider_input.setAttribute("type", "range"); + self.brush_slider_input.setAttribute("min", "1"); + self.brush_slider_input.setAttribute("max", "100"); + self.brush_slider_input.setAttribute("value", "10"); + const labelElement = document.createElement("label"); + labelElement.textContent = name; + divElement.appendChild(labelElement); + divElement.appendChild(self.brush_slider_input); + self.brush_slider_input.addEventListener("change", callback); + return divElement; + } + createOpacitySlider(self, name, callback) { + const divElement = document.createElement("div"); + divElement.id = "maskeditor-opacity-slider"; + divElement.style.cssFloat = "left"; + divElement.style.fontFamily = "sans-serif"; + divElement.style.marginRight = "4px"; + divElement.style.color = "var(--input-text)"; + divElement.style.backgroundColor = "var(--comfy-input-bg)"; + divElement.style.borderRadius = "8px"; + divElement.style.borderColor = "var(--border-color)"; + divElement.style.borderStyle = "solid"; + divElement.style.fontSize = "15px"; + divElement.style.height = "21px"; + divElement.style.padding = "1px 6px"; + divElement.style.display = "flex"; + divElement.style.position = "relative"; + divElement.style.top = "2px"; + divElement.style.pointerEvents = "auto"; + self.opacity_slider_input = document.createElement("input"); + self.opacity_slider_input.setAttribute("type", "range"); + self.opacity_slider_input.setAttribute("min", "0.1"); + self.opacity_slider_input.setAttribute("max", "1.0"); + self.opacity_slider_input.setAttribute("step", "0.01"); + self.opacity_slider_input.setAttribute("value", "0.7"); + const labelElement = document.createElement("label"); + labelElement.textContent = name; + divElement.appendChild(labelElement); + divElement.appendChild(self.opacity_slider_input); + self.opacity_slider_input.addEventListener("input", callback); + return divElement; + } + setlayout(imgCanvas, maskCanvas) { + const self = this; + var bottom_panel = document.createElement("div"); + bottom_panel.style.position = "absolute"; + bottom_panel.style.bottom = "0px"; + bottom_panel.style.left = "20px"; + bottom_panel.style.right = "20px"; + bottom_panel.style.height = "50px"; + bottom_panel.style.pointerEvents = "none"; + var brush = document.createElement("div"); + brush.id = "brush"; + brush.style.backgroundColor = "transparent"; + brush.style.outline = "1px dashed black"; + brush.style.boxShadow = "0 0 0 1px white"; + brush.style.borderRadius = "50%"; + brush.style.MozBorderRadius = "50%"; + brush.style.WebkitBorderRadius = "50%"; + brush.style.position = "absolute"; + brush.style.zIndex = "8889"; + brush.style.pointerEvents = "none"; + this.brush = brush; + this.element.appendChild(imgCanvas); + this.element.appendChild(maskCanvas); + this.element.appendChild(bottom_panel); + document.body.appendChild(brush); + var clearButton = this.createLeftButton("Clear", () => { + this.resetHistory(); + self.maskCtx.clearRect( + 0, + 0, + self.maskCanvas.width, + self.maskCanvas.height + ); + this.addHistory(); + }); + var undoButton = this.createLeftButton("Undo", () => { + this.undo(); + this.showLastHistory(); + }); + this.brush_size_slider = this.createLeftSlider( + self, + "Thickness", + (event) => { + self.brush_size = event.target.value; + self.updateBrushPreview(self); + } + ); + this.brush_opacity_slider = this.createOpacitySlider( + self, + "Opacity", + (event) => { + self.brush_opacity = event.target.value; + self.maskCanvas.style.opacity = self.brush_opacity.toString(); + } + ); + this.pointButton = this.createLeftButton(this.getPointButtonText(), () => { + if (self.brush_color_mode === "remain") { + self.brush_color_mode = "remove"; + } else if (self.brush_color_mode === "remove") { + self.brush_color_mode = "remain"; + } else { + + } + self.updateWhenBrushColorModeChanged(); + }); + this.modeButton = this.createLeftButton(this.getModeButtonText(), () => { + if (self.mode === "point") { + self.mode = "box"; + } else if (self.mode === "box") { + self.mode = "point"; + } else { + + } + this.modeButton.innerText = this.getModeButtonText(); + this.resetHistory(); + self.maskCtx.clearRect( + 0, + 0, + self.maskCanvas.width, + self.maskCanvas.height + ); + this.addHistory(); + }); + var cancelButton = this.createRightButton("Cancel", () => { + document.removeEventListener("keydown", SAMEditorDialog.handleKeyDown); + self.close(); + }); + this.saveButton = this.createRightButton("Save", () => { + document.removeEventListener("keydown", SAMEditorDialog.handleKeyDown); + self.save(); + }); + this.element.appendChild(imgCanvas); + this.element.appendChild(maskCanvas); + this.element.appendChild(bottom_panel); + bottom_panel.appendChild(clearButton); + bottom_panel.appendChild(undoButton); + bottom_panel.appendChild(this.saveButton); + bottom_panel.appendChild(cancelButton); + bottom_panel.appendChild(this.brush_size_slider); + bottom_panel.appendChild(this.brush_opacity_slider); + bottom_panel.appendChild(this.pointButton); + bottom_panel.appendChild(this.modeButton); + imgCanvas.style.position = "absolute"; + maskCanvas.style.position = "absolute"; + imgCanvas.style.top = "200"; + imgCanvas.style.left = "0"; + maskCanvas.style.top = imgCanvas.style.top; + maskCanvas.style.left = imgCanvas.style.left; + const maskCanvasStyle = this.getMaskCanvasStyle(); + maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode; + maskCanvas.style.opacity = maskCanvasStyle.opacity.toString(); + } + async show() { + this.zoom_ratio = 1; + this.pan_x = 0; + this.pan_y = 0; + if (!this.is_layout_created) { + const imgCanvas = document.createElement("canvas"); + const maskCanvas = document.createElement("canvas"); + imgCanvas.id = "imageCanvas"; + maskCanvas.id = "maskCanvas"; + this.setlayout(imgCanvas, maskCanvas); + this.imgCanvas = imgCanvas; + this.maskCanvas = maskCanvas; + this.maskCtx = maskCanvas.getContext("2d", { willReadFrequently: true }); + this.setEventHandler(maskCanvas); + this.is_layout_created = true; + const self = this; + + const observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.type === "attributes" && mutation.attributeName === "style") { + if (self.last_display_style && self.last_display_style != "none" && self.element.style.display == "none") { + self.brush.style.display = "none"; + ComfyApp.onClipspaceEditorClosed(); + } + self.last_display_style = self.element.style.display; + } + }); + }); + const config = { attributes: true }; + observer.observe(this.element, config); + } + document.addEventListener("keydown", SAMEditorDialog.handleKeyDown); + if (ComfyApp.clipspace_return_node) { + this.saveButton.innerText = "Save to node"; + } else { + this.saveButton.innerText = "Save"; + } + this.saveButton.disabled = false; + this.element.style.display = "block"; + this.element.style.width = "85%"; + this.element.style.margin = "0 7.5%"; + this.element.style.height = "100vh"; + this.element.style.top = "50%"; + this.element.style.left = "42%"; + this.element.style.zIndex = "8888"; + await this.setImages(this.imgCanvas); + this.is_visible = true; + const resp = await api.fetchApi("/bizyair/isresetsam"); + const status = await resp.json(); + + if(status['isresetsam']){ + this.resetHistory(); + } + + const resp1 = await api.fetchApi("/bizyair/getsam"); + + const sam_old_coords = await resp1.json(); + + if(sam_old_coords.filename === this.filename){ + let sam_coords; + if(sam_old_coords.mode == 1){ + sam_coords = sam_old_coords.point_coords; + } + else{ + sam_coords = sam_old_coords.box_coords; + } + for (const key in sam_coords) { + if (sam_old_coords.hasOwnProperty(key)) { + const value = sam_old_coords[key] + this.coords.push(value); + } + } + } + else{ + this.resetHistory(); + } + + this.addHistory(); + } + isOpened() { + return this.element.style.display == "block"; + } + invalidateCanvas(orig_image, mask_image) { + this.imgCanvas.width = orig_image.width; + this.imgCanvas.height = orig_image.height; + this.maskCanvas.width = orig_image.width; + this.maskCanvas.height = orig_image.height; + let imgCtx = this.imgCanvas.getContext("2d", { willReadFrequently: true }); + let maskCtx = this.maskCanvas.getContext("2d", { + willReadFrequently: true + }); + imgCtx.drawImage(orig_image, 0, 0, orig_image.width, orig_image.height); + prepare_mask(mask_image, this.maskCanvas, maskCtx, this.getMaskColor()); + } + async setImages(imgCanvas) { + let self = this; + const imgCtx = imgCanvas.getContext("2d", { willReadFrequently: true }); + const maskCtx = this.maskCtx; + const maskCanvas = this.maskCanvas; + imgCtx.clearRect(0, 0, this.imgCanvas.width, this.imgCanvas.height); + maskCtx.clearRect(0, 0, this.maskCanvas.width, this.maskCanvas.height); + const filepath = ComfyApp.clipspace.images; + const alpha_url = new URL( + ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]].src + ); + alpha_url.searchParams.delete("channel"); + alpha_url.searchParams.delete("preview"); + alpha_url.searchParams.set("channel", "a"); + let mask_image = await loadImage(alpha_url); + const rgb_url = new URL( + ComfyApp.clipspace.imgs[ComfyApp.clipspace["selectedIndex"]].src + ); + const href = rgb_url.href + const filenameRegex = /filename=(.+?)(&|$)/; + const match = href.match(filenameRegex); + if (match) { + this.filename = match[1].split('%20%5Binput%5D')[0]; + console.log("提取到的filename:", this.filename); + } else { + console.log("未找到filename匹配"); + } + + rgb_url.searchParams.delete("channel"); + rgb_url.searchParams.set("channel", "rgb"); + this.image = new Image(); + this.image.onload = function() { + maskCanvas.width = self.image.width; + maskCanvas.height = self.image.height; + self.invalidateCanvas(self.image, mask_image); + self.initializeCanvasPanZoom(); + }; + this.image.src = rgb_url.toString(); + } + initializeCanvasPanZoom() { + let drawWidth = this.image.width; + let drawHeight = this.image.height; + let width = this.element.clientWidth; + let height = this.element.clientHeight; + if (this.image.width > width) { + drawWidth = width; + drawHeight = drawWidth / this.image.width * this.image.height; + } + if (drawHeight > height) { + drawHeight = height; + drawWidth = drawHeight / this.image.height * this.image.width; + } + this.zoom_ratio = drawWidth / this.image.width; + const canvasX = (width - drawWidth) / 2; + const canvasY = (height - drawHeight) / 2; + this.pan_x = canvasX; + this.pan_y = canvasY; + this.invalidatePanZoom(); + } + invalidatePanZoom() { + let raw_width = this.image.width * this.zoom_ratio; + let raw_height = this.image.height * this.zoom_ratio; + if (this.pan_x + raw_width < 10) { + this.pan_x = 10 - raw_width; + } + if (this.pan_y + raw_height < 10) { + this.pan_y = 10 - raw_height; + } + let width = `${raw_width}px`; + let height = `${raw_height}px`; + let left = `${this.pan_x}px`; + let top = `${this.pan_y}px`; + this.maskCanvas.style.width = width; + this.maskCanvas.style.height = height; + this.maskCanvas.style.left = left; + this.maskCanvas.style.top = top; + this.imgCanvas.style.width = width; + this.imgCanvas.style.height = height; + this.imgCanvas.style.left = left; + this.imgCanvas.style.top = top; + } + setEventHandler(maskCanvas) { + const self = this; + if (!this.handler_registered) { + this.element.addEventListener( + "wheel", + (event) => this.handleWheelEvent(self, event) + ); + + this.element.addEventListener("dragstart", (event) => { + if (event.ctrlKey) { + event.preventDefault(); + } + }); + maskCanvas.addEventListener( + "pointerdown", + (event) => this.handlePointerDown(self, event) + ); + maskCanvas.addEventListener( + "pointermove", + (event) => this.draw_move(self, event) + ); + + maskCanvas.addEventListener("pointerover", (event) => { + this.brush.style.display = "block"; + }); + maskCanvas.addEventListener("pointerleave", (event) => { + this.brush.style.display = "none"; + }); + + maskCanvas.addEventListener( + "pointerup", + (event) => this.handlePointerUp(self, event) + ); + this.handler_registered = true; + } + } + getMaskCanvasStyle() { + if (this.brush_color_mode === "negative") { + return { + mixBlendMode: "difference", + opacity: "1" + }; + } else { + return { + mixBlendMode: "initial", + opacity: this.brush_opacity + }; + } + } + getMaskColor() { + if (this.brush_color_mode === "remain") { + return { r: 0, g: 0, b: 0 }; //black + } + if (this.brush_color_mode === "remove") { + return { r: 255, g: 0, b: 0 }; //red + } + return { r: 0, g: 0, b: 0 }; + } + getMaskFillStyle() { + const maskColor = this.getMaskColor(); + return "rgb(" + maskColor.r + "," + maskColor.g + "," + maskColor.b + ")"; + } + getPointButtonText() { + let colorCaption = "unknown"; + if (this.brush_color_mode === "remain") { + colorCaption = "remain"; + } else if (this.brush_color_mode === "remove") { + colorCaption = "remove"; + } + return "Point mode: " + colorCaption; + } + getModeButtonText() { + let modeCaption = "unknown"; + if (this.mode === "point") { + modeCaption = "point"; + } else if (this.mode === "box") { + modeCaption = "box"; + } + return "Select mode: " + modeCaption; + } + updateWhenBrushColorModeChanged() { + this.pointButton.innerText = this.getPointButtonText(); + const maskCanvasStyle = this.getMaskCanvasStyle(); + this.maskCanvas.style.mixBlendMode = maskCanvasStyle.mixBlendMode; + this.maskCanvas.style.opacity = maskCanvasStyle.opacity.toString(); + } + + brush_opacity = 0.7; + brush_size = 10; + brush_color_mode = "remain"; + drawing_mode = false; + lastx = -1; + lasty = -1; + lasttime = 0; + endx = 0; + endy = 0; + startx = 0; + starty = 0; + mode = "point"; + static handleKeyDown(event) { + const self = SAMEditorDialog.instance; + if (event.key === "]") { + self.brush_size = Math.min(self.brush_size + 2, 100); + self.brush_slider_input.value = self.brush_size; + } else if (event.key === "[") { + self.brush_size = Math.max(self.brush_size - 2, 1); + self.brush_slider_input.value = self.brush_size; + } else if (event.key === "Enter") { + self.save(); + } + self.updateBrushPreview(self); + } + handlePointerUp(self, event) { + event.preventDefault(); + + if(self.mode === "box"){ + self.maskCtx.beginPath(); + if (!event.altKey && event.button == 0) { + self.maskCtx.fillStyle = this.getMaskFillStyle(); + self.maskCtx.globalCompositeOperation = "source-over"; + } else { + self.maskCtx.globalCompositeOperation = "destination-out"; + } + var left = self.startx; + var top = self.starty + var w = self.endx - self.startx + var h = self.endy - self.starty + self.maskCtx.rect(left, top, w, h); + self.maskCtx.fill(); + } + if(self.mode === "box" && this.startx == this.endx && this.starty === this.endy){ + + } + else{ + this.addHistory(); + this.add_coords(); + } + + this.mousedown_x = null; + this.mousedown_y = null; + SAMEditorDialog.instance.drawing_mode = false; + } + updateBrushPreview(self) { + const brush = self.brush; + var centerX = self.cursorX; + var centerY = self.cursorY; + brush.style.width = self.brush_size * 2 * this.zoom_ratio + "px"; + brush.style.height = self.brush_size * 2 * this.zoom_ratio + "px"; + brush.style.left = centerX - self.brush_size * this.zoom_ratio + "px"; + brush.style.top = centerY - self.brush_size * this.zoom_ratio + "px"; + } + handleWheelEvent(self, event) { + event.preventDefault(); + if (event.ctrlKey) { + if (event.deltaY < 0) { + this.zoom_ratio = Math.min(10, this.zoom_ratio + 0.2); + } else { + this.zoom_ratio = Math.max(0.2, this.zoom_ratio - 0.2); + } + this.invalidatePanZoom(); + } else { + if (event.deltaY < 0) this.brush_size = Math.min(this.brush_size + 2, 100); + else this.brush_size = Math.max(this.brush_size - 2, 1); + this.brush_slider_input.value = this.brush_size.toString(); + this.updateBrushPreview(this); + } + } + pointMoveEvent(self, event) { + this.cursorX = event.pageX; + this.cursorY = event.pageY; + self.updateBrushPreview(self); + if (event.ctrlKey) { + event.preventDefault(); + self.pan_move(self, event); + } + let left_button_down = window.TouchEvent && event instanceof TouchEvent || event.buttons == 1; + if (event.shiftKey && left_button_down) { + self.drawing_mode = false; + const y = event.clientY; + let delta = (self.zoom_lasty - y) * 5e-3; + self.zoom_ratio = Math.max( + Math.min(10, self.last_zoom_ratio - delta), + 0.2 + ); + this.invalidatePanZoom(); + return; + } + } + pan_move(self, event) { + if (event.buttons == 1) { + if (SAMEditorDialog.mousedown_x) { + let deltaX = SAMEditorDialog.mousedown_x - event.clientX; + let deltaY = SAMEditorDialog.mousedown_y - event.clientY; + self.pan_x = this.mousedown_pan_x - deltaX; + self.pan_y = this.mousedown_pan_y - deltaY; + self.invalidatePanZoom(); + } + } + } + + draw_move(self, event) { + if (event.ctrlKey || event.shiftKey) { + return; + } + event.preventDefault(); + this.cursorX = event.pageX; + this.cursorY = event.pageY; + self.updateBrushPreview(self); + let left_button_down = window.TouchEvent && event instanceof TouchEvent || event.buttons == 1; + let right_button_down = [2, 5, 32].includes(event.buttons); + if (!event.altKey && left_button_down) { + var diff = performance.now() - self.lasttime; + const maskRect = self.maskCanvas.getBoundingClientRect(); + var x = event.offsetX; + var y = event.offsetY; + if (event.offsetX == null) { + x = event.targetTouches[0].clientX - maskRect.left; + } + if (event.offsetY == null) { + y = event.targetTouches[0].clientY - maskRect.top; + } + x /= self.zoom_ratio; + y /= self.zoom_ratio; + var brush_size = 3; + if (event instanceof PointerEvent && event.pointerType == "pen") { + brush_size *= event.pressure; + this.last_pressure = event.pressure; + } else if (window.TouchEvent && event instanceof TouchEvent && diff < 20) { + brush_size *= this.last_pressure; + } else { + brush_size = 3; + } + if (diff > 20 && !this.drawing_mode) + requestAnimationFrame(() => { + self.maskCtx.beginPath(); + self.maskCtx.fillStyle = this.getMaskFillStyle(); + self.maskCtx.globalCompositeOperation = "source-over"; + self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false); + self.maskCtx.rect(x, y, 20, 30); + self.maskCtx.fill(); + self.lastx = x; + self.lasty = y; + }); + else + requestAnimationFrame(() => { + this.showLastHistory() // 每次绘制先清除上一次 + self.maskCtx.save(); + self.maskCtx.beginPath(); + self.maskCtx.fillStyle = this.getMaskFillStyle(); + self.maskCtx.globalCompositeOperation = "source-over"; + var dx = x - self.lastx; + var dy = y - self.lasty; + var distance = Math.sqrt(dx * dx + dy * dy); + var directionX = dx / distance; + var directionY = dy / distance; + for (var i = 0; i < distance; i += 5) { + var px = self.lastx + directionX * i; + var py = self.lasty + directionY * i; + if(self.mode === "box"){ + self.endx = px; + self.endy = py; + var left = self.endx > self.startx ? self.startx : self.endx + var top = self.endy > self.starty ? self.starty : self.endy + var w = Math.abs(self.endx - self.startx) + var h = Math.abs(self.endy - self.starty) + + self.maskCtx.rect(left, top, w, h); + + self.maskCtx.stroke(); + self.maskCtx.restore(); + } + + } + self.lastx = x; + self.lasty = y; + }); + self.lasttime = performance.now(); + } else if (event.altKey && left_button_down || right_button_down) { + const maskRect = self.maskCanvas.getBoundingClientRect(); + const x2 = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio; + const y2 = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio; + var brush_size = this.brush_size; + if (event instanceof PointerEvent && event.pointerType == "pen") { + brush_size *= event.pressure; + this.last_pressure = event.pressure; + } else if (window.TouchEvent && event instanceof TouchEvent && diff < 20) { + brush_size *= this.last_pressure; + } else { + brush_size = this.brush_size; + } + if (diff > 20 && !this.drawing_mode) + requestAnimationFrame(() => { + self.maskCtx.beginPath(); + self.maskCtx.globalCompositeOperation = "destination-out"; + self.maskCtx.arc(x2, y2, brush_size, 0, Math.PI * 2, false); + self.maskCtx.fill(); + self.lastx = x2; + self.lasty = y2; + }); + else + requestAnimationFrame(() => { + self.maskCtx.beginPath(); + self.maskCtx.globalCompositeOperation = "destination-out"; + var dx = x2 - self.lastx; + var dy = y2 - self.lasty; + var distance = Math.sqrt(dx * dx + dy * dy); + var directionX = dx / distance; + var directionY = dy / distance; + for (var i = 0; i < distance; i += 5) { + var px = self.lastx + directionX * i; + var py = self.lasty + directionY * i; + self.maskCtx.arc(px, py, brush_size, 0, Math.PI * 2, false); + self.maskCtx.fill(); + } + self.lastx = x2; + self.lasty = y2; + }); + self.lasttime = performance.now(); + } + } + handlePointerDown(self, event) { + if (event.ctrlKey) { + if (event.buttons == 1) { + SAMEditorDialog.mousedown_x = event.clientX; + SAMEditorDialog.mousedown_y = event.clientY; + this.mousedown_pan_x = this.pan_x; + this.mousedown_pan_y = this.pan_y; + } + return; + } + var brush_size = this.brush_size; + if (event instanceof PointerEvent && event.pointerType == "pen") { + brush_size *= event.pressure; + this.last_pressure = event.pressure; + } + if ([0, 2, 5].includes(event.button)) { + self.drawing_mode = true; + event.preventDefault(); + if (event.shiftKey) { + self.zoom_lasty = event.clientY; + self.last_zoom_ratio = self.zoom_ratio; + return; + } + const maskRect = self.maskCanvas.getBoundingClientRect(); + const x = (event.offsetX || event.targetTouches[0].clientX - maskRect.left) / self.zoom_ratio; + const y = (event.offsetY || event.targetTouches[0].clientY - maskRect.top) / self.zoom_ratio; + + if (!event.altKey && event.button == 0) { + self.maskCtx.fillStyle = this.getMaskFillStyle(); + self.maskCtx.globalCompositeOperation = "source-over"; + } else { + self.maskCtx.globalCompositeOperation = "destination-out"; + } + if(self.mode === "point"){ + self.maskCtx.beginPath(); + self.maskCtx.arc(x, y, brush_size, 0, Math.PI * 2, false); + self.maskCtx.fill(); + self.startx = x; + self.starty = y; + } + else if(self.mode === "box"){ + self.startx = x; + self.starty = y; + } + + self.lastx = x; + self.lasty = y; + self.lasttime = performance.now(); + } + + } + async save() { + const backupCanvas = document.createElement("canvas"); + const backupCtx = backupCanvas.getContext("2d", { + willReadFrequently: true + }); + backupCanvas.width = this.image.width; + backupCanvas.height = this.image.height; + backupCtx.clearRect(0, 0, backupCanvas.width, backupCanvas.height); + backupCtx.drawImage( + this.maskCanvas, + 0, + 0, + this.maskCanvas.width, + this.maskCanvas.height, + 0, + 0, + backupCanvas.width, + backupCanvas.height + ); + const backupData = backupCtx.getImageData( + 0, + 0, + backupCanvas.width, + backupCanvas.height + ); + for (let i = 0; i < backupData.data.length; i += 4) { + + if (backupData.data[i + 3] == 255 && backupData.data[i] == 0){ + backupData.data[i + 3] = 0; + } + else if(backupData.data[i + 3] == 255 && backupData.data[i] == 255){ + backupData.data[i + 3] = 80; + } + else{ + backupData.data[i + 3] = 255; + backupData.data[i] = 0; + backupData.data[i + 1] = 0; + backupData.data[i + 2] = 0; + + } + + + } + backupCtx.globalCompositeOperation = "source-over"; + backupCtx.putImageData(backupData, 0, 0); + + const coords = { + }; + + this.coords.forEach((item, index) => { + const jsonItem = JSON.stringify(item); + coords[index] = jsonItem; + }); + + const formData = new FormData(); + const filename = "clipspace-mask-" + performance.now() + ".png"; + const item = { + filename, + subfolder: "clipspace", + type: "input" + }; + if (ComfyApp.clipspace.images) ComfyApp.clipspace.images[0] = item; + if (ComfyApp.clipspace.widgets) { + const index = ComfyApp.clipspace.widgets.findIndex( + (obj) => obj.name === "image" + ); + if (index >= 0) ComfyApp.clipspace.widgets[index].value = item; + } + const dataURL = backupCanvas.toDataURL(); + + const blob = dataURLToBlob(dataURL); + let original_url = new URL(this.image.src); + const original_ref = { + filename: original_url.searchParams.get("filename") + }; + let original_subfolder = original_url.searchParams.get("subfolder"); + if (original_subfolder) original_ref.subfolder = original_subfolder; + let original_type = original_url.searchParams.get("type"); + if (original_type) original_ref.type = original_type; + formData.append("image", blob, filename); + formData.append("original_ref", JSON.stringify(original_ref)); + formData.append("type", "input"); + formData.append("subfolder", "clipspace"); + this.saveButton.innerText = "Saving..."; + this.saveButton.disabled = true; + await uploadMask(item, formData); + ComfyApp.onClipspaceEditorSave(); + const samData = new FormData(); + const numItems = this.coords.length; + + samData.append('nums', numItems); + samData.append('coords', JSON.stringify(coords)); + samData.append('mode', (this.mode === "point") ? 1 : 0); + samData.append('filename', filename); + await uploadSamPrompt(samData) + this.close(); + } + addHistory(data) { + this.history.push({ + 'data': this.maskCtx.getImageData(0, 0, this.maskCanvas.width, this.maskCanvas.height) + }); + } + add_coords(){ + if(this.mode === "point"){ + this.coords.push({ + "startx": this.startx, + "starty": this.starty, + "pointType": ((this.brush_color_mode === "remain") ? 1 : 0) + }); + } + else{ + this.coords.push({ + "startx": this.startx, + "starty": this.starty, + "endx": this.endx, + "endy": this.endy + }); + } + } + + showLastHistory() { + if(this.history.length > 0){ + this.maskCtx.putImageData(this.history[this.history.length - 1]['data'], 0, 0) + } + } + undo() { + if(this.history.length > 0){ + this.history.pop(); + } + if(this.coords.length > 0){ + this.coords.pop(); + } + } + + resetHistory() { + while (this.history.length > 0) { + this.history.pop(); + } + while (this.coords.length > 0) { + this.coords.pop(); + } + } + + } + +export function sam_edit() { + const dlg = SAMEditorDialog.getInstance(); + if (!dlg.isOpened()) { + dlg.show(); + } +} diff --git a/bizyui/js/image_caption.js b/bizyui/js/image_caption.js new file mode 100644 index 00000000..e43e0b47 --- /dev/null +++ b/bizyui/js/image_caption.js @@ -0,0 +1,46 @@ +import { app } from "../../scripts/app.js"; +import { ComfyWidgets } from "../../scripts/widgets.js"; + +app.registerExtension({ + name: "bizyair.image.to.caption", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "BizyAirJoyCaption") { + function populate(text) { + if (this.widgets) { + const pos = this.widgets.findIndex((w) => w.name === "showtext"); + if (pos !== -1) { + for (let i = pos; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.(); + } + this.widgets.length = pos; + } + } + + for (const list of text) { + const w = ComfyWidgets["STRING"](this, "showtext", ["STRING", { multiline: true }], app).widget; + w.inputEl.readOnly = true; + w.inputEl.style.opacity = 0.6; + w.value = list; + } + + requestAnimationFrame(() => { + const sz = this.computeSize(); + if (sz[0] < this.size[0]) { + sz[0] = this.size[0]; + } + if (sz[1] < this.size[1]) { + sz[1] = this.size[1]; + } + this.onResize?.(sz); + app.graph.setDirtyCanvas(true, false); + }); + } + + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + populate.call(this, message.text); + }; + } + }, +}) diff --git a/bizyui/js/menus.js b/bizyui/js/menus.js new file mode 100644 index 00000000..da19b91b --- /dev/null +++ b/bizyui/js/menus.js @@ -0,0 +1,91 @@ +import { app } from "../../scripts/app.js"; +import { $el } from "../../scripts/ui.js"; +import { styleMenus } from "./subassembly/styleMenus.js"; +import './bizyair_frontend.js' +class FloatingButton { + constructor(show_cases) { + this.show_cases = show_cases + this.button = $el("div.bizyair-comfy-floating-button", { + parent: document.body, + style: { top: app.menu.element.style.display === 'none' ? '': '60px' }, + onmousedown: (e) => this.startDrag(e), + id: 'bizyair-menu-item', + }, [ + // $el("h2.bizyair-logo"), + // $el("div.bizyair-menu", {}, [ + // $el('strong', {}, ['BizyAir']), + // $el("div.bizyair-menu-item", { + // id: 'bizyair-menu-item', + // }), + // ]), + // $el('div.cmfy-floating-button-closer', { + // onclick: () => this.toggleVisibility(event) + // }) + ]); + + bizyAirLib.mount('#bizyair-menu-item', app) + + + this.dragging = false; + this.visible = true; + + document.addEventListener("mousemove", (e) => this.doDrag(e)); + document.addEventListener("mouseup", () => this.endDrag()); + + } + + getDisplayStyle(element) { + return element.currentStyle ? element.currentStyle.display : window.getComputedStyle(element).display; + } + + startDrag(e) { + document.body.style.userSelect = 'none'; + this.dragging = true; + this.offsetX = e.clientX - this.button.offsetLeft; + this.offsetY = e.clientY - this.button.offsetTop; + e.preventDefault(); + } + + endDrag() { + document.body.style.userSelect = ''; + this.dragging = false; + } + + doDrag(e) { + + if (this.dragging) { + this.button.style.left = `${e.clientX - this.offsetX}px`; + this.button.style.top = `${e.clientY - this.offsetY}px`; + this.button.style.bottom = 'auto'; + this.button.style.right = 'auto'; + } + } + + toggleVisibility(e) { + e.stopPropagation(); + const comfyFloatingButton = document.querySelector('.comfy-floating-button') + const bizyairMenu = document.querySelector('.bizyair-menu') + const bizyairMenuCloser = document.querySelector('.cmfy-floating-button-closer') + if (this.visible) { + comfyFloatingButton.className = 'comfy-floating-button comfy-floating-button-hidden'; + bizyairMenu.className = 'bizyair-menu bizyair-menu-hidden'; + bizyairMenuCloser.className = 'cmfy-floating-button-closer cmfy-floating-button-closer-overturn' + } else { + comfyFloatingButton.className = 'comfy-floating-button'; + bizyairMenu.className = 'bizyair-menu'; + bizyairMenuCloser.className = 'cmfy-floating-button-closer' + } + this.visible = !this.visible; + } +} + +app.registerExtension({ + name: "comfy.FloatingButton", + async setup() { + $el("style", { + textContent: styleMenus, + parent: document.head, + }); + new FloatingButton(); + }, +}); diff --git a/bizyui/js/model_apply.js b/bizyui/js/model_apply.js new file mode 100644 index 00000000..c623c49d --- /dev/null +++ b/bizyui/js/model_apply.js @@ -0,0 +1,189 @@ +import { app } from "../../scripts/app.js"; + +import './bizyair_frontend.js' +import { hideWidget } from './subassembly/tools.js' + +const possibleWidgetNames=[ + "clip_name", + "clip_name1", + "clip_name2", + "ckpt_name", + "lora_name", + "control_net_name", + "ipadapter_file", + "unet_name", + "vae_name", + "model_name", + "instantid_file", + "pulid_file", + "style_model_name", +] +function createSetWidgetCallback(modelType, selectedBaseModels = []) { + return function setWidgetCallback() { + const targetWidget = this.widgets.find(widget => possibleWidgetNames.includes(widget.name)); + if (targetWidget) { + targetWidget.value = targetWidget.value || "to choose" + targetWidget.mouse = function(e, pos, canvas) { + try { + if (e.type === "pointerdown" || e.type === "mousedown" || e.type === "click" || e.type === "pointerup") { + e.preventDefault(); + e.stopPropagation(); + e.widgetClick = true; + + const currentNode = this.node; + + if (!currentNode || !currentNode.widgets) { + console.warn("Node or widgets not available"); + return false; + } + + if (typeof bizyAirLib !== 'undefined' && typeof bizyAirLib.showModelSelect === 'function') { + bizyAirLib.showModelSelect({ + modelType: [modelType], + selectedBaseModels, + onApply: (version, model) => { + if (!currentNode || !currentNode.widgets) return; + + const currentLora = currentNode.widgets.find(widget => possibleWidgetNames.includes(widget.name)); + const currentModel = currentNode.widgets.find(w => w.name === "model_version_id"); + + if (model && currentModel && version) { + currentLora.value = model; + currentModel.value = version.id; + currentNode.setDirtyCanvas(true); + } + } + }); + } else { + console.error("bizyAirLib not available"); + } + return false; + } + } catch (error) { + console.error("Error handling mouse event:", error); + } + }; + + targetWidget.node = this; + targetWidget.options = targetWidget.options || {}; + targetWidget.options.values = () => []; + targetWidget.options.editable = false; + targetWidget.clickable = true; + targetWidget.processMouse = true; + } + } +} + +function setupNodeMouseBehavior(node, modelType) { + hideWidget(node, "model_version_id"); + + if (!node._bizyairState) { + node._bizyairState = { + lastClickTime: 0, + DEBOUNCE_DELAY: 300, + original_onMouseDown: node.onMouseDown + }; + } + + node.onMouseDown = function(e, pos, canvas) { + if (e.widgetClick) { + return this._bizyairState.original_onMouseDown?.apply(this, arguments); + } + + + + const targetWidget = this.widgets.find(widget => possibleWidgetNames.includes(widget.name)); + if (targetWidget && pos[1] - targetWidget.last_y > 0 && pos[1] - targetWidget.last_y < 20) { + const litecontextmenu = document.querySelector('.litegraph.litecontextmenu') + if (litecontextmenu) { + litecontextmenu.style.display = 'none' + } + e.stopImmediatePropagation(); + e.preventDefault(); + if (e.button !== 0) { + return false; + } + + const currentTime = new Date().getTime(); + if (currentTime - this._bizyairState.lastClickTime < this._bizyairState.DEBOUNCE_DELAY) { + return false; + } + this._bizyairState.lastClickTime = currentTime; + + const currentNode = this; + bizyAirLib.showModelSelect({ + modelType: [modelType], + selectedBaseModels: [], + onApply: (version, model) => { + if (!currentNode || !currentNode.widgets) return; + + const currentLora = currentNode.widgets.find(widget => possibleWidgetNames.includes(widget.name)); + const currentModel = currentNode.widgets.find(w => w.name === "model_version_id"); + + if (model && currentModel && version) { + currentLora.value = model; + currentModel.value = version.id; + currentNode.setDirtyCanvas(true); + } + } + }); + return false; + } else { + return this._bizyairState.original_onMouseDown?.apply(this, arguments); + } + } +} + +const nodeDataNames = { + LoRA: ["BizyAir_LoraLoader","BizyAir_NunchakuFluxLoraLoader"], + Controlnet: "BizyAir_ControlNetLoader", + Checkpoint: "BizyAir_CheckpointLoaderSimple", + // Clip: "BizyAir_CLIPVisionLoader", + // Ipadapter: "BizyAir_IPAdapterModelLoade", + // Unet: "BizyAir_MZ_KolorsUNETLoaderV2", + // Vae: "BizyAir_VAELoader", + // Upscale_models: "BizyAir_UpscaleModelLoader", + // Instantid: "BizyAir_InstantIDModelLoader", + // Pulid: "BizyAir_PulidFluxModelLoader" +} +app.registerExtension({ + name: "bizyair.siliconcloud.share.lora.loader.new", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + for( const key in nodeDataNames){ + const names = nodeDataNames[key]; + const isMatch = Array.isArray(names) ? + names.includes(nodeData.name) : + (nodeData.name === names); + + if(isMatch){ + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function() { + try { + const result = onNodeCreated?.apply(this, arguments); + let selectedBaseModels = [];if (nodeData.name === nodeDataNames.Checkpoint) { + selectedBaseModels = ['SDXL', 'Pony', 'SD 3.5', 'Illustrious'] + } + createSetWidgetCallback(key, selectedBaseModels).call(this); + return result; + } catch (error) { + console.error("Error in node creation:", error); + } + }; + } + } + }, + + async nodeCreated(node) { + for (const key in nodeDataNames) { + + const names = nodeDataNames[key]; // string | array | undefined + const isMatch = names ? + (Array.isArray(names) ? names.includes(node?.comfyClass) : node?.comfyClass === names) + : false; + + if (isMatch) { + setupNodeMouseBehavior(node, key); + } + } + } +}) diff --git a/bizyui/js/sam_loader.js b/bizyui/js/sam_loader.js new file mode 100644 index 00000000..a8579188 --- /dev/null +++ b/bizyui/js/sam_loader.js @@ -0,0 +1,70 @@ +import { app, ComfyApp } from "../../scripts/app.js"; +import { sam_edit } from "./dialog/sam_edit.js"; +import { api } from "../../../scripts/api.js"; + +app.registerExtension({ + name: "bizyair.sam.nodes", + + async beforeRegisterNodeDef(nodeType, nodeData, app) { + + if(nodeData.name === "BizyAirSegmentAnythingPointBox"){ + const original_getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions; + + nodeType.prototype.getExtraMenuOptions = function(_, options) { + original_getExtraMenuOptions?.apply(this, arguments); + options.push({ + content: "Open in SAM EDITOR", + callback: async () => { + ComfyApp.copyToClipspace(this); + ComfyApp.clipspace_return_node = this; + sam_edit() + + } + }) + } + } + + + }, + async nodeCreated(node){ + + function showImage(name) { + const img = new Image(); + img.onload = () => { + node.imgs = [img]; + app.graph.setDirtyCanvas(true); + }; + let folder_separator = name.lastIndexOf("/"); + let subfolder = ""; + if (folder_separator > -1) { + subfolder = name.substring(0, folder_separator); + name = name.substring(folder_separator + 1); + } + img.src = api.apiURL( + `/view?filename=${encodeURIComponent(name)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}` + ); + node.setSizeForImage?.(); + } + async function reset_sam(){ + await api.fetchApi("/bizyair/resetsam"); + } + + if(node.title === "☁️BizyAir Point-Box Guided SAM"){ + const imageWidget = node.widgets.find(widget => widget.name === "image"); + const cb = node.callback; + if (imageWidget) { + imageWidget.callback = async function(){ + showImage(imageWidget.value); + if (cb) { + return cb.apply(this, arguments); + } + await reset_sam(); + }; + } else { + console.log("image widget not found"); + } + + } + } + +}); diff --git a/bizyui/js/share_load_controlnet.js b/bizyui/js/share_load_controlnet.js new file mode 100644 index 00000000..4f6ec5b7 --- /dev/null +++ b/bizyui/js/share_load_controlnet.js @@ -0,0 +1,43 @@ +import { api } from "../../../scripts/api.js"; +import { app } from "../../scripts/app.js"; +app.registerExtension({ + name: "bizyair.siliconcloud.share.controlnet.loader", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "BizyAir_SharedControlNetLoader") { + async function onTextChange(share_id, canvas, comfynode) { + console.log("share_id:", share_id); + const response = await api.fetchApi(`/bizyair/modelhost/${share_id}/models/files?type=bizyair/controlnet`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + const { data: controlnets } = await response.json(); + const controlnet_name_widget = comfynode.widgets.find(widget => widget.name === "control_net_name"); + if (controlnets.length > 0) { + controlnet_name_widget.value = controlnets[0]; + controlnet_name_widget.options.values = controlnets; + } else { + console.log("No controlnets found in the response"); + controlnet_name_widget.value = ""; + controlnet_name_widget.options.values = []; + } + } + + function setWigetCallback(){ + const shareid_widget = this.widgets.find(widget => widget.name === "share_id"); + if (shareid_widget) { + shareid_widget.callback = onTextChange; + } else { + console.log("share_id widget not found"); + } + } + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, arguments); + setWigetCallback.call(this, arguments); + }; + } + }, +}) diff --git a/bizyui/js/share_lora_loader.js b/bizyui/js/share_lora_loader.js new file mode 100644 index 00000000..96bd5d97 --- /dev/null +++ b/bizyui/js/share_lora_loader.js @@ -0,0 +1,43 @@ +import { api } from "../../../scripts/api.js"; +import { app } from "../../scripts/app.js"; +app.registerExtension({ + name: "bizyair.siliconcloud.share.lora.loader", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "BizyAir_SharedLoraLoader") { + async function onTextChange(share_id, canvas, comfynode) { + console.log("share_id:", share_id); + const response = await api.fetchApi(`/bizyair/modelhost/${share_id}/models/files?type=bizyair/lora`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + const { data: loras_list } = await response.json(); + const lora_name_widget = comfynode.widgets.find(widget => widget.name === "lora_name"); + if (loras_list.length > 0) { + lora_name_widget.value = loras_list[0]; + lora_name_widget.options.values = loras_list; + } else { + console.log("No loras found in the response"); + lora_name_widget.value = ""; + lora_name_widget.options.values = []; + } + } + + function setWigetCallback(){ + const shareid_widget = this.widgets.find(widget => widget.name === "share_id"); + if (shareid_widget) { + shareid_widget.callback = onTextChange; + } else { + console.log("share_id widget not found"); + } + } + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, arguments); + setWigetCallback.call(this, arguments); + }; + } + }, +}) diff --git a/bizyui/js/siliconcloud_llm_api.js b/bizyui/js/siliconcloud_llm_api.js new file mode 100644 index 00000000..551c8a5d --- /dev/null +++ b/bizyui/js/siliconcloud_llm_api.js @@ -0,0 +1,87 @@ +import { app } from "../../scripts/app.js"; + +const createModelFetchExtension = (nodeName, endpoint) => { + return { + name: `bizyair.siliconcloud.${nodeName.toLowerCase()}.api.model_fetch`, + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === nodeName) { + const originalNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = async function () { + if (originalNodeCreated) { + originalNodeCreated.apply(this, arguments); + } + + const modelWidget = this.widgets.find((w) => w.name === "model"); + + const fetchModels = async () => { + try { + const response = await fetch(endpoint, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({}), + }); + + if (response.ok) { + const models = await response.json(); + console.debug(`Fetched ${nodeName} models:`, models); + return models; + } else { + console.error(`Failed to fetch ${nodeName} models: ${response.status}`); + return []; + } + } catch (error) { + console.error(`Error fetching ${nodeName} models`, error); + return []; + } + }; + + const updateModels = async () => { + const prevValue = modelWidget.value; + modelWidget.value = ""; + modelWidget.options.values = []; + + const models = await fetchModels(); + + modelWidget.options.values = models; + console.debug(`Updated ${nodeName} modelWidget.options.values:`, modelWidget.options.values); + + if (models.includes(prevValue)) { + modelWidget.value = prevValue; // stay on current. + } else if (models.length > 0) { + modelWidget.value = models[0]; // set first as default. + } + + console.debug(`Updated ${nodeName} modelWidget.value:`, modelWidget.value); + app.graph.setDirtyCanvas(true); + }; + + const dummy = async () => { + // calling async method will update the widgets with actual value from the browser and not the default from Node definition. + }; + + // Initial update + await dummy(); // this will cause the widgets to obtain the actual value from web page. + await updateModels(); + }; + } + }, + }; +}; + +// LLM Extension +app.registerExtension( + createModelFetchExtension( + "BizyAirSiliconCloudLLMAPI", + "/bizyair/get_silicon_cloud_llm_models" + ) +); + +// VLM Extension +app.registerExtension( + createModelFetchExtension( + "BizyAirSiliconCloudVLMAPI", + "/bizyair/get_silicon_cloud_vlm_models" + ) +); diff --git a/bizyui/js/subassembly/dialog.js b/bizyui/js/subassembly/dialog.js new file mode 100644 index 00000000..ff716bf6 --- /dev/null +++ b/bizyui/js/subassembly/dialog.js @@ -0,0 +1,166 @@ +import { $el } from "../../../scripts/ui.js"; + + +function generateUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = window.crypto.getRandomValues(new Uint8Array(1))[0] % 16 | (c === 'x' ? 0 : 8); + return r.toString(16); + }); +} +let dialogStack = []; +export function dialog(params) { + const id = `bizyair-dialog${generateUUID()}`; + const style = {}; + let h = 'calc(80vh - 40px - 40px)'; + if (params.yesText || params.noText || params.neutralText) { + h = 'calc(80vh - 40px - 40px - 34px)'; + } + if (params.title) { + h = 'calc(80vh - 40px - 40px - 34px - 48px)' + } + function setContent() { + if (params.content) { + return $el("div.bizyair-new-dialog-body", { + style: { maxHeight: h } + }, [ + (params.type && params.type === 'succeed' ? $el('div.bizyair-new-dialog-icon.bizyair-new-dialog-succeed', {}, []) : ''), + (params.type && params.type === 'warning' ? $el('div.bizyair-new-dialog-icon.bizyair-new-dialog-warning', {}, []) : ''), + (params.type && params.type === 'error' ? $el('div.bizyair-new-dialog-icon.bizyair-new-dialog-error', {}, []) : ''), + params.content + ]) + } + return '' + } + const el = $el("div.bizyair-new-dialog", { + parent: document.body, + id, + style: { zIndex: 10000 + document.querySelectorAll('.bizyair-new-dialog').length }, + onclick: function () { + if (params.closeOnClickModal) { + removeDialog() + } + }, + }, [ + $el("div.bizyair-dialog-content", { + style, + onclick: (e) => { + e.stopPropagation(); + } + }, [ + (!params.closeOnClickModal ? $el('div.bizyair-icon-operate.bizyair-icon-nude-close.bizyair-dialog-content-close', { + onclick: () => { + if (params.onNo) { + params.onNo(); + } + removeDialog(document.getElementById(id)) + } + }) : ''), + (params.title ? $el("p.bizyair-new-dialog-title", {}, [params.title]) : ''), + setContent(), + $el('div.bizyair-new-dialog-footer', {}, [ + (params.yesText ? $el("button.bizyair-new-dialog-btn", { + type: "button", + textContent: params.yesText, + id: params.yesId ? params.yesId : '', + onclick: async () => { + if (params.onYes) { + const res = await params.onYes(); + if (!res) { + return false + } + } + removeDialog() + } + }) : ''), + (params.neutralText ? $el("button.bizyair-new-dialog-btn", { + type: "button", + textContent: params.neutralText, + id: params.neutralId ? params.neutralId : '', + onclick: async () => { + if (params.onNeutral) { + const res = await params.onNeutral(); + if (!res) { + return false + } + } + removeDialog() + } + }) : ''), + (params.noText ? $el("button.bizyair-new-dialog-btn", { + type: "button", + textContent: params.noText, + onclick: async () => { + if (params.onNo) { + await params.onNo(); + } + removeDialog() + } + }) : '') + ]), + ]) + ]); + const fnEscapeClose = async (e) => { + if (e.key === "Escape") { + const topDialog = dialogStack[dialogStack.length - 1]; + + if (topDialog === el) { + if (params.onNo) { + await params.onNo(); + } + removeDialog(); + } + } + }; + dialogStack.push(el); + + if (!params.onEscape) { + document.addEventListener("keydown", fnEscapeClose); + } + + function removeDialog() { + const el = document.getElementById(id); + requestAnimationFrame(() => { + el.querySelector('.bizyair-dialog-content').style.transition = 'all 0.2s'; + el.querySelector('.bizyair-dialog-content').style.transform = 'translate(-50%, -50%) scale(0)'; + el.style.transition = 'all 0.3s'; + el.style.opacity = '0'; + setTimeout(() => { + el.remove(); + if (params.onClose) { + params.onClose(); + } + }, 200); + }); + document.removeEventListener("keydown", fnEscapeClose); + dialogStack = dialogStack.filter(d => d !== el); + } + return { + close: () => { + removeDialog(document.getElementById(id)) + } + } +} +dialog.succeed = params => { + if (typeof params === 'string') { + const contentParams = { content: params, type: 'succeed' } + dialog(contentParams) + } + params.type = 'succeed'; + dialog(params) +} +dialog.warning = params => { + if (typeof params === 'string') { + const contentParams = { content: params, type: 'warning' } + dialog(contentParams) + } + params.type = 'warning'; + dialog(params) +} +dialog.error = params => { + if (typeof params === 'string') { + const contentParams = { content: params, type: 'error' } + dialog(contentParams) + } + params.type = 'error'; + dialog(params) +} diff --git a/bizyui/js/subassembly/styleDialog.js b/bizyui/js/subassembly/styleDialog.js new file mode 100644 index 00000000..ae20390c --- /dev/null +++ b/bizyui/js/subassembly/styleDialog.js @@ -0,0 +1,81 @@ +export const styleDialog = ` +.bizyair-new-dialog{ + position: fixed; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.6); +} +.bizyair-dialog-content{ + position: fixed; + left: 50%; + top: 50%; + transform: translate(-50%, -50%) scale(1); + padding: 24px; + min-width: 400px; + min-height: 120px; + max-height: 80vh; + max-width: 80vw; + color: var(--p-dialog-color); + border-radius: var(--radius-rounded-lg, 8px); + border: 1px solid var(--border-border-toast-destructive, rgba(31, 41, 55, 0.40)); + background: #353535; + box-shadow: 0px 20px 40px 0px rgba(0, 0, 0, 0.25); + position: relative; +} +.bizyair-dialog-content-close{ + position: absolute; + right: 20px; + top: 20px; + margin: 0; +} +.bizyair-new-dialog-title{ + font-size: 18px; + line-height: 26px; + font-weight: bold; + margin: 0; + padding: 0; +} +.bizyair-new-dialog-body{ + margin: 16px 0 20px 0; + overflow-y: auto; +} +.bizyair-new-dialog-footer{ + display: flex; + justify-content: flex-end; +} +.bizyair-new-dialog-btn{ + color: var(--input-text); + background-color: var(--comfy-input-bg); + border-radius: 8px; + border-color: var(--border-color); + border-style: solid; + fons-size: 20px; + box-sizing: border-box; + line-height: 30px; + cursor: pointer; + padding: 0 18px; + margin-left: 10px; +} +.bizyair-new-dialog-btn:hover{ + background-color: var(--comfy-input-bg-hover); +} +.bizyair-new-dialog-icon{ + width: 64px; + height: 64px; + background-size: cover; + background-repeat: no-repeat; + background-position: center; + margin: 20px auto; +} +.bizyair-new-dialog-succeed{ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 36 36'%3E%3Cpath fill='%2367c23a' d='M18 2a16 16 0 1 0 16 16A16 16 0 0 0 18 2m10.45 10.63L15.31 25.76L7.55 18a1.4 1.4 0 0 1 2-2l5.78 5.78l11.14-11.13a1.4 1.4 0 1 1 2 2Z' class='clr-i-solid clr-i-solid-path-1'/%3E%3Cpath fill='none' d='M0 0h36v36H0z'/%3E%3C/svg%3E"); +} +.bizyair-new-dialog-warning{ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 20 20'%3E%3Cpath fill='%23e6a23c' d='M10 2c4.42 0 8 3.58 8 8s-3.58 8-8 8s-8-3.58-8-8s3.58-8 8-8m1.13 9.38l.35-6.46H8.52l.35 6.46zm-.09 3.36c.24-.23.37-.55.37-.96c0-.42-.12-.74-.36-.97s-.59-.35-1.06-.35s-.82.12-1.07.35s-.37.55-.37.97c0 .41.13.73.38.96c.26.23.61.34 1.06.34s.8-.11 1.05-.34'/%3E%3C/svg%3E"); +} +.bizyair-new-dialog-error{ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='%23f56c6c' d='M12 4a8 8 0 1 0 0 16a8 8 0 0 0 0-16M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12m5.793-4.207a1 1 0 0 1 1.414 0L12 10.586l2.793-2.793a1 1 0 1 1 1.414 1.414L13.414 12l2.793 2.793a1 1 0 0 1-1.414 1.414L12 13.414l-2.793 2.793a1 1 0 0 1-1.414-1.414L10.586 12L7.793 9.207a1 1 0 0 1 0-1.414'/%3E%3C/svg%3E"); +} +` diff --git a/bizyui/js/subassembly/styleMenus.js b/bizyui/js/subassembly/styleMenus.js new file mode 100644 index 00000000..16352299 --- /dev/null +++ b/bizyui/js/subassembly/styleMenus.js @@ -0,0 +1,72 @@ +export const styleMenus = ` +.bizyair-comfy-floating-button { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + width: auto; + height: 32px; + border-radius: 4px; + // background: #7C3AED; + display: flex; + // padding: 0px 0 0 4px; + z-index: 999; +} +.bizyair-logo{ + width: 28px; + height: 32px; + margin: 0; + padding: 0; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAAAXNSR0IArs4c6QAAAARzQklUCAgICHwIZIgAAAQwSURBVHic7ZwxiFxVFIb/KyuskCLFFtNpsUIKiwgWEVKslREiWERIIOAKlhYRUgRstlC0EEQsRBKIjaQWi11IkYiCSBZslBVmIEKEWVBQ2IUtZuGzeG/g+Zz39t1z78zszL0fLAvLeeec+efed887796VMplMJpPJZDKZTCaTSQsX6gAgRiKR2Jc0kPSDpB3n3HfTDrhsAtYZSPpA0tfOueNpBFh2AcfsSrrmnBvEdpyKgJJ0KOlN59xOTKcpCShJx5JejyliagJKxUh8MdZ0fiqGkwXjjKR7wEoMZykKKEkvSboew1GKU3jMwDn3fKiTVEegJK0DG6FOUhZQkl4NdZC6gBdCHcxSwENJr7kWJD0t6avSfl/SM232E669VsbpyrmYH9AE3blygp8e8GPF/qYxn5seOc1/AeyY5wg40+LjHDCs2P8NnDXksgr0ZyngrKbwL865iVMLuCjpgaRe5c+fOef+McS5JWndw37fECMuHb/o2w3XXgQOarZHwJohj/XyWh8ehH7+WY3AX+t/AM5L2lbxaFXlS+fcX4YYX0ha9bzmJ0OcuHT8pi/Vrlnnv/e86ujrNcVqyeGq58gbsxFNCCsdE32uYr8G7DXY3THEPws8MYjXjyqElQ6JHlVsV4D7TXbYRt+nBvEANqMKYaVDov2K7VaL3ceG2C9QlEi+PCJSOyuYDsneL+0utdhY677vDeIdAD6lznTpkPBdYIP/lytVvJ86gOsG8UbUFrS50yHpIe3TbAh4lR8UC8ekVbyNg1MnnuT1LNzEpiGm78LxiNM0basEireH580cv4WjD2z6xvBh3ivRe4YdA5+rOe99Sb+peMLYcc49DMitE/NsqH7j+34WuCpp4wSzFUmUP6cf49Q9ovJ00jHOKvDYM04feGuaUzgYo4BbhjjvG2PBki0ij/EvW3q015HbHeIuTRnzhiHG3RZ/VygK9S4sZCFdZdvg/zzNZctWabPpkcPCPcqNObIkTnP3ZodyccD//rhQzYQxtwy+mxoQT6i0/YHbngLCArWzAH7G8I2X19U5AC7U7CxdmYVpqI4o3n/4+p3UbRkBlyfY/mkQEBakpb9l8LnC5Pe770ywXTOKB/BRFBFCOCHBPTxrvtLnpFX1RoNt1xJmEsGvNYNpSW5E7V7V0V999I2axCvtbwQIOAz79BFoSe4To7/q6Btx8p6aewECzr/h0JKb984nitE37jQPKbZ9tNn3sC8gUQScZjH5LfCuiv5cVy6r2CPzu4o9zH8AzzbYrkm6U/62Erw3JuU90pL00Dn3SoiD1HeoBu+NSV3A4BNLKU/hfMwhkA9jOEl1BO5KejnGGeIUBcyHDQM4VnFmONrB65QEPFTks8JSOgLuqpi2UcWTll/AgaS3VSwY0f9fgjT/vTGxmfnemEwmk8lkMplMJpPJZFLjXzMoB90iULCqAAAAAElFTkSuQmCC) no-repeat center center; + background-size: 28px 28px; +} +.menus-item{ + line-height: 32px; + padding: 0 8px 0 32px; + font-size: 14px; + cursor: pointer; + position: relative; + background-size: 18px 18px; + background-repeat: no-repeat; + transition: background-color 0.4s; + background-position: 8px center; + margin: 0 4px; +} +.menus-item:hover{ + background-color: #4A238E; +} +.menus-item:hover .menus-item-arrow{ + opacity: 0; +} +.cmfy-floating-button-closer{ + width: 32px; + height: 32px; + cursor: pointer; + transition: background-color 0.4s; + transition: all 0.4s; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M7 4a1 1 0 0 1 1 1v6.333l10.223-6.815a.5.5 0 0 1 .777.416v14.132a.5.5 0 0 1-.777.416L8 12.667V19a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1m10 3.737L10.606 12L17 16.263z'/%3E%3C/svg%3E") center center no-repeat; + background-size: 16px 16px; + border-radius: 0 4px 4px 0; +} +.cmfy-floating-button-closer:hover{ + background-color: #4A238E; +} +.cmfy-floating-button-closer-overturn{ + transform: rotate(180deg); +} +.comfy-floating-button-hidden{ + padding-left: 2px; + padding-right: 2px; +} +.comfy-floating-button-hidden .cmfy-floating-button-closer{ + width: 0; + margin: 0; +} +.comfy-floating-button-hidden:hover{ + padding-right: 0; +} +.comfy-floating-button-hidden:hover .cmfy-floating-button-closer{ + width: 32px; + border-radius: 4px 0 0 4px; +} +` diff --git a/bizyui/js/subassembly/tools.js b/bizyui/js/subassembly/tools.js new file mode 100644 index 00000000..26d91694 --- /dev/null +++ b/bizyui/js/subassembly/tools.js @@ -0,0 +1,18 @@ +export const hideWidget = (node, widget_name) => { + const widget = node.widgets.find(widget => widget.name === widget_name) + if (!widget) { + return + } + + const originalComputeSize = widget.computeSize; + const originalType = widget.type; + + widget.computeSize = () => [0, -4]; + widget.type = "hidden"; + widget.options = widget.options || {}; + widget.show = () => { + widget.computeSize = originalComputeSize; + widget.type = originalType; + widget.height = undefined; + }; +} diff --git a/index.html b/index.html index dde16aaf..1a770183 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@
- +