Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
610
custom_nodes/ComfyUI-Easy-Use/web_version/v1/js/easy/easy.js
Normal file
610
custom_nodes/ComfyUI-Easy-Use/web_version/v1/js/easy/easy.js
Normal file
@@ -0,0 +1,610 @@
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import {deepEqual, addCss, addMeta, isLocalNetwork} from "../common/utils.js";
|
||||
import {logoIcon, quesitonIcon, rocketIcon, groupIcon, rebootIcon, closeIcon} from "../common/icon.js";
|
||||
import {$t} from '../common/i18n.js';
|
||||
import {toast} from "../common/toast.js";
|
||||
import {$el, ComfyDialog} from "../../../../scripts/ui.js";
|
||||
|
||||
|
||||
addCss('css/index.css')
|
||||
|
||||
api.addEventListener("easyuse-toast",event=>{
|
||||
const content = event.detail.content
|
||||
const type = event.detail.type
|
||||
const duration = event.detail.duration
|
||||
if(!type){
|
||||
toast.info(content, duration)
|
||||
}
|
||||
else{
|
||||
toast.showToast({
|
||||
id: `toast-${type}`,
|
||||
content: `${toast[type+"_icon"]} ${content}`,
|
||||
duration: duration || 3000,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let draggerEl = null
|
||||
let isGroupMapcanMove = true
|
||||
function createGroupMap(){
|
||||
let div = document.querySelector('#easyuse_groups_map')
|
||||
if(div){
|
||||
div.style.display = div.style.display == 'none' ? 'flex' : 'none'
|
||||
return
|
||||
}
|
||||
let groups = app.canvas.graph._groups
|
||||
let nodes = app.canvas.graph._nodes
|
||||
let old_nodes = groups.length
|
||||
div = document.createElement('div')
|
||||
div.id = 'easyuse_groups_map'
|
||||
div.innerHTML = ''
|
||||
let btn = document.createElement('div')
|
||||
btn.style = `display: flex;
|
||||
width: calc(100% - 8px);
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 6px;
|
||||
height: 44px;`
|
||||
let hideBtn = $el('button.closeBtn',{
|
||||
innerHTML:closeIcon,
|
||||
onclick:_=>div.style.display = 'none'
|
||||
})
|
||||
let textB = document.createElement('p')
|
||||
btn.appendChild(textB)
|
||||
btn.appendChild(hideBtn)
|
||||
textB.style.fontSize = '11px'
|
||||
textB.innerHTML = `<b>${$t('Groups Map')} (EasyUse)</b>`
|
||||
div.appendChild(btn)
|
||||
|
||||
div.addEventListener('mousedown', function (e) {
|
||||
var startX = e.clientX
|
||||
var startY = e.clientY
|
||||
var offsetX = div.offsetLeft
|
||||
var offsetY = div.offsetTop
|
||||
|
||||
function moveBox (e) {
|
||||
var newX = e.clientX
|
||||
var newY = e.clientY
|
||||
var deltaX = newX - startX
|
||||
var deltaY = newY - startY
|
||||
div.style.left = offsetX + deltaX + 'px'
|
||||
div.style.top = offsetY + deltaY + 'px'
|
||||
}
|
||||
|
||||
function stopMoving () {
|
||||
document.removeEventListener('mousemove', moveBox)
|
||||
document.removeEventListener('mouseup', stopMoving)
|
||||
}
|
||||
|
||||
if(isGroupMapcanMove){
|
||||
document.addEventListener('mousemove', moveBox)
|
||||
document.addEventListener('mouseup', stopMoving)
|
||||
}
|
||||
})
|
||||
|
||||
function updateGroups(groups, groupsDiv, autoSortDiv){
|
||||
if(groups.length>0){
|
||||
autoSortDiv.style.display = 'block'
|
||||
}else autoSortDiv.style.display = 'none'
|
||||
for (let index in groups) {
|
||||
const group = groups[index]
|
||||
const title = group.title
|
||||
const show_text = $t('Always')
|
||||
const hide_text = $t('Bypass')
|
||||
const mute_text = $t('Never')
|
||||
let group_item = document.createElement('div')
|
||||
let group_item_style = `justify-content: space-between;display:flex;background-color: var(--comfy-input-bg);border-radius: 5px;border:1px solid var(--border-color);margin-top:5px;`
|
||||
group_item.addEventListener("mouseover",event=>{
|
||||
event.preventDefault()
|
||||
group_item.style = group_item_style + "filter:brightness(1.2);"
|
||||
})
|
||||
group_item.addEventListener("mouseleave",event=>{
|
||||
event.preventDefault()
|
||||
group_item.style = group_item_style + "filter:brightness(1);"
|
||||
})
|
||||
group_item.addEventListener("dragstart",e=>{
|
||||
draggerEl = e.currentTarget;
|
||||
e.currentTarget.style.opacity = "0.6";
|
||||
e.currentTarget.style.border = "1px dashed yellow";
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setDragImage(emptyImg, 0, 0);
|
||||
})
|
||||
group_item.addEventListener("dragend",e=>{
|
||||
e.target.style.opacity = "1";
|
||||
e.currentTarget.style.border = "1px dashed transparent";
|
||||
e.currentTarget.removeAttribute("draggable");
|
||||
document.querySelectorAll('.easyuse-group-item').forEach((el,i) => {
|
||||
var prev_i = el.dataset.id;
|
||||
if (el == draggerEl && prev_i != i ) {
|
||||
groups.splice(i, 0, groups.splice(prev_i, 1)[0]);
|
||||
}
|
||||
el.dataset.id = i;
|
||||
});
|
||||
isGroupMapcanMove = true
|
||||
})
|
||||
group_item.addEventListener("dragover",e=>{
|
||||
e.preventDefault();
|
||||
if (e.currentTarget == draggerEl) return;
|
||||
let rect = e.currentTarget.getBoundingClientRect();
|
||||
if (e.clientY > rect.top + rect.height / 2) {
|
||||
e.currentTarget.parentNode.insertBefore(draggerEl, e.currentTarget.nextSibling);
|
||||
} else {
|
||||
e.currentTarget.parentNode.insertBefore(draggerEl, e.currentTarget);
|
||||
}
|
||||
isGroupMapcanMove = true
|
||||
})
|
||||
|
||||
|
||||
group_item.setAttribute('data-id',index)
|
||||
group_item.className = 'easyuse-group-item'
|
||||
group_item.style = group_item_style
|
||||
// 标题
|
||||
let text_group_title = document.createElement('div')
|
||||
text_group_title.style = `flex:1;font-size:12px;color:var(--input-text);padding:4px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;cursor:pointer`
|
||||
text_group_title.innerHTML = `${title}`
|
||||
text_group_title.addEventListener('mousedown',e=>{
|
||||
isGroupMapcanMove = false
|
||||
e.currentTarget.parentNode.draggable = 'true';
|
||||
})
|
||||
text_group_title.addEventListener('mouseleave',e=>{
|
||||
setTimeout(_=>{
|
||||
isGroupMapcanMove = true
|
||||
},150)
|
||||
})
|
||||
group_item.append(text_group_title)
|
||||
// 按钮组
|
||||
let buttons = document.createElement('div')
|
||||
group.recomputeInsideNodes();
|
||||
const nodesInGroup = group._nodes;
|
||||
let isGroupShow = nodesInGroup && nodesInGroup.length>0 && nodesInGroup[0].mode == 0
|
||||
let isGroupMute = nodesInGroup && nodesInGroup.length>0 && nodesInGroup[0].mode == 2
|
||||
let go_btn = document.createElement('button')
|
||||
go_btn.style = "margin-right:6px;cursor:pointer;font-size:10px;padding:2px 4px;color:var(--input-text);background-color: var(--comfy-input-bg);border: 1px solid var(--border-color);border-radius:4px;"
|
||||
go_btn.innerText = "Go"
|
||||
go_btn.addEventListener('click', () => {
|
||||
app.canvas.ds.offset[0] = -group.pos[0] - group.size[0] * 0.5 + (app.canvas.canvas.width * 0.5) / app.canvas.ds.scale;
|
||||
app.canvas.ds.offset[1] = -group.pos[1] - group.size[1] * 0.5 + (app.canvas.canvas.height * 0.5) / app.canvas.ds.scale;
|
||||
app.canvas.setDirty(true, true);
|
||||
app.canvas.setZoom(1)
|
||||
})
|
||||
buttons.append(go_btn)
|
||||
let see_btn = document.createElement('button')
|
||||
let defaultStyle = `cursor:pointer;font-size:10px;;padding:2px;border: 1px solid var(--border-color);border-radius:4px;width:36px;`
|
||||
see_btn.style = isGroupMute ? `background-color:var(--error-text);color:var(--input-text);` + defaultStyle : (isGroupShow ? `background-color:var(--theme-color);color:var(--input-text);` + defaultStyle : `background-color: var(--comfy-input-bg);color:var(--descrip-text);` + defaultStyle)
|
||||
see_btn.innerText = isGroupMute ? mute_text : (isGroupShow ? show_text : hide_text)
|
||||
let pressTimer
|
||||
let firstTime =0, lastTime =0
|
||||
let isHolding = false
|
||||
see_btn.addEventListener('click', () => {
|
||||
if(isHolding){
|
||||
isHolding = false
|
||||
return
|
||||
}
|
||||
for (const node of nodesInGroup) {
|
||||
node.mode = isGroupShow ? 4 : 0;
|
||||
node.graph.change();
|
||||
}
|
||||
isGroupShow = nodesInGroup[0].mode == 0 ? true : false
|
||||
isGroupMute = nodesInGroup[0].mode == 2 ? true : false
|
||||
see_btn.style = isGroupMute ? `background-color:var(--error-text);color:var(--input-text);` + defaultStyle : (isGroupShow ? `background-color:#006691;color:var(--input-text);` + defaultStyle : `background-color: var(--comfy-input-bg);color:var(--descrip-text);` + defaultStyle)
|
||||
see_btn.innerText = isGroupMute ? mute_text : (isGroupShow ? show_text : hide_text)
|
||||
})
|
||||
see_btn.addEventListener('mousedown', () => {
|
||||
firstTime = new Date().getTime();
|
||||
clearTimeout(pressTimer);
|
||||
pressTimer = setTimeout(_=>{
|
||||
for (const node of nodesInGroup) {
|
||||
node.mode = isGroupMute ? 0 : 2;
|
||||
node.graph.change();
|
||||
}
|
||||
isGroupShow = nodesInGroup[0].mode == 0 ? true : false
|
||||
isGroupMute = nodesInGroup[0].mode == 2 ? true : false
|
||||
see_btn.style = isGroupMute ? `background-color:var(--error-text);color:var(--input-text);` + defaultStyle : (isGroupShow ? `background-color:#006691;color:var(--input-text);` + defaultStyle : `background-color: var(--comfy-input-bg);color:var(--descrip-text);` + defaultStyle)
|
||||
see_btn.innerText = isGroupMute ? mute_text : (isGroupShow ? show_text : hide_text)
|
||||
},500)
|
||||
})
|
||||
see_btn.addEventListener('mouseup', () => {
|
||||
lastTime = new Date().getTime();
|
||||
if(lastTime - firstTime > 500) isHolding = true
|
||||
clearTimeout(pressTimer);
|
||||
})
|
||||
buttons.append(see_btn)
|
||||
group_item.append(buttons)
|
||||
|
||||
groupsDiv.append(group_item)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let groupsDiv = document.createElement('div')
|
||||
groupsDiv.id = 'easyuse-groups-items'
|
||||
groupsDiv.style = `overflow-y: auto;max-height: 400px;height:100%;width: 100%;`
|
||||
|
||||
let autoSortDiv = document.createElement('button')
|
||||
autoSortDiv.style = `cursor:pointer;font-size:10px;padding:2px 4px;color:var(--input-text);background-color: var(--comfy-input-bg);border: 1px solid var(--border-color);border-radius:4px;`
|
||||
autoSortDiv.innerText = $t('Auto Sorting')
|
||||
autoSortDiv.addEventListener('click',e=>{
|
||||
e.preventDefault()
|
||||
groupsDiv.innerHTML = ``
|
||||
let new_groups = groups.sort((a,b)=> a['pos'][0] - b['pos'][0]).sort((a,b)=> a['pos'][1] - b['pos'][1])
|
||||
updateGroups(new_groups, groupsDiv, autoSortDiv)
|
||||
})
|
||||
|
||||
updateGroups(groups, groupsDiv, autoSortDiv)
|
||||
|
||||
div.appendChild(groupsDiv)
|
||||
|
||||
let remarkDiv = document.createElement('p')
|
||||
remarkDiv.style = `text-align:center; font-size:10px; padding:0 10px;color:var(--descrip-text)`
|
||||
remarkDiv.innerText = $t('Toggle `Show/Hide` can set mode of group, LongPress can set group nodes to never')
|
||||
div.appendChild(groupsDiv)
|
||||
div.appendChild(remarkDiv)
|
||||
div.appendChild(autoSortDiv)
|
||||
|
||||
let graphDiv = document.getElementById("graph-canvas")
|
||||
graphDiv.addEventListener('mouseover', async () => {
|
||||
groupsDiv.innerHTML = ``
|
||||
let new_groups = app.canvas.graph._groups
|
||||
updateGroups(new_groups, groupsDiv, autoSortDiv)
|
||||
old_nodes = nodes
|
||||
})
|
||||
|
||||
if (!document.querySelector('#easyuse_groups_map')){
|
||||
document.body.appendChild(div)
|
||||
}else{
|
||||
div.style.display = 'flex'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function cleanup(){
|
||||
try {
|
||||
const {Running, Pending} = await api.getQueue()
|
||||
if(Running.length>0 || Pending.length>0){
|
||||
toast.error($t("Clean Failed")+ ":"+ $t("Please stop all running tasks before cleaning GPU"))
|
||||
return
|
||||
}
|
||||
api.fetchApi("/easyuse/cleangpu",{
|
||||
method:"POST"
|
||||
}).then(res=>{
|
||||
if(res.status == 200){
|
||||
toast.success($t("Clean SuccessFully"))
|
||||
}else{
|
||||
toast.error($t("Clean Failed"))
|
||||
}
|
||||
})
|
||||
|
||||
} catch (exception) {}
|
||||
}
|
||||
|
||||
|
||||
let guideDialog = null
|
||||
let isDownloading = false
|
||||
function download_model(url,local_dir){
|
||||
if(isDownloading || !url || !local_dir) return
|
||||
isDownloading = true
|
||||
let body = new FormData();
|
||||
body.append('url', url);
|
||||
body.append('local_dir', local_dir);
|
||||
api.fetchApi("/easyuse/model/download",{
|
||||
method:"POST",
|
||||
body
|
||||
}).then(res=>{
|
||||
if(res.status == 200){
|
||||
toast.success($t("Download SuccessFully"))
|
||||
}else{
|
||||
toast.error($t("Download Failed"))
|
||||
}
|
||||
isDownloading = false
|
||||
})
|
||||
|
||||
}
|
||||
class GuideDialog {
|
||||
|
||||
constructor(note, need_models){
|
||||
this.dialogDiv = null
|
||||
this.modelsDiv = null
|
||||
|
||||
if(need_models?.length>0){
|
||||
let tbody = []
|
||||
|
||||
for(let i=0;i<need_models.length;i++){
|
||||
tbody.push($el('tr',[
|
||||
$el('td',{innerHTML:need_models[i].title || need_models[i].name || ''}),
|
||||
$el('td',[
|
||||
need_models[i]['download_url'] ? $el('a',{onclick:_=>download_model(need_models[i]['download_url'],need_models[i]['local_dir']), target:"_blank", textContent:$t('Download Model')}) : '',
|
||||
need_models[i]['source_url'] ? $el('a',{href:need_models[i]['source_url'], target:"_blank", textContent:$t('Source Url')}) : '',
|
||||
need_models[i]['desciption'] ? $el('span',{textContent:need_models[i]['desciption']}) : '',
|
||||
]),
|
||||
]))
|
||||
}
|
||||
this.modelsDiv = $el('div.easyuse-guide-dialog-models.markdown-body',[
|
||||
$el('h3',{textContent:$t('Models Required')}),
|
||||
$el('table',{cellpadding:0,cellspacing:0},[
|
||||
$el('thead',[
|
||||
$el('tr',[
|
||||
$el('th',{innerHTML:$t('ModelName')}),
|
||||
$el('th',{innerHTML:$t('Description')}),
|
||||
])
|
||||
]),
|
||||
$el('tbody',tbody)
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
this.dialogDiv = $el('div.easyuse-guide-dialog.hidden',[
|
||||
$el('div.easyuse-guide-dialog-header',[
|
||||
$el('div.easyuse-guide-dialog-top',[
|
||||
$el('div.easyuse-guide-dialog-title',{
|
||||
innerHTML:$t('Workflow Guide')
|
||||
}),
|
||||
$el('button.closeBtn',{innerHTML:closeIcon,onclick:_=>this.close()})
|
||||
]),
|
||||
|
||||
$el('div.easyuse-guide-dialog-remark',{
|
||||
innerHTML:`${$t('Workflow created by')} <a href="https://github.com/yolain/" target="_blank">Yolain</a> , ${$t('Watch more video content')} <a href="https://space.bilibili.com/1840885116" target="_blank">B站乱乱呀</a>`
|
||||
})
|
||||
]),
|
||||
$el('div.easyuse-guide-dialog-content.markdown-body',[
|
||||
$el('div.easyuse-guide-dialog-note',{
|
||||
innerHTML:note
|
||||
}),
|
||||
...this.modelsDiv ? [this.modelsDiv] : []
|
||||
])
|
||||
])
|
||||
|
||||
if(disableRenderInfo){
|
||||
this.dialogDiv.classList.add('disable-render-info')
|
||||
}
|
||||
document.body.appendChild(this.dialogDiv)
|
||||
}
|
||||
show(){
|
||||
if(this.dialogDiv) this.dialogDiv.classList.remove('hidden')
|
||||
}
|
||||
|
||||
close(){
|
||||
if(this.dialogDiv){
|
||||
this.dialogDiv.classList.add('hidden')
|
||||
}
|
||||
}
|
||||
toggle(){
|
||||
if(this.dialogDiv){
|
||||
if(this.dialogDiv.classList.contains('hidden')){
|
||||
this.show()
|
||||
}else{
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remove(){
|
||||
if(this.dialogDiv) document.body.removeChild(this.dialogDiv)
|
||||
}
|
||||
}
|
||||
|
||||
// toolbar
|
||||
const toolBarId = "Comfy.EasyUse.toolBar"
|
||||
const getEnableToolBar = _ => app.ui.settings.getSettingValue(toolBarId, true)
|
||||
const getNewMenuPosition = _ => {
|
||||
try{
|
||||
return app.ui.settings.getSettingValue('Comfy.UseNewMenu', 'Disabled')
|
||||
}catch (e){
|
||||
return 'Disabled'
|
||||
}
|
||||
}
|
||||
|
||||
let note = null
|
||||
let toolbar = null
|
||||
let enableToolBar = getEnableToolBar() && getNewMenuPosition() == 'Disabled'
|
||||
let disableRenderInfo = localStorage['Comfy.Settings.Comfy.EasyUse.disableRenderInfo'] ? true : false
|
||||
export function addToolBar(app) {
|
||||
app.ui.settings.addSetting({
|
||||
id: toolBarId,
|
||||
name: $t("Enable tool bar fixed on the left-bottom (ComfyUI-Easy-Use)"),
|
||||
type: "boolean",
|
||||
defaultValue: enableToolBar,
|
||||
onChange(value) {
|
||||
enableToolBar = !!value;
|
||||
if(enableToolBar){
|
||||
showToolBar()
|
||||
}else hideToolBar()
|
||||
},
|
||||
});
|
||||
}
|
||||
function showToolBar(){
|
||||
if(toolbar) toolbar.style.display = 'flex'
|
||||
}
|
||||
function hideToolBar(){
|
||||
if(toolbar) toolbar.style.display = 'none'
|
||||
}
|
||||
let monitor = null
|
||||
function setCrystoolsUI(position){
|
||||
const crystools = document.getElementById('crystools-root')?.children || null
|
||||
if(crystools?.length>0){
|
||||
if(!monitor){
|
||||
for (let i = 0; i < crystools.length; i++) {
|
||||
if (crystools[i].id === 'crystools-monitor-container') {
|
||||
monitor = crystools[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(monitor){
|
||||
if(position == 'Disabled'){
|
||||
let replace = true
|
||||
for (let i = 0; i < crystools.length; i++) {
|
||||
if (crystools[i].id === 'crystools-monitor-container') {
|
||||
replace = false
|
||||
break;
|
||||
}
|
||||
}
|
||||
document.getElementById('crystools-root').appendChild(monitor)
|
||||
}
|
||||
else {
|
||||
let monitor_div = document.getElementById('comfyui-menu-monitor')
|
||||
if(!monitor_div) app.menu.settingsGroup.element.before($el('div',{id:'comfyui-menu-monitor'},monitor))
|
||||
else monitor_div.appendChild(monitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const changeNewMenuPosition = app.ui.settings.settingsLookup?.['Comfy.UseNewMenu']
|
||||
if(changeNewMenuPosition) changeNewMenuPosition.onChange = v => {
|
||||
v == 'Disabled' ? showToolBar() : hideToolBar()
|
||||
setCrystoolsUI(v)
|
||||
}
|
||||
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easyUse",
|
||||
init() {
|
||||
// Canvas Menu
|
||||
const getCanvasMenuOptions = LGraphCanvas.prototype.getCanvasMenuOptions;
|
||||
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
|
||||
const options = getCanvasMenuOptions.apply(this, arguments);
|
||||
let emptyImg = new Image()
|
||||
emptyImg.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=";
|
||||
|
||||
options.push(null,
|
||||
// Groups Map
|
||||
{
|
||||
content: groupIcon.replace('currentColor','var(--warning-color)') + ' '+ $t('Groups Map') + ' (EasyUse)',
|
||||
callback: async() => {
|
||||
createGroupMap()
|
||||
}
|
||||
},
|
||||
// Force clean ComfyUI GPU Used 强制卸载模型GPU占用
|
||||
{
|
||||
content: rocketIcon.replace('currentColor','var(--theme-color-light)') + ' '+ $t('Cleanup Of GPU Usage') + ' (EasyUse)',
|
||||
callback: async() =>{
|
||||
await cleanup()
|
||||
}
|
||||
},
|
||||
// Only show the reboot option if the server is running on a local network 仅在本地或局域网环境可重启服务
|
||||
isLocalNetwork(window.location.host) ? {
|
||||
content: rebootIcon.replace('currentColor','var(--error-color)') + ' '+ $t('Reboot ComfyUI') + ' (EasyUse)',
|
||||
callback: _ =>{
|
||||
if (confirm($t("Are you sure you'd like to reboot the server?"))){
|
||||
try {
|
||||
api.fetchApi("/easyuse/reboot");
|
||||
} catch (exception) {}
|
||||
}
|
||||
}
|
||||
} : null,
|
||||
);
|
||||
return options;
|
||||
};
|
||||
|
||||
let renderInfoEvent = LGraphCanvas.prototype.renderInfo
|
||||
if(disableRenderInfo){
|
||||
LGraphCanvas.prototype.renderInfo = function (ctx, x, y) {}
|
||||
}
|
||||
|
||||
if(!toolbar){
|
||||
toolbar = $el('div.easyuse-toolbar',[
|
||||
$el('div.easyuse-toolbar-item',{
|
||||
onclick:_=>{
|
||||
createGroupMap()
|
||||
}
|
||||
},[
|
||||
$el('div.easyuse-toolbar-icon.group', {innerHTML:groupIcon}),
|
||||
$el('div.easyuse-toolbar-tips',$t('Groups Map'))
|
||||
]),
|
||||
$el('div.easyuse-toolbar-item',{
|
||||
onclick:async()=>{
|
||||
await cleanup()
|
||||
}
|
||||
},[
|
||||
$el('div.easyuse-toolbar-icon.rocket',{innerHTML:rocketIcon}),
|
||||
$el('div.easyuse-toolbar-tips',$t('Cleanup Of GPU Usage'))
|
||||
]),
|
||||
])
|
||||
if(disableRenderInfo){
|
||||
toolbar.classList.add('disable-render-info')
|
||||
}else{
|
||||
toolbar.classList.remove('disable-render-info')
|
||||
}
|
||||
document.body.appendChild(toolbar)
|
||||
}
|
||||
|
||||
// rewrite handleFile
|
||||
let loadGraphDataEvent = app.loadGraphData
|
||||
app.loadGraphData = async function (data, clean=true) {
|
||||
// if(data?.extra?.cpr){
|
||||
// toast.copyright()
|
||||
// }
|
||||
if(data?.extra?.note){
|
||||
if(guideDialog) {
|
||||
guideDialog.remove()
|
||||
guideDialog = null
|
||||
}
|
||||
if(note && toolbar) toolbar.removeChild(note)
|
||||
const need_models = data.extra?.need_models || null
|
||||
guideDialog = new GuideDialog(data.extra.note, need_models)
|
||||
note = $el('div.easyuse-toolbar-item',{
|
||||
onclick:async()=>{
|
||||
guideDialog.toggle()
|
||||
}
|
||||
},[
|
||||
$el('div.easyuse-toolbar-icon.question',{innerHTML:quesitonIcon}),
|
||||
$el('div.easyuse-toolbar-tips',$t('Workflow Guide'))
|
||||
])
|
||||
if(toolbar) toolbar.insertBefore(note, toolbar.firstChild)
|
||||
}
|
||||
else{
|
||||
if(note) {
|
||||
toolbar.removeChild(note)
|
||||
note = null
|
||||
}
|
||||
}
|
||||
return await loadGraphDataEvent.apply(this, [...arguments])
|
||||
}
|
||||
|
||||
addToolBar(app)
|
||||
},
|
||||
async setup() {
|
||||
// New style menu button
|
||||
if(app.menu?.actionsGroup){
|
||||
const groupMap = new (await import('../../../../scripts/ui/components/button.js')).ComfyButton({
|
||||
icon:'list-box',
|
||||
action:()=> createGroupMap(),
|
||||
tooltip: "EasyUse Group Map",
|
||||
// content: "EasyUse Group Map",
|
||||
classList: "comfyui-button comfyui-menu-mobile-collapse"
|
||||
});
|
||||
app.menu.actionsGroup.element.after(groupMap.element);
|
||||
const position = getNewMenuPosition()
|
||||
setCrystoolsUI(position)
|
||||
if(position == 'Disabled') showToolBar()
|
||||
else hideToolBar()
|
||||
// const easyNewMenu = $el('div.easyuse-new-menu',[
|
||||
// $el('div.easyuse-new-menu-intro',[
|
||||
// $el('div.easyuse-new-menu-logo',{innerHTML:logoIcon}),
|
||||
// $el('div.easyuse-new-menu-title',[
|
||||
// $el('div.title',{textContent:'ComfyUI-Easy-Use'}),
|
||||
// $el('div.desc',{textContent:'Version:'})
|
||||
// ])
|
||||
// ])
|
||||
// ])
|
||||
// app.menu?.actionsGroup.element.after(new (await import('../../../../scripts/ui/components/splitButton.js')).ComfySplitButton({
|
||||
// primary: groupMap,
|
||||
// mode:'click',
|
||||
// position:'absolute',
|
||||
// horizontal: 'right'
|
||||
// },easyNewMenu).element);
|
||||
}
|
||||
|
||||
},
|
||||
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name.startsWith("easy")) {
|
||||
const origOnConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined;
|
||||
return r;
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,283 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { $el, ComfyDialog } from "../../../../scripts/ui.js";
|
||||
import { $t } from '../common/i18n.js'
|
||||
import { toast } from "../common/toast.js";
|
||||
import {sleep, accSub} from "../common/utils.js";
|
||||
|
||||
let api_keys = []
|
||||
let api_current = 0
|
||||
let user_info = {}
|
||||
|
||||
const api_cost = {
|
||||
'sd3': 6.5,
|
||||
'sd3-turbo': 4,
|
||||
}
|
||||
|
||||
class AccountDialog extends ComfyDialog {
|
||||
constructor() {
|
||||
super();
|
||||
this.lists = []
|
||||
this.dialog_div = null
|
||||
this.user_div = null
|
||||
}
|
||||
|
||||
addItem(index, user_div){
|
||||
return $el('div.easyuse-account-dialog-item',[
|
||||
$el('input',{type:'text',placeholder:'Enter name',oninput: e=>{
|
||||
const dataIndex = Array.prototype.indexOf.call(this.dialog_div.querySelectorAll('.easyuse-account-dialog-item'), e.target.parentNode)
|
||||
api_keys[dataIndex]['name'] = e.target.value
|
||||
},value:api_keys[index]['name']}),
|
||||
$el('input.key',{type:'text',oninput: e=>{
|
||||
const dataIndex = Array.prototype.indexOf.call(this.dialog_div.querySelectorAll('.easyuse-account-dialog-item'), e.target.parentNode)
|
||||
api_keys[dataIndex]['key'] = e.target.value
|
||||
},placeholder:'Enter APIKEY', value:api_keys[index]['key']}),
|
||||
$el('button.choose',{textContent:$t('Choose'),onclick:async(e)=>{
|
||||
const dataIndex = Array.prototype.indexOf.call(this.dialog_div.querySelectorAll('.easyuse-account-dialog-item'), e.target.parentNode)
|
||||
let name = api_keys[dataIndex]['name']
|
||||
let key = api_keys[dataIndex]['key']
|
||||
if(!name){
|
||||
toast.error($t('Please enter the account name'))
|
||||
return
|
||||
}
|
||||
else if(!key){
|
||||
toast.error($t('Please enter the APIKEY'))
|
||||
return
|
||||
}
|
||||
let missing = true
|
||||
for(let i=0;i<api_keys.length;i++){
|
||||
if(!api_keys[i].key) {
|
||||
missing = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if(!missing){
|
||||
toast.error($t('APIKEY is not Empty'))
|
||||
return
|
||||
}
|
||||
// 保存记录
|
||||
api_current = dataIndex
|
||||
const body = new FormData();
|
||||
body.append('api_keys', JSON.stringify(api_keys));
|
||||
body.append('current',api_current)
|
||||
const res = await api.fetchApi('/easyuse/stability/set_api_keys', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
if (res.status == 200) {
|
||||
const data = await res.json()
|
||||
if(data?.account && data?.balance){
|
||||
const avatar = data.account?.profile_picture || null
|
||||
const email = data.account?.email || null
|
||||
const credits = data.balance?.credits || 0
|
||||
user_div.replaceChildren(
|
||||
$el('div.easyuse-account-user-info', {
|
||||
onclick:_=>{
|
||||
new AccountDialog().show(user_div);
|
||||
}
|
||||
},[
|
||||
$el('div.user',[
|
||||
$el('div.avatar', avatar ? [$el('img',{src:avatar})] : '😀'),
|
||||
$el('div.info', [
|
||||
$el('h5.name', email),
|
||||
$el('h6.remark','Credits: '+ credits)
|
||||
])
|
||||
]),
|
||||
$el('div.edit', {textContent:$t('Edit')})
|
||||
])
|
||||
)
|
||||
toast.success($t('Save Succeed'))
|
||||
}
|
||||
else toast.success($t('Save Succeed'))
|
||||
this.close()
|
||||
} else {
|
||||
toast.error($t('Save Failed'))
|
||||
}
|
||||
}}),
|
||||
$el('button.delete',{textContent:$t('Delete'),onclick:e=>{
|
||||
const dataIndex = Array.prototype.indexOf.call(this.dialog_div.querySelectorAll('.easyuse-account-dialog-item'), e.target.parentNode)
|
||||
if(api_keys.length<=1){
|
||||
toast.error($t('At least one account is required'))
|
||||
return
|
||||
}
|
||||
api_keys.splice(dataIndex,1)
|
||||
this.dialog_div.removeChild(e.target.parentNode)
|
||||
}}),
|
||||
])
|
||||
}
|
||||
|
||||
show(userdiv) {
|
||||
api_keys.forEach((item,index)=>{
|
||||
this.lists.push(this.addItem(index,userdiv))
|
||||
})
|
||||
this.dialog_div = $el("div.easyuse-account-dialog", this.lists)
|
||||
super.show(
|
||||
$el('div.easyuse-account-dialog-main',[
|
||||
$el('div',[
|
||||
$el('a',{href:'https://platform.stability.ai/account/keys',target:'_blank',textContent:$t('Getting Your APIKEY')}),
|
||||
]),
|
||||
this.dialog_div,
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
createButtons() {
|
||||
const btns = super.createButtons();
|
||||
btns.unshift($el('button',{
|
||||
type:'button',
|
||||
textContent:$t('Save Account Info'),
|
||||
onclick:_=>{
|
||||
let missing = true
|
||||
for(let i=0;i<api_keys.length;i++){
|
||||
if(!api_keys[i].key) {
|
||||
missing = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if(!missing){
|
||||
toast.error($t('APIKEY is not Empty'))
|
||||
}
|
||||
else {
|
||||
const body = new FormData();
|
||||
body.append('api_keys', JSON.stringify(api_keys));
|
||||
api.fetchApi('/easyuse/stability/set_api_keys', {
|
||||
method: 'POST',
|
||||
body
|
||||
}).then(res => {
|
||||
if (res.status == 200) {
|
||||
toast.success($t('Save Succeed'))
|
||||
|
||||
} else {
|
||||
toast.error($t('Save Failed'))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}))
|
||||
btns.unshift($el('button',{
|
||||
type:'button',
|
||||
textContent:$t('Add Account'),
|
||||
onclick:_=>{
|
||||
const name = 'Account '+(api_keys.length).toString()
|
||||
api_keys.push({name,key:''})
|
||||
const item = this.addItem(api_keys.length - 1)
|
||||
this.lists.push(item)
|
||||
this.dialog_div.appendChild(item)
|
||||
}
|
||||
}))
|
||||
return btns
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'comfy.easyUse.account',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if(nodeData.name == 'easy stableDiffusion3API'){
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function() {
|
||||
onNodeCreated ? onNodeCreated?.apply(this, arguments) : undefined;
|
||||
const seed_widget = this.widgets.find(w => ['seed_num','seed'].includes(w.name))
|
||||
const seed_control = this.widgets.find(w=> ['control_before_generate','control_after_generate'].includes(w.name))
|
||||
let model_widget = this.widgets.find(w => w.name == 'model')
|
||||
model_widget.callback = value =>{
|
||||
cost_widget.value = '-'+api_cost[value]
|
||||
}
|
||||
const cost_widget = this.addWidget('text', 'cost_credit', '0', _=>{
|
||||
},{
|
||||
serialize:false,
|
||||
})
|
||||
cost_widget.disabled = true
|
||||
setTimeout(_=>{
|
||||
if(seed_control.name == 'control_before_generate' && seed_widget.value === 0){
|
||||
seed_widget.value = Math.floor(Math.random() * 4294967294)
|
||||
}
|
||||
cost_widget.value = '-'+api_cost[model_widget.value]
|
||||
},100)
|
||||
let user_div = $el('div.easyuse-account-user', [$t('Loading UserInfo...')])
|
||||
let account = this.addDOMWidget('account',"btn",$el('div.easyuse-account',user_div));
|
||||
// 更新balance信息
|
||||
api.addEventListener('stable-diffusion-api-generate-succeed', async ({detail}) => {
|
||||
let remarkDiv = user_div.querySelectorAll('.remark')
|
||||
if(remarkDiv && remarkDiv[0]){
|
||||
const credits = detail?.model ? api_cost[detail.model] : 0
|
||||
if(credits) {
|
||||
let balance = accSub(parseFloat(remarkDiv[0].innerText.replace(/Credits: /g,'')),credits)
|
||||
if(balance>0){
|
||||
remarkDiv[0].innerText = 'Credits: '+ balance.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
await sleep(10000)
|
||||
const res = await api.fetchApi('/easyuse/stability/balance')
|
||||
if(res.status == 200){
|
||||
const data = await res.json()
|
||||
if(data?.balance){
|
||||
const credits = data.balance?.credits || 0
|
||||
if(remarkDiv && remarkDiv[0]){
|
||||
remarkDiv[0].innerText = 'Credits: ' + credits
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// 获取api_keys
|
||||
const res = await api.fetchApi('/easyuse/stability/api_keys')
|
||||
if (res.status == 200){
|
||||
let data = await res.json()
|
||||
api_keys = data.keys
|
||||
api_current = data.current
|
||||
if (api_keys.length > 0 && api_current!==undefined){
|
||||
const api_key = api_keys[api_current]['key']
|
||||
const api_name = api_keys[api_current]['name']
|
||||
if(!api_key){
|
||||
user_div.replaceChildren(
|
||||
$el('div.easyuse-account-user-info', {
|
||||
onclick:_=>{
|
||||
new AccountDialog().show(user_div);
|
||||
}
|
||||
},[
|
||||
$el('div.user',[
|
||||
$el('div.avatar', '😀'),
|
||||
$el('div.info', [
|
||||
$el('h5.name', api_name),
|
||||
$el('h6.remark',$t('Click to set the APIKEY first'))
|
||||
])
|
||||
]),
|
||||
$el('div.edit', {textContent:$t('Edit')})
|
||||
])
|
||||
)
|
||||
}else{
|
||||
// 获取账号信息
|
||||
const res = await api.fetchApi('/easyuse/stability/user_info')
|
||||
if(res.status == 200){
|
||||
const data = await res.json()
|
||||
if(data?.account && data?.balance){
|
||||
const avatar = data.account?.profile_picture || null
|
||||
const email = data.account?.email || null
|
||||
const credits = data.balance?.credits || 0
|
||||
user_div.replaceChildren(
|
||||
$el('div.easyuse-account-user-info', {
|
||||
onclick:_=>{
|
||||
new AccountDialog().show(user_div);
|
||||
}
|
||||
},[
|
||||
$el('div.user',[
|
||||
$el('div.avatar', avatar ? [$el('img',{src:avatar})] : '😀'),
|
||||
$el('div.info', [
|
||||
$el('h5.name', email),
|
||||
$el('h6.remark','Credits: '+ credits)
|
||||
])
|
||||
]),
|
||||
$el('div.edit', {textContent:$t('Edit')})
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,174 @@
|
||||
import {app} from "../../../../scripts/app.js";
|
||||
import {api} from "../../../../scripts/api.js";
|
||||
import {$el} from "../../../../scripts/ui.js";
|
||||
import {$t} from "../common/i18n.js";
|
||||
import {getExtension, spliceExtension} from '../common/utils.js'
|
||||
import {toast} from "../common/toast.js";
|
||||
|
||||
const setting_id = "Comfy.EasyUse.MenuNestSub"
|
||||
let enableMenuNestSub = false
|
||||
let thumbnails = []
|
||||
|
||||
export function addMenuNestSubSetting(app) {
|
||||
app.ui.settings.addSetting({
|
||||
id: setting_id,
|
||||
name: $t("Enable ContextMenu Auto Nest Subdirectories (ComfyUI-Easy-Use)"),
|
||||
type: "boolean",
|
||||
defaultValue: enableMenuNestSub,
|
||||
onChange(value) {
|
||||
enableMenuNestSub = !!value;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const getEnableMenuNestSub = _ => app.ui.settings.getSettingValue(setting_id, enableMenuNestSub)
|
||||
|
||||
|
||||
const Loaders = ['easy fullLoader','easy a1111Loader','easy comfyLoader']
|
||||
app.registerExtension({
|
||||
name:"comfy.easyUse.contextMenu",
|
||||
async setup(app){
|
||||
addMenuNestSubSetting(app)
|
||||
// 获取所有模型图像
|
||||
const imgRes = await api.fetchApi(`/easyuse/models/thumbnail`)
|
||||
if (imgRes.status === 200) {
|
||||
let data = await imgRes.json();
|
||||
thumbnails = data
|
||||
}
|
||||
else if(getEnableMenuNestSub()){
|
||||
toast.error($t("Too many thumbnails, have closed the display"))
|
||||
}
|
||||
const existingContextMenu = LiteGraph.ContextMenu;
|
||||
LiteGraph.ContextMenu = function(values,options){
|
||||
const threshold = 10;
|
||||
const enabled = getEnableMenuNestSub();
|
||||
if(!enabled || (values?.length || 0) <= threshold || !(options?.callback) || values.some(i => typeof i !== 'string')){
|
||||
if(enabled){
|
||||
// console.log('Skipping context menu auto nesting for incompatible menu.');
|
||||
}
|
||||
return existingContextMenu.apply(this,[...arguments]);
|
||||
}
|
||||
const compatValues = values;
|
||||
const originalValues = [...compatValues];
|
||||
const folders = {};
|
||||
const specialOps = [];
|
||||
const folderless = [];
|
||||
for(const value of compatValues){
|
||||
const splitBy = value.indexOf('/') > -1 ? '/' : '\\';
|
||||
const valueSplit = value.split(splitBy);
|
||||
if(valueSplit.length > 1){
|
||||
const key = valueSplit.shift();
|
||||
folders[key] = folders[key] || [];
|
||||
folders[key].push(valueSplit.join(splitBy));
|
||||
}else if(value === 'CHOOSE' || value.startsWith('DISABLE ')){
|
||||
specialOps.push(value);
|
||||
}else{
|
||||
folderless.push(value);
|
||||
}
|
||||
}
|
||||
const foldersCount = Object.values(folders).length;
|
||||
if(foldersCount > 0){
|
||||
const oldcallback = options.callback;
|
||||
options.callback = null;
|
||||
const newCallback = (item,options) => {
|
||||
if(['None','无','無','なし'].includes(item.content)) oldcallback('None',options)
|
||||
else oldcallback(originalValues.find(i => i.endsWith(item.content),options));
|
||||
};
|
||||
const addContent = (content, folderName='') => {
|
||||
const name = folderName ? folderName + '\\' + spliceExtension(content) : spliceExtension(content);
|
||||
const ext = getExtension(content)
|
||||
const time = new Date().getTime()
|
||||
let thumbnail = ''
|
||||
if(['ckpt', 'pt', 'bin', 'pth', 'safetensors'].includes(ext)){
|
||||
for(let i=0;i<thumbnails.length;i++){
|
||||
let thumb = thumbnails[i]
|
||||
if(name && thumb && thumb.indexOf(name) != -1){
|
||||
thumbnail = thumbnails[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let newContent
|
||||
if(thumbnail){
|
||||
const protocol = window.location.protocol
|
||||
const host = window.location.host
|
||||
const base_url = `${protocol}//${host}`
|
||||
const thumb_url = thumbnail.replace(':','%3A').replace(/\\/g,'/')
|
||||
newContent = $el("div.easyuse-model", {},[$el("span",{},content + ' *'),$el("img",{src:`${base_url}/${thumb_url}?t=${time}`})])
|
||||
}else{
|
||||
newContent = $el("div.easyuse-model", {},[
|
||||
$el("span",{},content)
|
||||
])
|
||||
}
|
||||
|
||||
return {
|
||||
content,
|
||||
title:newContent.outerHTML,
|
||||
callback: newCallback
|
||||
}
|
||||
}
|
||||
const newValues = [];
|
||||
const add_sub_folder = (folder, folderName) => {
|
||||
let subs = []
|
||||
let less = []
|
||||
const b = folder.map(name=> {
|
||||
const _folders = {};
|
||||
const splitBy = name.indexOf('/') > -1 ? '/' : '\\';
|
||||
const valueSplit = name.split(splitBy);
|
||||
if(valueSplit.length > 1){
|
||||
const key = valueSplit.shift();
|
||||
_folders[key] = _folders[key] || [];
|
||||
_folders[key].push(valueSplit.join(splitBy));
|
||||
}
|
||||
const foldersCount = Object.values(folders).length;
|
||||
if(foldersCount > 0){
|
||||
let key = Object.keys(_folders)[0]
|
||||
if(key && _folders[key]) subs.push({key, value:_folders[key][0]})
|
||||
else{
|
||||
less.push(addContent(name,key))
|
||||
}
|
||||
}
|
||||
return addContent(name,folderName)
|
||||
})
|
||||
if(subs.length>0){
|
||||
let subs_obj = {}
|
||||
subs.forEach(item => {
|
||||
subs_obj[item.key] = subs_obj[item.key] || []
|
||||
subs_obj[item.key].push(item.value)
|
||||
})
|
||||
return [...Object.entries(subs_obj).map(f => {
|
||||
return {
|
||||
content: f[0],
|
||||
has_submenu: true,
|
||||
callback: () => {},
|
||||
submenu: {
|
||||
options: add_sub_folder(f[1], f[0]),
|
||||
}
|
||||
}
|
||||
}),...less]
|
||||
}
|
||||
else return b
|
||||
}
|
||||
|
||||
for(const [folderName,folder] of Object.entries(folders)){
|
||||
newValues.push({
|
||||
content:folderName,
|
||||
has_submenu:true,
|
||||
callback:() => {},
|
||||
submenu:{
|
||||
options:add_sub_folder(folder,folderName),
|
||||
}
|
||||
});
|
||||
}
|
||||
newValues.push(...folderless.map(f => addContent(f, '')));
|
||||
if(specialOps.length > 0) newValues.push(...specialOps.map(f => addContent(f, '')));
|
||||
return existingContextMenu.call(this,newValues,options);
|
||||
}
|
||||
return existingContextMenu.apply(this,[...arguments]);
|
||||
}
|
||||
LiteGraph.ContextMenu.prototype = existingContextMenu.prototype;
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,593 @@
|
||||
import {app} from "../../../../scripts/app.js";
|
||||
import {$t} from '../common/i18n.js'
|
||||
import {CheckpointInfoDialog, LoraInfoDialog} from "../common/model.js";
|
||||
|
||||
const loaders = ['easy fullLoader', 'easy a1111Loader', 'easy comfyLoader', 'easy kolorsLoader', 'easy hunyuanDiTLoader', 'easy pixArtLoader']
|
||||
const preSampling = ['easy preSampling', 'easy preSamplingAdvanced', 'easy preSamplingDynamicCFG', 'easy preSamplingNoiseIn', 'easy preSamplingCustom', 'easy preSamplingLayerDiffusion', 'easy fullkSampler']
|
||||
const kSampler = ['easy kSampler', 'easy kSamplerTiled', 'easy kSamplerInpainting', 'easy kSamplerDownscaleUnet', 'easy kSamplerLayerDiffusion']
|
||||
const controlnet = ['easy controlnetLoader', 'easy controlnetLoaderADV', 'easy controlnetLoader++', 'easy instantIDApply', 'easy instantIDApplyADV']
|
||||
const ipadapter = ['easy ipadapterApply', 'easy ipadapterApplyADV', 'easy ipadapterApplyFaceIDKolors', 'easy ipadapterStyleComposition', 'easy ipadapterApplyFromParams', 'easy pulIDApply', 'easy pulIDApplyADV']
|
||||
const positive_prompt = ['easy positive', 'easy wildcards']
|
||||
const imageNode = ['easy loadImageBase64', 'LoadImage', 'LoadImageMask']
|
||||
const inpaint = ['easy applyBrushNet', 'easy applyPowerPaint', 'easy applyInpaint']
|
||||
const widgetMapping = {
|
||||
"positive_prompt":{
|
||||
"text": "positive",
|
||||
"positive": "text"
|
||||
},
|
||||
"loaders":{
|
||||
"ckpt_name": "ckpt_name",
|
||||
"vae_name": "vae_name",
|
||||
"clip_skip": "clip_skip",
|
||||
"lora_name": "lora_name",
|
||||
"resolution": "resolution",
|
||||
"empty_latent_width": "empty_latent_width",
|
||||
"empty_latent_height": "empty_latent_height",
|
||||
"positive": "positive",
|
||||
"negative": "negative",
|
||||
"batch_size": "batch_size",
|
||||
"a1111_prompt_style": "a1111_prompt_style"
|
||||
},
|
||||
"preSampling":{
|
||||
"steps": "steps",
|
||||
"cfg": "cfg",
|
||||
"cfg_scale_min": "cfg",
|
||||
"sampler_name": "sampler_name",
|
||||
"scheduler": "scheduler",
|
||||
"denoise": "denoise",
|
||||
"seed_num": "seed_num",
|
||||
"seed": "seed"
|
||||
},
|
||||
"kSampler":{
|
||||
"image_output": "image_output",
|
||||
"save_prefix": "save_prefix",
|
||||
"link_id": "link_id"
|
||||
},
|
||||
"controlnet":{
|
||||
"control_net_name":"control_net_name",
|
||||
"strength": ["strength", "cn_strength"],
|
||||
"scale_soft_weights": ["scale_soft_weights","cn_soft_weights"],
|
||||
"cn_strength": ["strength", "cn_strength"],
|
||||
"cn_soft_weights": ["scale_soft_weights","cn_soft_weights"],
|
||||
},
|
||||
"ipadapter":{
|
||||
"preset":"preset",
|
||||
"lora_strength": "lora_strength",
|
||||
"provider": "provider",
|
||||
"weight":"weight",
|
||||
"weight_faceidv2": "weight_faceidv2",
|
||||
"start_at": "start_at",
|
||||
"end_at": "end_at",
|
||||
"cache_mode": "cache_mode",
|
||||
"use_tiled": "use_tiled",
|
||||
"insightface": "insightface",
|
||||
"pulid_file": "pulid_file"
|
||||
},
|
||||
"load_image":{
|
||||
"image":"image",
|
||||
"base64_data":"base64_data",
|
||||
"channel": "channel"
|
||||
},
|
||||
"inpaint":{
|
||||
"dtype": "dtype",
|
||||
"fitting": "fitting",
|
||||
"function": "function",
|
||||
"scale": "scale",
|
||||
"start_at": "start_at",
|
||||
"end_at": "end_at"
|
||||
}
|
||||
}
|
||||
const inputMapping = {
|
||||
"loaders":{
|
||||
"optional_lora_stack": "optional_lora_stack",
|
||||
"positive": "positive",
|
||||
"negative": "negative"
|
||||
},
|
||||
"preSampling":{
|
||||
"pipe": "pipe",
|
||||
"image_to_latent": "image_to_latent",
|
||||
"latent": "latent"
|
||||
},
|
||||
"kSampler":{
|
||||
"pipe": "pipe",
|
||||
"model": "model"
|
||||
},
|
||||
"controlnet":{
|
||||
"pipe": "pipe",
|
||||
"image": "image",
|
||||
"image_kps": "image_kps",
|
||||
"control_net": "control_net",
|
||||
"positive": "positive",
|
||||
"negative": "negative",
|
||||
"mask": "mask"
|
||||
},
|
||||
"positive_prompt":{
|
||||
|
||||
},
|
||||
"ipadapter":{
|
||||
"model":"model",
|
||||
"image":"image",
|
||||
"image_style": "image",
|
||||
"attn_mask":"attn_mask",
|
||||
"optional_ipadapter":"optional_ipadapter"
|
||||
},
|
||||
"inpaint":{
|
||||
"pipe": "pipe",
|
||||
"image": "image",
|
||||
"mask": "mask"
|
||||
}
|
||||
};
|
||||
|
||||
const outputMapping = {
|
||||
"loaders":{
|
||||
"pipe": "pipe",
|
||||
"model": "model",
|
||||
"vae": "vae",
|
||||
"clip": null,
|
||||
"positive": null,
|
||||
"negative": null,
|
||||
"latent": null,
|
||||
},
|
||||
"preSampling":{
|
||||
"pipe":"pipe"
|
||||
},
|
||||
"kSampler":{
|
||||
"pipe": "pipe",
|
||||
"image": "image"
|
||||
},
|
||||
"controlnet":{
|
||||
"pipe": "pipe",
|
||||
"positive": "positive",
|
||||
"negative": "negative"
|
||||
},
|
||||
"positive_prompt":{
|
||||
"text": "positive",
|
||||
"positive": "text"
|
||||
},
|
||||
"load_image":{
|
||||
"IMAGE":"IMAGE",
|
||||
"MASK": "MASK"
|
||||
},
|
||||
"ipadapter":{
|
||||
"model":"model",
|
||||
"tiles":"tiles",
|
||||
"masks":"masks",
|
||||
"ipadapter":"ipadapter"
|
||||
},
|
||||
"inpaint":{
|
||||
"pipe": "pipe",
|
||||
}
|
||||
};
|
||||
|
||||
// 替换节点
|
||||
function replaceNode(oldNode, newNodeName, type) {
|
||||
const newNode = LiteGraph.createNode(newNodeName);
|
||||
if (!newNode) {
|
||||
return;
|
||||
}
|
||||
app.graph.add(newNode);
|
||||
|
||||
newNode.pos = oldNode.pos.slice();
|
||||
newNode.size = oldNode.size.slice();
|
||||
|
||||
oldNode.widgets.forEach(widget => {
|
||||
if(widgetMapping[type][widget.name]){
|
||||
const newName = widgetMapping[type][widget.name];
|
||||
if (newName) {
|
||||
const newWidget = findWidgetByName(newNode, newName);
|
||||
if (newWidget) {
|
||||
newWidget.value = widget.value;
|
||||
if(widget.name == 'seed_num'){
|
||||
newWidget.linkedWidgets[0].value = widget.linkedWidgets[0].value
|
||||
}
|
||||
if(widget.type == 'converted-widget'){
|
||||
convertToInput(newNode, newWidget, widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(oldNode.inputs){
|
||||
oldNode.inputs.forEach((input, index) => {
|
||||
if (input && input.link && inputMapping[type][input.name]) {
|
||||
const newInputName = inputMapping[type][input.name];
|
||||
// If the new node does not have this output, skip
|
||||
if (newInputName === null) {
|
||||
return;
|
||||
}
|
||||
const newInputIndex = newNode.findInputSlot(newInputName);
|
||||
if (newInputIndex !== -1) {
|
||||
const originLinkInfo = oldNode.graph.links[input.link];
|
||||
if (originLinkInfo) {
|
||||
const originNode = oldNode.graph.getNodeById(originLinkInfo.origin_id);
|
||||
if (originNode) {
|
||||
originNode.connect(originLinkInfo.origin_slot, newNode, newInputIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(oldNode.outputs){
|
||||
oldNode.outputs.forEach((output, index) => {
|
||||
if (output && output.links && outputMapping[type] && outputMapping[type][output.name]) {
|
||||
const newOutputName = outputMapping[type][output.name];
|
||||
// If the new node does not have this output, skip
|
||||
if (newOutputName === null) {
|
||||
return;
|
||||
}
|
||||
const newOutputIndex = newNode.findOutputSlot(newOutputName);
|
||||
if (newOutputIndex !== -1) {
|
||||
output.links.forEach(link => {
|
||||
const targetLinkInfo = oldNode.graph.links[link];
|
||||
if (targetLinkInfo) {
|
||||
const targetNode = oldNode.graph.getNodeById(targetLinkInfo.target_id);
|
||||
if (targetNode) {
|
||||
newNode.connect(newOutputIndex, targetNode, targetLinkInfo.target_slot);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Remove old node
|
||||
app.graph.remove(oldNode);
|
||||
|
||||
// Remove others
|
||||
if(newNode.type == 'easy fullkSampler'){
|
||||
const link_output_id = newNode.outputs[0].links
|
||||
if(link_output_id && link_output_id[0]){
|
||||
const nodes = app.graph._nodes
|
||||
const node = nodes.find(cate=> cate.inputs && cate.inputs[0] && cate.inputs[0]['link'] == link_output_id[0])
|
||||
if(node){
|
||||
app.graph.remove(node);
|
||||
}
|
||||
}
|
||||
}else if(preSampling.includes(newNode.type)){
|
||||
const link_output_id = newNode.outputs[0].links
|
||||
if(!link_output_id || !link_output_id[0]){
|
||||
const ksampler = LiteGraph.createNode('easy kSampler');
|
||||
app.graph.add(ksampler);
|
||||
ksampler.pos = newNode.pos.slice();
|
||||
ksampler.pos[0] = ksampler.pos[0] + newNode.size[0] + 20;
|
||||
const newInputIndex = newNode.findInputSlot('pipe');
|
||||
if (newInputIndex !== -1) {
|
||||
if (newNode) {
|
||||
newNode.connect(0, ksampler, newInputIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// autoHeight
|
||||
newNode.setSize([newNode.size[0], newNode.computeSize()[1]]);
|
||||
}
|
||||
|
||||
export function findWidgetByName(node, widgetName) {
|
||||
return node.widgets.find(widget => typeof widgetName == 'object' ? widgetName.includes(widget.name) : widget.name === widgetName);
|
||||
}
|
||||
function replaceNodeMenuCallback(currentNode, targetNodeName, type) {
|
||||
return function() {
|
||||
replaceNode(currentNode, targetNodeName, type);
|
||||
};
|
||||
}
|
||||
const addMenuHandler = (nodeType, cb)=> {
|
||||
const getOpts = nodeType.prototype.getExtraMenuOptions;
|
||||
nodeType.prototype.getExtraMenuOptions = function () {
|
||||
const r = getOpts.apply(this, arguments);
|
||||
cb.apply(this, arguments);
|
||||
return r;
|
||||
};
|
||||
}
|
||||
const addMenu = (content, type, nodes_include, nodeType, has_submenu=true) => {
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: content,
|
||||
has_submenu: has_submenu,
|
||||
callback: (value, options, e, menu, node) => showSwapMenu(value, options, e, menu, node, type, nodes_include)
|
||||
})
|
||||
if(type == 'loaders') {
|
||||
options.unshift({
|
||||
content: $t("💎 View Lora Info..."),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
const widget = node.widgets.find(cate => cate.name == 'lora_name')
|
||||
let name = widget.value;
|
||||
if (!name || name == 'None') return
|
||||
new LoraInfoDialog(name).show('loras', name);
|
||||
}
|
||||
})
|
||||
options.unshift({
|
||||
content: $t("💎 View Checkpoint Info..."),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
let name = node.widgets[0].value;
|
||||
if (!name || name == 'None') return
|
||||
new CheckpointInfoDialog(name).show('checkpoints', name);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
const showSwapMenu = (value, options, e, menu, node, type, nodes_include) => {
|
||||
const swapOptions = [];
|
||||
nodes_include.map(cate=>{
|
||||
if (node.type !== cate) {
|
||||
swapOptions.push({
|
||||
content: `${cate}`,
|
||||
callback: replaceNodeMenuCallback(node, cate, type)
|
||||
});
|
||||
}
|
||||
})
|
||||
new LiteGraph.ContextMenu(swapOptions, {
|
||||
event: e,
|
||||
callback: null,
|
||||
parentMenu: menu,
|
||||
node: node
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重载节点
|
||||
const CONVERTED_TYPE = "converted-widget";
|
||||
const GET_CONFIG = Symbol();
|
||||
|
||||
function hideWidget(node, widget, suffix = "") {
|
||||
widget.origType = widget.type;
|
||||
widget.origComputeSize = widget.computeSize;
|
||||
widget.origSerializeValue = widget.serializeValue;
|
||||
widget.computeSize = () => [0, -4]; // -4 is due to the gap litegraph adds between widgets automatically
|
||||
widget.type = CONVERTED_TYPE + suffix;
|
||||
widget.serializeValue = () => {
|
||||
// Prevent serializing the widget if we have no input linked
|
||||
if (!node.inputs) {
|
||||
return undefined;
|
||||
}
|
||||
let node_input = node.inputs.find((i) => i.widget?.name === widget.name);
|
||||
|
||||
if (!node_input || !node_input.link) {
|
||||
return undefined;
|
||||
}
|
||||
return widget.origSerializeValue ? widget.origSerializeValue() : widget.value;
|
||||
};
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
hideWidget(node, w, ":" + widget.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
function convertToInput(node, widget, config) {
|
||||
console.log('config:', config)
|
||||
hideWidget(node, widget);
|
||||
|
||||
const { type } = getWidgetType(config);
|
||||
|
||||
// Add input and store widget config for creating on primitive node
|
||||
const sz = node.size;
|
||||
if(!widget.options || !widget.options.forceInput){
|
||||
node.addInput(widget.name, type, {
|
||||
widget: { name: widget.name, [GET_CONFIG]: () => config },
|
||||
});
|
||||
}
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT;
|
||||
}
|
||||
|
||||
// Restore original size but grow if needed
|
||||
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])]);
|
||||
}
|
||||
|
||||
function getWidgetType(config) {
|
||||
// Special handling for COMBO so we restrict links based on the entries
|
||||
let type = config[0];
|
||||
if (type instanceof Array) {
|
||||
type = "COMBO";
|
||||
}
|
||||
return { type };
|
||||
}
|
||||
|
||||
const reloadNode = function (node) {
|
||||
const nodeType = node.constructor.type;
|
||||
const origVals = node.properties.origVals || {};
|
||||
|
||||
const nodeTitle = origVals.title || node.title;
|
||||
const nodeColor = origVals.color || node.color;
|
||||
const bgColor = origVals.bgcolor || node.bgcolor;
|
||||
const oldNode = node
|
||||
const options = {
|
||||
'size': [...node.size],
|
||||
'color': nodeColor,
|
||||
'bgcolor': bgColor,
|
||||
'pos': [...node.pos]
|
||||
}
|
||||
|
||||
let inputLinks = []
|
||||
let outputLinks = []
|
||||
if(node.inputs){
|
||||
for (const input of node.inputs) {
|
||||
if (input.link) {
|
||||
const input_name = input.name
|
||||
const input_slot = node.findInputSlot(input_name)
|
||||
const input_node = node.getInputNode(input_slot)
|
||||
const input_link = node.getInputLink(input_slot)
|
||||
inputLinks.push([input_link.origin_slot, input_node, input_name])
|
||||
}
|
||||
}
|
||||
}
|
||||
if(node.outputs) {
|
||||
for (const output of node.outputs) {
|
||||
if (output.links) {
|
||||
const output_name = output.name
|
||||
|
||||
for (const linkID of output.links) {
|
||||
const output_link = graph.links[linkID]
|
||||
const output_node = graph._nodes_by_id[output_link.target_id]
|
||||
outputLinks.push([output_name, output_node, output_link.target_slot])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.graph.remove(node)
|
||||
const newNode = app.graph.add(LiteGraph.createNode(nodeType, nodeTitle, options));
|
||||
|
||||
function handleLinks() {
|
||||
// re-convert inputs
|
||||
if(oldNode.widgets) {
|
||||
for (let w of oldNode.widgets) {
|
||||
if (w.type === 'converted-widget') {
|
||||
const WidgetToConvert = newNode.widgets.find((nw) => nw.name === w.name);
|
||||
for (let i of oldNode.inputs) {
|
||||
if (i.name === w.name) {
|
||||
convertToInput(newNode, WidgetToConvert, i.widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// replace input and output links
|
||||
for (let input of inputLinks) {
|
||||
const [output_slot, output_node, input_name] = input;
|
||||
output_node.connect(output_slot, newNode.id, input_name)
|
||||
}
|
||||
for (let output of outputLinks) {
|
||||
const [output_name, input_node, input_slot] = output;
|
||||
newNode.connect(output_name, input_node, input_slot)
|
||||
}
|
||||
}
|
||||
|
||||
// fix widget values
|
||||
let values = oldNode.widgets_values;
|
||||
if (!values && newNode.widgets?.length>0) {
|
||||
newNode.widgets.forEach((newWidget, index) => {
|
||||
const oldWidget = oldNode.widgets[index];
|
||||
if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) {
|
||||
newWidget.value = oldWidget.value;
|
||||
}
|
||||
});
|
||||
handleLinks();
|
||||
return;
|
||||
}
|
||||
let pass = false
|
||||
const isIterateForwards = values?.length <= newNode.widgets?.length;
|
||||
let vi = isIterateForwards ? 0 : values.length - 1;
|
||||
function evalWidgetValues(testValue, newWidg) {
|
||||
if (testValue === true || testValue === false) {
|
||||
if (newWidg.options?.on && newWidg.options?.off) {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
} else if (typeof testValue === "number") {
|
||||
if (newWidg.options?.min <= testValue && testValue <= newWidg.options?.max) {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
} else if (newWidg.options?.values?.includes(testValue)) {
|
||||
return { value: testValue, pass: true };
|
||||
} else if (newWidg.inputEl && typeof testValue === "string") {
|
||||
return { value: testValue, pass: true };
|
||||
}
|
||||
return { value: newWidg.value, pass: false };
|
||||
}
|
||||
const updateValue = (wi) => {
|
||||
const oldWidget = oldNode.widgets[wi];
|
||||
let newWidget = newNode.widgets[wi];
|
||||
if (newWidget.name === oldWidget.name && newWidget.type === oldWidget.type) {
|
||||
while ((isIterateForwards ? vi < values.length : vi >= 0) && !pass) {
|
||||
let { value, pass } = evalWidgetValues(values[vi], newWidget);
|
||||
if (pass && value !== null) {
|
||||
newWidget.value = value;
|
||||
break;
|
||||
}
|
||||
vi += isIterateForwards ? 1 : -1;
|
||||
}
|
||||
vi++
|
||||
if (!isIterateForwards) {
|
||||
vi = values.length - (newNode.widgets?.length - 1 - wi);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (isIterateForwards && newNode.widgets?.length>0) {
|
||||
for (let wi = 0; wi < newNode.widgets.length; wi++) {
|
||||
updateValue(wi);
|
||||
}
|
||||
} else if(newNode.widgets?.length>0){
|
||||
for (let wi = newNode.widgets.length - 1; wi >= 0; wi--) {
|
||||
updateValue(wi);
|
||||
}
|
||||
}
|
||||
handleLinks();
|
||||
};
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easyUse.extraMenu",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
// 刷新节点
|
||||
addMenuHandler(nodeType, function (_, options) {
|
||||
options.unshift({
|
||||
content: $t("🔃 Reload Node"),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
let graphcanvas = LGraphCanvas.active_canvas;
|
||||
if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1) {
|
||||
reloadNode(node);
|
||||
} else {
|
||||
for (let i in graphcanvas.selected_nodes) {
|
||||
reloadNode(graphcanvas.selected_nodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// ckptNames
|
||||
if(nodeData.name == 'easy ckptNames'){
|
||||
options.unshift({
|
||||
content: $t("💎 View Checkpoint Info..."),
|
||||
callback: (value, options, e, menu, node) => {
|
||||
let name = node.widgets[0].value;
|
||||
if (!name || name == 'None') return
|
||||
new CheckpointInfoDialog(name).show('checkpoints', name);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Swap提示词
|
||||
if (positive_prompt.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyPrompt", 'positive_prompt', positive_prompt, nodeType)
|
||||
}
|
||||
// Swap加载器
|
||||
if (loaders.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyLoader", 'loaders', loaders, nodeType)
|
||||
}
|
||||
// Swap预采样器
|
||||
if (preSampling.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyPreSampling", 'preSampling', preSampling, nodeType)
|
||||
}
|
||||
// Swap kSampler
|
||||
if (kSampler.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyKSampler", 'preSampling', kSampler, nodeType)
|
||||
}
|
||||
// Swap ControlNet
|
||||
if (controlnet.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyControlnet", 'controlnet', controlnet, nodeType)
|
||||
}
|
||||
// Swap IPAdapater
|
||||
if (ipadapter.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap EasyAdapater", 'ipadapter', ipadapter, nodeType)
|
||||
}
|
||||
// Swap Image
|
||||
if (imageNode.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap LoadImage", 'load_image', imageNode, nodeType)
|
||||
}
|
||||
// Swap inpaint
|
||||
if (inpaint.includes(nodeData.name)) {
|
||||
addMenu("↪️ Swap InpaintNode", 'inpaint', inpaint, nodeType)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,788 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { $el } from "../../../../scripts/ui.js";
|
||||
import {addPreconnect, addCss} from "../common/utils.js";
|
||||
|
||||
const locale = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
|
||||
|
||||
const customThemeColor = "#236692"
|
||||
const customThemeColorLight = "#3485bb"
|
||||
// 增加Slot颜色
|
||||
const customPipeLineLink = "#7737AA"
|
||||
const customPipeLineSDXLLink = "#7737AA"
|
||||
const customIntLink = "#29699C"
|
||||
const customXYPlotLink = "#74DA5D"
|
||||
const customLoraStackLink = "#94dccd"
|
||||
const customXYLink = "#38291f"
|
||||
|
||||
var customLinkColors = JSON.parse(localStorage.getItem('Comfy.Settings.ttN.customLinkColors')) || {};
|
||||
if (!customLinkColors["PIPE_LINE"] || !LGraphCanvas.link_type_colors["PIPE_LINE"]) {customLinkColors["PIPE_LINE"] = customPipeLineLink;}
|
||||
if (!customLinkColors["PIPE_LINE_SDXL"] || !LGraphCanvas.link_type_colors["PIPE_LINE_SDXL"]) {customLinkColors["PIPE_LINE_SDXL"] = customPipeLineSDXLLink;}
|
||||
if (!customLinkColors["INT"] || !LGraphCanvas.link_type_colors["INT"]) {customLinkColors["INT"] = customIntLink;}
|
||||
if (!customLinkColors["XYPLOT"] || !LGraphCanvas.link_type_colors["XYPLOT"]) {customLinkColors["XYPLOT"] = customXYPlotLink;}
|
||||
if (!customLinkColors["X_Y"] || !LGraphCanvas.link_type_colors["X_Y"]) {customLinkColors["X_Y"] = customXYLink;}
|
||||
if (!customLinkColors["LORA_STACK"] || !LGraphCanvas.link_type_colors["LORA_STACK"]) {customLinkColors["LORA_STACK"] = customLoraStackLink;}
|
||||
if (!customLinkColors["CONTROL_NET_STACK"] || !LGraphCanvas.link_type_colors["CONTROL_NET_STACK"]) {customLinkColors["CONTROL_NET_STACK"] = customLoraStackLink;}
|
||||
|
||||
localStorage.setItem('Comfy.Settings.easyUse.customLinkColors', JSON.stringify(customLinkColors));
|
||||
|
||||
// 增加自定义主题
|
||||
const ui = {
|
||||
"version": 102,
|
||||
"id": "obsidian",
|
||||
"name": "Obsidian",
|
||||
"colors": {
|
||||
"node_slot": {
|
||||
"CLIP": "#FFD500",
|
||||
"CLIP_VISION": "#A8DADC",
|
||||
"CLIP_VISION_OUTPUT": "#ad7452",
|
||||
"CONDITIONING": "#FFA931",
|
||||
"CONTROL_NET": "#6EE7B7",
|
||||
"IMAGE": "#64B5F6",
|
||||
"LATENT": "#FF9CF9",
|
||||
"MASK": "#81C784",
|
||||
"MODEL": "#B39DDB",
|
||||
"STYLE_MODEL": "#C2FFAE",
|
||||
"VAE": "#FF6E6E",
|
||||
"TAESD": "#DCC274",
|
||||
"PIPE_LINE": customPipeLineLink,
|
||||
"PIPE_LINE_SDXL": customPipeLineSDXLLink,
|
||||
"INT": customIntLink,
|
||||
"XYPLOT": customXYPlotLink,
|
||||
"X_Y": customXYLink
|
||||
},
|
||||
"litegraph_base": {
|
||||
"BACKGROUND_IMAGE": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",
|
||||
"CLEAR_BACKGROUND_COLOR": "#222222",
|
||||
"NODE_TITLE_COLOR": "rgba(255,255,255,.75)",
|
||||
"NODE_SELECTED_TITLE_COLOR": "#FFF",
|
||||
"NODE_TEXT_SIZE": 14,
|
||||
"NODE_TEXT_COLOR": "#b8b8b8",
|
||||
"NODE_SUBTEXT_SIZE": 12,
|
||||
"NODE_DEFAULT_COLOR": "rgba(0,0,0,.8)",
|
||||
"NODE_DEFAULT_BGCOLOR": "rgba(22,22,22,.8)",
|
||||
"NODE_DEFAULT_BOXCOLOR": "rgba(255,255,255,.75)",
|
||||
"NODE_DEFAULT_SHAPE": "box",
|
||||
"NODE_BOX_OUTLINE_COLOR": customThemeColor,
|
||||
"DEFAULT_SHADOW_COLOR": "rgba(0,0,0,0)",
|
||||
"DEFAULT_GROUP_FONT": 24,
|
||||
|
||||
"WIDGET_BGCOLOR": "#242424",
|
||||
"WIDGET_OUTLINE_COLOR": "#333",
|
||||
"WIDGET_TEXT_COLOR": "#a3a3a8",
|
||||
"WIDGET_SECONDARY_TEXT_COLOR": "#97979c",
|
||||
|
||||
"LINK_COLOR": "#9A9",
|
||||
"EVENT_LINK_COLOR": "#A86",
|
||||
"CONNECTING_LINK_COLOR": "#AFA"
|
||||
},
|
||||
"comfy_base": {
|
||||
"fg-color": "#fff",
|
||||
"bg-color": "#242424",
|
||||
"comfy-menu-bg": "rgba(24,24,24,.9)",
|
||||
"comfy-input-bg": "#262626",
|
||||
"input-text": "#ddd",
|
||||
"descrip-text": "#999",
|
||||
"drag-text": "#ccc",
|
||||
"error-text": "#ff4444",
|
||||
"border-color": "#29292c",
|
||||
"tr-even-bg-color": "rgba(28,28,28,.9)",
|
||||
"tr-odd-bg-color": "rgba(19,19,19,.9)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let custom_theme = null
|
||||
let control_mode = null
|
||||
try{
|
||||
custom_theme = localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes') ? JSON.parse(localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes')) : {};
|
||||
}
|
||||
catch (e) {custom_theme = {}}
|
||||
try{
|
||||
const dark_bg = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAACXBIWXMAAAsTAAALEwEAmpwYAAAGlmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgOS4xLWMwMDEgNzkuMTQ2Mjg5OSwgMjAyMy8wNi8yNS0yMDowMTo1NSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiB4bXA6Q3JlYXRlRGF0ZT0iMjAyMy0xMS0xM1QwMDoxODowMiswMTowMCIgeG1wOk1vZGlmeURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHhtcDpNZXRhZGF0YURhdGU9IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOmIyYzRhNjA5LWJmYTctYTg0MC1iOGFlLTk3MzE2ZjM1ZGIyNyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjk0ZmNlZGU4LTE1MTctZmQ0MC04ZGU3LWYzOTgxM2E3ODk5ZiIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjIzMWIxMGIwLWI0ZmItMDI0ZS1iMTJlLTMwNTMwM2NkMDdjOCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249ImNyZWF0ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MjMxYjEwYjAtYjRmYi0wMjRlLWIxMmUtMzA1MzAzY2QwN2M4IiBzdEV2dDp3aGVuPSIyMDIzLTExLTEzVDAwOjE4OjAyKzAxOjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjQ4OWY1NzlmLTJkNjUtZWQ0Zi04OTg0LTA4NGE2MGE1ZTMzNSIgc3RFdnQ6d2hlbj0iMjAyMy0xMS0xNVQwMjowNDo1OSswMTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDI1LjEgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMmM0YTYwOS1iZmE3LWE4NDAtYjhhZS05NzMxNmYzNWRiMjciIHN0RXZ0OndoZW49IjIwMjMtMTEtMTVUMDI6MDQ6NTkrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyNS4xIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4OTe6GAAAAx0lEQVR42u3WMQoAIQxFwRzJys77X8vSLiRgITif7bYbgrwYc/mKXyBoY4VVBgsWLFiwYFmOlTv+9jfDOjHmr8u6eVkGCxYsWLBgmc5S8ApewXvgYRksWLBgKXidpeBdloL3wMOCBctgwVLwCl7BuyyDBQsWLFiwTGcpeAWv4D3wsAwWLFiwFLzOUvAuS8F74GHBgmWwYCl4Ba/gXZbBggULFixYprMUvIJX8B54WAYLFixYCl5nKXiXpeA98LBgwTJYsGC9tg1o8f4TTtqzNQAAAABJRU5ErkJggg=="
|
||||
// 修改自定义主题
|
||||
if(!custom_theme || !custom_theme.obsidian || !custom_theme.obsidian.version || custom_theme.obsidian.version<ui.version){
|
||||
custom_theme.obsidian = ui
|
||||
let ui2 = JSON.parse(JSON.stringify(ui))
|
||||
ui2.id = 'obsidian_dark'
|
||||
ui2.name = 'Obsidian Dark'
|
||||
ui2.colors.litegraph_base.BACKGROUND_IMAGE = dark_bg
|
||||
ui2.colors.litegraph_base.CLEAR_BACKGROUND_COLOR = '#000'
|
||||
custom_theme[ui2.id] = ui2
|
||||
localStorage.setItem('Comfy.Settings.Comfy.CustomColorPalettes', JSON.stringify(custom_theme));
|
||||
}
|
||||
let theme_name = localStorage.getItem('Comfy.Settings.Comfy.ColorPalette')
|
||||
control_mode = localStorage.getItem('Comfy.Settings.Comfy.WidgetControlMode')
|
||||
// if(control_mode) {
|
||||
// control_mode = JSON.parse(control_mode)
|
||||
// if(control_mode == 'before'){
|
||||
// localStorage['Comfy.Settings.AE.mouseover'] = false
|
||||
// localStorage['Comfy.Settings.AE.highlight'] = false
|
||||
// }
|
||||
// }
|
||||
// 兼容 ComfyUI Revision: 1887 [235727fe] 以上版本
|
||||
if(api.storeSettings){
|
||||
const _settings = await api.getSettings()
|
||||
let settings = null
|
||||
// 运行操作设置
|
||||
if(!control_mode && _settings['Comfy.WidgetControlMode']) {
|
||||
control_mode = _settings['Comfy.WidgetControlMode']
|
||||
}else if(!control_mode) control_mode = 'after'
|
||||
// 主题设置
|
||||
if(!theme_name && _settings['Comfy.ColorPalette']) {
|
||||
theme_name = `"${_settings['Comfy.ColorPalette']}"`
|
||||
localStorage.setItem('Comfy.Settings.Comfy.ColorPalette', theme_name)
|
||||
}
|
||||
if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)) {
|
||||
if(!settings) settings = {}
|
||||
settings["Comfy.ColorPalette"] = JSON.parse(theme_name)
|
||||
}
|
||||
if(!_settings || !_settings["Comfy.CustomColorPalettes"] || !_settings["Comfy.CustomColorPalettes"]["obsidian"] || _settings["Comfy.CustomColorPalettes"]["obsidian"]['version']<ui.version){
|
||||
if(!settings) settings = {}
|
||||
settings["Comfy.CustomColorPalettes"] = localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes') ? JSON.parse(localStorage.getItem('Comfy.Settings.Comfy.CustomColorPalettes')) : {}
|
||||
await api.storeSettings(settings);
|
||||
app.ui.settings.load()
|
||||
}else if(settings){
|
||||
await api.storeSettings(settings);
|
||||
}
|
||||
}
|
||||
// 判断主题为黑曜石时改变扩展UI
|
||||
if(['"custom_obsidian"','"custom_obsidian_dark"'].includes(theme_name)){
|
||||
// 字体文件
|
||||
addPreconnect("https://fonts.googleapis.com", true)
|
||||
addCss("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700&family=JetBrains+Mono&display=swap", false)
|
||||
// 添加easy的主题样式
|
||||
addCss('css/easy.css')
|
||||
// canvas
|
||||
const bgcolor = LGraphCanvas.node_colors.bgcolor;
|
||||
LGraphCanvas.node_colors = {
|
||||
red: { color: "#af3535", bgcolor, groupcolor: "#A88" },
|
||||
brown: { color: "#38291f", bgcolor, groupcolor: "#b06634" },
|
||||
green: { color: "#346434", bgcolor, groupcolor: "#8A8" },
|
||||
blue: { color: "#1f1f48", bgcolor, groupcolor: "#88A" },
|
||||
pale_blue: {color: "#006691", bgcolor, groupcolor: "#3f789e"},
|
||||
cyan: { color: "#008181", bgcolor, groupcolor: "#8AA" },
|
||||
purple: { color: "#422342", bgcolor, groupcolor: "#a1309b" },
|
||||
yellow: { color: "#c09430", bgcolor, groupcolor: "#b58b2a" },
|
||||
black: { color: "rgba(0,0,0,.8)", bgcolor, groupcolor: "#444" }
|
||||
};
|
||||
LiteGraph.NODE_TEXT_SIZE = 13
|
||||
LiteGraph.DEFAULT_BACKGROUND_IMAGE = ui.colors.litegraph_base.BACKGROUND_IMAGE
|
||||
LGraphCanvas.prototype.drawNodeShape = function(
|
||||
node,
|
||||
ctx,
|
||||
size,
|
||||
fgcolor,
|
||||
bgcolor,
|
||||
selected,
|
||||
mouse_over
|
||||
) {
|
||||
//bg rect
|
||||
ctx.strokeStyle = fgcolor;
|
||||
ctx.fillStyle = bgcolor;
|
||||
|
||||
var title_height = LiteGraph.NODE_TITLE_HEIGHT;
|
||||
var low_quality = this.ds.scale < 0.5;
|
||||
|
||||
//render node area depending on shape
|
||||
var shape =
|
||||
node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE;
|
||||
|
||||
var title_mode = node.constructor.title_mode;
|
||||
|
||||
var render_title = true;
|
||||
if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) {
|
||||
render_title = false;
|
||||
} else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) {
|
||||
render_title = true;
|
||||
}
|
||||
|
||||
var area = new Float32Array(4);
|
||||
area[0] = 0; //x
|
||||
area[1] = render_title ? -title_height : 0; //y
|
||||
area[2] = size[0] + 1; //w
|
||||
area[3] = render_title ? size[1] + title_height : size[1]; //h
|
||||
|
||||
var old_alpha = ctx.globalAlpha;
|
||||
|
||||
//full node shape
|
||||
// if(node.flags.collapsed)
|
||||
{
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
if (shape == LiteGraph.BOX_SHAPE || low_quality) {
|
||||
ctx.fillRect(area[0], area[1], area[2], area[3]);
|
||||
} else if (
|
||||
shape == LiteGraph.ROUND_SHAPE ||
|
||||
shape == LiteGraph.CARD_SHAPE
|
||||
) {
|
||||
ctx.roundRect(
|
||||
area[0],
|
||||
area[1],
|
||||
area[2],
|
||||
area[3],
|
||||
shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius]
|
||||
);
|
||||
} else if (shape == LiteGraph.CIRCLE_SHAPE) {
|
||||
ctx.arc(
|
||||
size[0] * 0.5,
|
||||
size[1] * 0.5,
|
||||
size[0] * 0.5,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
}
|
||||
ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = fgcolor;
|
||||
ctx.fill();
|
||||
|
||||
//separator
|
||||
if(!node.flags.collapsed && render_title)
|
||||
{
|
||||
ctx.shadowColor = "transparent";
|
||||
ctx.fillStyle = "rgba(0,0,0,0.2)";
|
||||
ctx.fillRect(0, -1, area[2], 2);
|
||||
}
|
||||
}
|
||||
ctx.shadowColor = "transparent";
|
||||
|
||||
if (node.onDrawBackground) {
|
||||
node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );
|
||||
}
|
||||
|
||||
//title bg (remember, it is rendered ABOVE the node)
|
||||
if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) {
|
||||
//title bar
|
||||
if (node.onDrawTitleBar) {
|
||||
node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor );
|
||||
} else if (
|
||||
title_mode != LiteGraph.TRANSPARENT_TITLE &&
|
||||
(node.constructor.title_color || this.render_title_colored)
|
||||
) {
|
||||
var title_color = node.constructor.title_color || fgcolor;
|
||||
|
||||
if (node.flags.collapsed) {
|
||||
ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR;
|
||||
}
|
||||
|
||||
//* gradient test
|
||||
if (this.use_gradients) {
|
||||
var grad = LGraphCanvas.gradients[title_color];
|
||||
if (!grad) {
|
||||
grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0);
|
||||
grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException
|
||||
grad.addColorStop(1, "#000");
|
||||
}
|
||||
ctx.fillStyle = grad;
|
||||
} else {
|
||||
ctx.fillStyle = title_color;
|
||||
}
|
||||
|
||||
//ctx.globalAlpha = 0.5 * old_alpha;
|
||||
ctx.beginPath();
|
||||
if (shape == LiteGraph.BOX_SHAPE || low_quality) {
|
||||
ctx.rect(0, -title_height, size[0] + 1, title_height);
|
||||
} else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) {
|
||||
ctx.roundRect(
|
||||
0,
|
||||
-title_height,
|
||||
size[0] + 1,
|
||||
title_height,
|
||||
node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0]
|
||||
);
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.shadowColor = "transparent";
|
||||
}
|
||||
|
||||
var colState = false;
|
||||
if (LiteGraph.node_box_coloured_by_mode){
|
||||
if(LiteGraph.NODE_MODES_COLORS[node.mode]){
|
||||
colState = LiteGraph.NODE_MODES_COLORS[node.mode];
|
||||
}
|
||||
}
|
||||
if (LiteGraph.node_box_coloured_when_on){
|
||||
colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState);
|
||||
}
|
||||
|
||||
//title box
|
||||
var box_size = 10;
|
||||
if (node.onDrawTitleBox) {
|
||||
node.onDrawTitleBox(ctx, title_height, size, this.ds.scale);
|
||||
} else if (
|
||||
shape == LiteGraph.ROUND_SHAPE ||
|
||||
shape == LiteGraph.CIRCLE_SHAPE ||
|
||||
shape == LiteGraph.CARD_SHAPE
|
||||
) {
|
||||
if (low_quality) {
|
||||
ctx.fillStyle = "black";
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
title_height * 0.5,
|
||||
title_height * -0.5,
|
||||
box_size * 0.5 + 1,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// BOX_TITLE_ICON
|
||||
ctx.fillStyle = selected ? LiteGraph.NODE_SELECTED_TITLE_COLOR : (node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR);
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(10,0-box_size-1,box_size * 1.15,box_size * 0.15);
|
||||
ctx.fillRect(10,0-box_size*1.5-1,box_size * 1.15,box_size * 0.15);
|
||||
ctx.fillRect(10,0-box_size*2-1,box_size * 1.15,box_size * 0.15);
|
||||
} else {
|
||||
if (low_quality) {
|
||||
ctx.fillStyle = "black";
|
||||
ctx.fillRect(
|
||||
(title_height - box_size) * 0.5 - 1,
|
||||
(title_height + box_size) * -0.5 - 1,
|
||||
box_size + 2,
|
||||
box_size + 2
|
||||
);
|
||||
}
|
||||
ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR;
|
||||
ctx.fillRect(
|
||||
(title_height - box_size) * 0.5,
|
||||
(title_height + box_size) * -0.5,
|
||||
box_size,
|
||||
box_size
|
||||
);
|
||||
}
|
||||
ctx.globalAlpha = old_alpha;
|
||||
|
||||
//title text
|
||||
if (node.onDrawTitleText) {
|
||||
node.onDrawTitleText(
|
||||
ctx,
|
||||
title_height,
|
||||
size,
|
||||
this.ds.scale,
|
||||
this.title_text_font,
|
||||
selected
|
||||
);
|
||||
}
|
||||
if (!low_quality) {
|
||||
ctx.font = this.title_text_font;
|
||||
var title = String(node.getTitle());
|
||||
if (title) {
|
||||
if (selected) {
|
||||
ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR;
|
||||
} else {
|
||||
ctx.fillStyle =
|
||||
node.constructor.title_text_color ||
|
||||
this.node_title_color;
|
||||
}
|
||||
if (node.flags.collapsed) {
|
||||
ctx.textAlign = "left";
|
||||
var measure = ctx.measureText(title);
|
||||
ctx.fillText(
|
||||
title.substr(0,20), //avoid urls too long
|
||||
title_height,// + measure.width * 0.5,
|
||||
LiteGraph.NODE_TITLE_TEXT_Y - title_height
|
||||
);
|
||||
ctx.textAlign = "left";
|
||||
} else {
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(
|
||||
title,
|
||||
title_height,
|
||||
LiteGraph.NODE_TITLE_TEXT_Y - title_height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//subgraph box
|
||||
if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {
|
||||
var w = LiteGraph.NODE_TITLE_HEIGHT;
|
||||
var x = node.size[0] - w;
|
||||
var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );
|
||||
ctx.fillStyle = over ? "#888" : "#555";
|
||||
if( shape == LiteGraph.BOX_SHAPE || low_quality)
|
||||
ctx.fillRect(x+2, -w+2, w-4, w-4);
|
||||
else
|
||||
{
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(x+2, -w+2, w-4, w-4,[4]);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + w * 0.2, -w * 0.6);
|
||||
ctx.lineTo(x + w * 0.8, -w * 0.6);
|
||||
ctx.lineTo(x + w * 0.5, -w * 0.3);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
//custom title render
|
||||
if (node.onDrawTitle) {
|
||||
node.onDrawTitle(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
//render selection marker
|
||||
if (selected) {
|
||||
if (node.onBounding) {
|
||||
node.onBounding(area);
|
||||
}
|
||||
|
||||
if (title_mode == LiteGraph.TRANSPARENT_TITLE) {
|
||||
area[1] -= title_height;
|
||||
area[3] += title_height;
|
||||
}
|
||||
ctx.lineWidth = 2;
|
||||
ctx.globalAlpha = 0.8;
|
||||
ctx.beginPath();
|
||||
// var out_a = -6,out_b = 12,scale = 2
|
||||
var out_a = 0, out_b = 0, scale = 1
|
||||
if (shape == LiteGraph.BOX_SHAPE) {
|
||||
ctx.rect(
|
||||
out_a + area[0],
|
||||
out_a + area[1],
|
||||
out_b + area[2],
|
||||
out_b + area[3]
|
||||
);
|
||||
} else if (
|
||||
shape == LiteGraph.ROUND_SHAPE ||
|
||||
(shape == LiteGraph.CARD_SHAPE && node.flags.collapsed)
|
||||
) {
|
||||
ctx.roundRect(
|
||||
out_a + area[0],
|
||||
out_a + area[1],
|
||||
out_b + area[2],
|
||||
out_b + area[3],
|
||||
[this.round_radius * scale]
|
||||
);
|
||||
} else if (shape == LiteGraph.CARD_SHAPE) {
|
||||
ctx.roundRect(
|
||||
out_a + area[0],
|
||||
out_a + area[1],
|
||||
out_b + area[2],
|
||||
out_b + area[3],
|
||||
[this.round_radius * scale,scale,this.round_radius * scale,scale]
|
||||
);
|
||||
} else if (shape == LiteGraph.CIRCLE_SHAPE) {
|
||||
ctx.arc(
|
||||
size[0] * 0.5,
|
||||
size[1] * 0.5,
|
||||
size[0] * 0.5 + 6,
|
||||
0,
|
||||
Math.PI * 2
|
||||
);
|
||||
}
|
||||
ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR;
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = fgcolor;
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
|
||||
// these counter helps in conditioning drawing based on if the node has been executed or an action occurred
|
||||
if (node.execute_triggered>0) node.execute_triggered--;
|
||||
if (node.action_triggered>0) node.action_triggered--;
|
||||
};
|
||||
LGraphCanvas.prototype.drawNodeWidgets = function(
|
||||
node,
|
||||
posY,
|
||||
ctx,
|
||||
active_widget
|
||||
) {
|
||||
if (!node.widgets || !node.widgets.length) {
|
||||
return 0;
|
||||
}
|
||||
var width = node.size[0];
|
||||
var widgets = node.widgets;
|
||||
posY += 2;
|
||||
var H = LiteGraph.NODE_WIDGET_HEIGHT;
|
||||
var show_text = this.ds.scale > 0.5;
|
||||
ctx.save();
|
||||
ctx.globalAlpha = this.editor_alpha;
|
||||
var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR;
|
||||
var background_color = LiteGraph.WIDGET_BGCOLOR;
|
||||
var text_color = LiteGraph.WIDGET_TEXT_COLOR;
|
||||
var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
|
||||
var margin = 12;
|
||||
|
||||
for (var i = 0; i < widgets.length; ++i) {
|
||||
var w = widgets[i];
|
||||
var y = posY;
|
||||
if (w.y) {
|
||||
y = w.y;
|
||||
}
|
||||
w.last_y = y;
|
||||
ctx.strokeStyle = outline_color;
|
||||
ctx.fillStyle = "#222";
|
||||
ctx.textAlign = "left";
|
||||
ctx.lineWidth = 1;
|
||||
if(w.disabled)
|
||||
ctx.globalAlpha *= 0.5;
|
||||
var widget_width = w.width || width;
|
||||
|
||||
switch (w.type) {
|
||||
case "button":
|
||||
ctx.font = "10px Inter"
|
||||
ctx.fillStyle = background_color;
|
||||
if (w.clicked) {
|
||||
ctx.fillStyle = "#AAA";
|
||||
w.clicked = false;
|
||||
this.dirty_canvas = true;
|
||||
}
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.25]);
|
||||
ctx.fill();
|
||||
if(show_text && !w.disabled)
|
||||
ctx.stroke();
|
||||
if (show_text) {
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillStyle = text_color;
|
||||
ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7);
|
||||
}
|
||||
break;
|
||||
case "toggle":
|
||||
ctx.font = "10px Inter"
|
||||
ctx.textAlign = "left";
|
||||
ctx.strokeStyle = outline_color;
|
||||
ctx.fillStyle = background_color;
|
||||
ctx.beginPath();
|
||||
if (show_text)
|
||||
ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.25]);
|
||||
else
|
||||
ctx.rect(margin, y, widget_width - margin * 2, H );
|
||||
ctx.fill();
|
||||
if(show_text && !w.disabled)
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = w.value ? customThemeColor : "#333";
|
||||
ctx.beginPath();
|
||||
ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.25, 0, Math.PI * 2 );
|
||||
ctx.fill();
|
||||
if (show_text) {
|
||||
ctx.fillStyle = secondary_text_color;
|
||||
const label = w.label || w.name;
|
||||
if (label != null) {
|
||||
ctx.fillText(label, margin * 1.6, y + H * 0.7);
|
||||
}
|
||||
ctx.font = "10px Inter"
|
||||
ctx.fillStyle = w.value ? text_color : secondary_text_color;
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(
|
||||
w.value
|
||||
? w.options.on || "true"
|
||||
: w.options.off || "false",
|
||||
widget_width - 35,
|
||||
y + H * 0.7
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "slider":
|
||||
ctx.font = "10px Inter"
|
||||
ctx.fillStyle = background_color;
|
||||
ctx.strokeStyle = outline_color;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(margin, y, widget_width - margin * 2, H, [H*0.25]);
|
||||
ctx.fill();
|
||||
ctx.stroke()
|
||||
var range = w.options.max - w.options.min;
|
||||
var nvalue = (w.value - w.options.min) / range;
|
||||
if(nvalue < 0.0) nvalue = 0.0;
|
||||
if(nvalue > 1.0) nvalue = 1.0;
|
||||
ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#333" : customThemeColor);
|
||||
ctx.beginPath();
|
||||
ctx.roundRect(margin, y, nvalue * (widget_width - margin * 2), H, [H*0.25]);
|
||||
ctx.fill();
|
||||
if (w.marker) {
|
||||
var marker_nvalue = (w.marker - w.options.min) / range;
|
||||
if(marker_nvalue < 0.0) marker_nvalue = 0.0;
|
||||
if(marker_nvalue > 1.0) marker_nvalue = 1.0;
|
||||
ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9";
|
||||
ctx.roundRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H , [H * 0.25] );
|
||||
}
|
||||
if (show_text) {
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillStyle = text_color;
|
||||
var text = (w.label || w.name) + ": " + (Number(w.value).toFixed(w.options.precision != null ? w.options.precision : 3)).toString()
|
||||
ctx.fillText(
|
||||
text,
|
||||
widget_width * 0.5,
|
||||
y + H * 0.7
|
||||
);
|
||||
|
||||
}
|
||||
break;
|
||||
case "number":
|
||||
case "combo":
|
||||
ctx.textAlign = "left";
|
||||
ctx.strokeStyle = outline_color;
|
||||
ctx.fillStyle = background_color;
|
||||
ctx.beginPath();
|
||||
if(show_text)
|
||||
ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.25] );
|
||||
else
|
||||
ctx.rect(margin, y, widget_width - margin * 2, H );
|
||||
ctx.fill();
|
||||
if (show_text) {
|
||||
if(!w.disabled)
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = text_color;
|
||||
if(!w.disabled)
|
||||
{
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(margin + 12, y + 6.5);
|
||||
ctx.lineTo(margin + 6, y + H * 0.5);
|
||||
ctx.lineTo(margin + 12, y + H - 6.5);
|
||||
ctx.fill();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(widget_width - margin - 12, y + 6.5);
|
||||
ctx.lineTo(widget_width - margin - 6, y + H * 0.5);
|
||||
ctx.lineTo(widget_width - margin - 12, y + H - 6.5);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.fillStyle = secondary_text_color;
|
||||
ctx.font = "10px Inter"
|
||||
ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7);
|
||||
ctx.fillStyle = text_color;
|
||||
ctx.textAlign = "right";
|
||||
var rightDistance = 6
|
||||
if (w.type == "number") {
|
||||
ctx.font = "10px Inter,JetBrains Mono,monospace"
|
||||
ctx.fillText(
|
||||
Number(w.value).toFixed(
|
||||
w.options.precision !== undefined
|
||||
? w.options.precision
|
||||
: 3
|
||||
),
|
||||
widget_width - margin * 2 - rightDistance,
|
||||
y + H * 0.7
|
||||
);
|
||||
} else {
|
||||
var v = w.value;
|
||||
if( w.options.values )
|
||||
{
|
||||
var values = w.options.values;
|
||||
if( values.constructor === Function )
|
||||
values = values();
|
||||
if(values && values.constructor !== Array)
|
||||
v = values[ w.value ];
|
||||
}
|
||||
ctx.fillText(
|
||||
v,
|
||||
widget_width - margin * 2 - rightDistance,
|
||||
y + H * 0.7
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
case "text":
|
||||
ctx.textAlign = "left";
|
||||
ctx.strokeStyle = outline_color;
|
||||
ctx.fillStyle = background_color;
|
||||
ctx.beginPath();
|
||||
if (show_text)
|
||||
ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.25]);
|
||||
else
|
||||
ctx.rect( margin, y, widget_width - margin * 2, H );
|
||||
ctx.fill();
|
||||
if (show_text) {
|
||||
if(!w.disabled)
|
||||
ctx.stroke();
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.rect(margin, y, widget_width - margin * 2, H);
|
||||
ctx.clip();
|
||||
|
||||
//ctx.stroke();
|
||||
ctx.fillStyle = secondary_text_color;
|
||||
const label = w.label || w.name;
|
||||
ctx.font = "10px Inter"
|
||||
if (label != null) {
|
||||
ctx.fillText(label, margin * 2, y + H * 0.7);
|
||||
}
|
||||
ctx.fillStyle = text_color;
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max
|
||||
ctx.restore();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (w.draw) {
|
||||
w.draw(ctx, node, widget_width, y, H);
|
||||
}
|
||||
break;
|
||||
}
|
||||
posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4;
|
||||
ctx.globalAlpha = this.editor_alpha;
|
||||
|
||||
}
|
||||
ctx.restore();
|
||||
ctx.textAlign = "left";
|
||||
};
|
||||
}
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
function updateControlWidgetLabel(widget, controlValueRunBefore=false) {
|
||||
let replacement = "after";
|
||||
let find = "before";
|
||||
if (controlValueRunBefore) {
|
||||
[find, replacement] = [replacement, find]
|
||||
}
|
||||
widget.label = (widget.label ?? widget.name).replace(find, replacement);
|
||||
widget.name = widget.label;
|
||||
}
|
||||
|
||||
// 节点颜色
|
||||
const COLOR_THEMES = LGraphCanvas.node_colors
|
||||
const NODE_COLORS = {
|
||||
"easy positive":"green",
|
||||
"easy negative":"red",
|
||||
"easy promptList":"cyan",
|
||||
"easy promptLine":"cyan",
|
||||
"easy promptConcat":"cyan",
|
||||
"easy promptReplace":"cyan",
|
||||
"easy XYInputs: Seeds++ Batch": customXYLink,
|
||||
"easy XYInputs: ModelMergeBlocks": customXYLink,
|
||||
'easy textSwitch': "pale_blue"
|
||||
}
|
||||
|
||||
function setNodeColors(node, theme) {
|
||||
if (!theme) {return;}
|
||||
if(theme.color) node.color = theme.color;
|
||||
if(theme.bgcolor) node.bgcolor = theme.bgcolor;
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easyUse.interface",
|
||||
setup() {
|
||||
Object.assign(app.canvas.default_connection_color_byType, customLinkColors);
|
||||
Object.assign(LGraphCanvas.link_type_colors, customLinkColors);
|
||||
},
|
||||
|
||||
async nodeCreated(node) {
|
||||
if (NODE_COLORS.hasOwnProperty(node.comfyClass)) {
|
||||
const colorKey = NODE_COLORS[node.comfyClass]
|
||||
const theme = COLOR_THEMES[colorKey];
|
||||
setNodeColors(node, theme);
|
||||
}
|
||||
// 修复官方bug: 应该初始化修改节点的control_mode name
|
||||
if(control_mode && control_mode == 'before'){
|
||||
const controlValueRunBefore = control_mode == 'before'
|
||||
if(node.widgets && node.widgets.length>0) {
|
||||
for (const w of node.widgets) {
|
||||
if (['control_before_generate', 'control_after_generate'].includes(w.name)) {
|
||||
await updateControlWidgetLabel(w, controlValueRunBefore);
|
||||
if (w.linkedWidgets) {
|
||||
for (const l of w.linkedWidgets) {
|
||||
await updateControlWidgetLabel(l, controlValueRunBefore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,250 @@
|
||||
// 1.0.2
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { GroupNodeConfig } from "../../../../extensions/core/groupNode.js";
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { $t } from "../common/i18n.js"
|
||||
|
||||
const nodeTemplateShortcutId = "Comfy.EasyUse.NodeTemplateShortcut"
|
||||
const processBarId = "Comfy.EasyUse.queueProcessBar"
|
||||
|
||||
let enableNodeTemplateShortcut = true
|
||||
let enableQueueProcess = false
|
||||
|
||||
export function addNodeTemplateShortcutSetting(app) {
|
||||
app.ui.settings.addSetting({
|
||||
id: nodeTemplateShortcutId,
|
||||
name: $t("Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)"),
|
||||
type: "boolean",
|
||||
defaultValue: enableNodeTemplateShortcut,
|
||||
onChange(value) {
|
||||
enableNodeTemplateShortcut = !!value;
|
||||
},
|
||||
});
|
||||
}
|
||||
export function addQueueProcessSetting(app) {
|
||||
app.ui.settings.addSetting({
|
||||
id: processBarId,
|
||||
name: $t("Enable process bar in queue button (ComfyUI-Easy-Use)"),
|
||||
type: "boolean",
|
||||
defaultValue: enableQueueProcess,
|
||||
onChange(value) {
|
||||
enableQueueProcess = !!value;
|
||||
},
|
||||
});
|
||||
}
|
||||
const getEnableNodeTemplateShortcut = _ => app.ui.settings.getSettingValue(nodeTemplateShortcutId, true)
|
||||
const getQueueProcessSetting = _ => app.ui.settings.getSettingValue(processBarId, false)
|
||||
|
||||
function loadTemplate(){
|
||||
return localStorage['Comfy.NodeTemplates'] ? JSON.parse(localStorage['Comfy.NodeTemplates']) : null
|
||||
}
|
||||
const clipboardAction = async (cb) => {
|
||||
const old = localStorage.getItem("litegrapheditor_clipboard");
|
||||
await cb();
|
||||
localStorage.setItem("litegrapheditor_clipboard", old);
|
||||
};
|
||||
async function addTemplateToCanvas(t){
|
||||
const data = JSON.parse(t.data);
|
||||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {});
|
||||
localStorage.setItem("litegrapheditor_clipboard", t.data);
|
||||
app.canvas.pasteFromClipboard();
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'comfy.easyUse.quick',
|
||||
init() {
|
||||
const keybindListener = async function (event) {
|
||||
let modifierPressed = event.altKey;
|
||||
const isEnabled = getEnableNodeTemplateShortcut()
|
||||
if(isEnabled){
|
||||
const mac_alt_nums = ['¡','™','£','¢','∞','§','¶','•','ª']
|
||||
const nums = ['1','2','3','4','5','6','7','8','9']
|
||||
let key = event.key
|
||||
if(mac_alt_nums.includes(key)){
|
||||
const idx = mac_alt_nums.findIndex(cate=> cate == key)
|
||||
key = nums[idx]
|
||||
modifierPressed = true
|
||||
}
|
||||
if(['1','2','3','4','5','6','7','8','9'].includes(key) && modifierPressed) {
|
||||
const template = loadTemplate()
|
||||
const idx = parseInt(key) - 1
|
||||
if (template && template[idx]) {
|
||||
let t = template[idx]
|
||||
try{
|
||||
let data = JSON.parse(t.data)
|
||||
data.title = t.name
|
||||
t.data = JSON.stringify(data)
|
||||
clipboardAction(_ => {
|
||||
addTemplateToCanvas(t)
|
||||
})
|
||||
}catch (e){
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
}
|
||||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
window.addEventListener("keydown", keybindListener, true);
|
||||
},
|
||||
|
||||
setup(app) {
|
||||
addNodeTemplateShortcutSetting(app)
|
||||
addQueueProcessSetting(app)
|
||||
registerListeners()
|
||||
}
|
||||
});
|
||||
|
||||
const registerListeners = () => {
|
||||
const queue_button = document.getElementById("queue-button")
|
||||
const old_queue_button_text = queue_button.innerText
|
||||
api.addEventListener('progress', ({
|
||||
detail,
|
||||
}) => {
|
||||
const isEnabled = getQueueProcessSetting()
|
||||
if(isEnabled){
|
||||
const {
|
||||
value, max, node,
|
||||
} = detail;
|
||||
const progress = Math.floor((value / max) * 100);
|
||||
// console.log(progress)
|
||||
if (!isNaN(progress) && progress >= 0 && progress <= 100) {
|
||||
queue_button.innerText = progress ==0 || progress == 100 ? old_queue_button_text : "ㅤ "
|
||||
const width = progress ==0 || progress == 100 ? '0%' : progress.toString() + '%'
|
||||
let bar = document.createElement("div")
|
||||
queue_button.setAttribute('data-attr', progress ==0 || progress == 100 ? "" : progress.toString() + '%')
|
||||
document.documentElement.style.setProperty('--process-bar-width', width)
|
||||
}
|
||||
}
|
||||
|
||||
}, false);
|
||||
|
||||
api.addEventListener('status', ({
|
||||
detail,
|
||||
}) => {
|
||||
const queueRemaining = detail?.exec_info.queue_remaining;
|
||||
if(queueRemaining === 0){
|
||||
let queue_button = document.getElementById("queue-button")
|
||||
queue_button.innerText = old_queue_button_text
|
||||
queue_button.setAttribute('data-attr', "")
|
||||
document.documentElement.style.setProperty('--process-bar-width', '0%')
|
||||
}
|
||||
}, false);
|
||||
};
|
||||
|
||||
|
||||
// 修改粘贴指令
|
||||
LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {
|
||||
// if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior
|
||||
if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {
|
||||
return;
|
||||
}
|
||||
var data = localStorage.getItem("litegrapheditor_clipboard");
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.graph.beforeChange();
|
||||
|
||||
//create nodes
|
||||
var clipboard_info = JSON.parse(data);
|
||||
// calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos
|
||||
var posMin = false;
|
||||
var posMinIndexes = false;
|
||||
for (var i = 0; i < clipboard_info.nodes.length; ++i) {
|
||||
if (posMin){
|
||||
if(posMin[0]>clipboard_info.nodes[i].pos[0]){
|
||||
posMin[0] = clipboard_info.nodes[i].pos[0];
|
||||
posMinIndexes[0] = i;
|
||||
}
|
||||
if(posMin[1]>clipboard_info.nodes[i].pos[1]){
|
||||
posMin[1] = clipboard_info.nodes[i].pos[1];
|
||||
posMinIndexes[1] = i;
|
||||
}
|
||||
}
|
||||
else{
|
||||
posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]];
|
||||
posMinIndexes = [i, i];
|
||||
}
|
||||
}
|
||||
var nodes = [];
|
||||
var left_arr = [], right_arr = [], top_arr =[], bottom_arr =[];
|
||||
|
||||
for (var i = 0; i < clipboard_info.nodes.length; ++i) {
|
||||
var node_data = clipboard_info.nodes[i];
|
||||
var node = LiteGraph.createNode(node_data.type);
|
||||
if (node) {
|
||||
|
||||
node.configure(node_data);
|
||||
//paste in last known mouse position
|
||||
node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5;
|
||||
node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5;
|
||||
|
||||
left_arr.push(node.pos[0])
|
||||
right_arr.push(node.pos[0] + node.size[0])
|
||||
top_arr.push(node.pos[1])
|
||||
bottom_arr.push(node.pos[1] + node.size[1])
|
||||
|
||||
this.graph.add(node,{doProcessChange:false});
|
||||
|
||||
nodes.push(node);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if(clipboard_info.title){
|
||||
var l = Math.min(...left_arr) - 15;
|
||||
var r = Math.max(...right_arr) - this.graph_mouse[0] + 30;
|
||||
var t = Math.min(...top_arr) - 100;
|
||||
var b = Math.max(...bottom_arr) - this.graph_mouse[1] + 130;
|
||||
|
||||
// create group
|
||||
const groups = [
|
||||
{
|
||||
"title": clipboard_info.title,
|
||||
"bounding": [
|
||||
l,
|
||||
t,
|
||||
r,
|
||||
b
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
}
|
||||
]
|
||||
|
||||
for (var i = 0; i < groups.length; ++i) {
|
||||
var group = new LiteGraph.LGraphGroup();
|
||||
group.configure(groups[i]);
|
||||
this.graph.add(group);
|
||||
}
|
||||
}
|
||||
|
||||
//create links
|
||||
for (var i = 0; i < clipboard_info.links.length; ++i) {
|
||||
var link_info = clipboard_info.links[i];
|
||||
var origin_node;
|
||||
var origin_node_relative_id = link_info[0];
|
||||
if (origin_node_relative_id != null) {
|
||||
origin_node = nodes[origin_node_relative_id];
|
||||
} else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {
|
||||
var origin_node_id = link_info[4];
|
||||
if (origin_node_id) {
|
||||
origin_node = this.graph.getNodeById(origin_node_id);
|
||||
}
|
||||
}
|
||||
var target_node = nodes[link_info[2]];
|
||||
if( origin_node && target_node )
|
||||
origin_node.connect(link_info[1], target_node, link_info[3]);
|
||||
else
|
||||
console.warn("Warning, nodes missing on pasting");
|
||||
}
|
||||
|
||||
this.selectNodes(nodes);
|
||||
this.graph.afterChange();
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { applyTextReplacements } from "../../../../scripts/utils.js";
|
||||
|
||||
const extraNodes = ["easy imageSave", "easy fullkSampler", "easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo","easy detailerFix"]
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.Easy.SaveImageExtraOutput",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (extraNodes.includes(nodeData.name)) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
// When the SaveImage node is created we want to override the serialization of the output name widget to run our S&R
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
|
||||
const widget = this.widgets.find((w) => w.name === "filename_prefix" || w.name === 'save_prefix');
|
||||
widget.serializeValue = () => {
|
||||
return applyTextReplacements(app, widget.value);
|
||||
};
|
||||
|
||||
return r;
|
||||
};
|
||||
} else {
|
||||
// When any other node is created add a property to alias the node
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
|
||||
if (!this.properties || !("Node name for S&R" in this.properties)) {
|
||||
this.addProperty("Node name for S&R", this.constructor.type, "string");
|
||||
}
|
||||
|
||||
return r;
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
136
custom_nodes/ComfyUI-Easy-Use/web_version/v1/js/easy/easySeg.js
Normal file
136
custom_nodes/ComfyUI-Easy-Use/web_version/v1/js/easy/easySeg.js
Normal file
@@ -0,0 +1,136 @@
|
||||
import {app} from "../../../../scripts/app.js";
|
||||
import {$el} from "../../../../scripts/ui.js";
|
||||
import {$t} from "../common/i18n.js";
|
||||
import {findWidgetByName, toggleWidget} from "../common/utils.js";
|
||||
|
||||
|
||||
const tags = {
|
||||
"selfie_multiclass_256x256": ["Background", "Hair", "Body", "Face", "Clothes", "Others",],
|
||||
"human_parsing_lip":["Background","Hat","Hair","Glove","Sunglasses","Upper-clothes","Dress","Coat","Socks","Pants","Jumpsuits","Scarf","Skirt","Face","Left-arm","Right-arm","Left-leg","Right-leg","Left-shoe","Right-shoe"],
|
||||
}
|
||||
function getTagList(tags) {
|
||||
let rlist=[]
|
||||
tags.forEach((k,i) => {
|
||||
rlist.push($el(
|
||||
"label.easyuse-prompt-styles-tag",
|
||||
{
|
||||
dataset: {
|
||||
tag: i,
|
||||
name: $t(k),
|
||||
index: i
|
||||
},
|
||||
$: (el) => {
|
||||
el.children[0].onclick = () => {
|
||||
el.classList.toggle("easyuse-prompt-styles-tag-selected");
|
||||
};
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("input",{
|
||||
type: 'checkbox',
|
||||
name: i
|
||||
}),
|
||||
$el("span",{
|
||||
textContent: $t(k),
|
||||
})
|
||||
]
|
||||
))
|
||||
});
|
||||
return rlist
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'comfy.easyUse.seg',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
|
||||
if (nodeData.name == 'easy humanSegmentation') {
|
||||
// 创建时
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
onNodeCreated ? onNodeCreated?.apply(this, arguments) : undefined;
|
||||
const method = this.widgets.findIndex((w) => w.name == 'method');
|
||||
const list = $el("ul.easyuse-prompt-styles-list.no-top", []);
|
||||
let method_values = ''
|
||||
this.setProperty("values", [])
|
||||
|
||||
let selector = this.addDOMWidget('mask_components',"btn",$el('div.easyuse-prompt-styles',[list]))
|
||||
|
||||
Object.defineProperty(this.widgets[method],'value',{
|
||||
set:(value)=>{
|
||||
method_values = value
|
||||
if(method_values){
|
||||
selector.element.children[0].innerHTML = ''
|
||||
if(method_values == 'selfie_multiclass_256x256'){
|
||||
toggleWidget(this, findWidgetByName(this, 'confidence'), true)
|
||||
this.setSize([300, 260]);
|
||||
}else{
|
||||
toggleWidget(this, findWidgetByName(this, 'confidence'))
|
||||
this.setSize([300, 500]);
|
||||
}
|
||||
let list = getTagList(tags[method_values]);
|
||||
selector.element.children[0].append(...list)
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
return method_values
|
||||
}
|
||||
})
|
||||
|
||||
let mask_select_values = ''
|
||||
|
||||
Object.defineProperty(selector, "value", {
|
||||
set: (value) => {
|
||||
setTimeout(_=>{
|
||||
selector.element.children[0].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
let arr = value.split(',')
|
||||
if (arr.includes(el.dataset.tag)) {
|
||||
el.classList.add("easyuse-prompt-styles-tag-selected");
|
||||
el.children[0].checked = true
|
||||
}
|
||||
})
|
||||
},100)
|
||||
},
|
||||
get: () => {
|
||||
selector.element.children[0].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
if(el.classList.value.indexOf("easyuse-prompt-styles-tag-selected")>=0){
|
||||
if(!this.properties["values"].includes(el.dataset.tag)){
|
||||
this.properties["values"].push(el.dataset.tag);
|
||||
}
|
||||
}else{
|
||||
if(this.properties["values"].includes(el.dataset.tag)){
|
||||
this.properties["values"]= this.properties["values"].filter(v=>v!=el.dataset.tag);
|
||||
}
|
||||
}
|
||||
});
|
||||
mask_select_values = this.properties["values"].join(',');
|
||||
return mask_select_values;
|
||||
}
|
||||
});
|
||||
|
||||
let old_values = ''
|
||||
let mask_lists_dom = selector.element.children[0]
|
||||
|
||||
// 初始化
|
||||
setTimeout(_=>{
|
||||
if(!method_values) {
|
||||
method_values = 'selfie_multiclass_256x256'
|
||||
selector.element.children[0].innerHTML = ''
|
||||
// 重新排序
|
||||
let list = getTagList(tags[method_values]);
|
||||
selector.element.children[0].append(...list)
|
||||
}
|
||||
if(method_values == 'selfie_multiclass_256x256'){
|
||||
toggleWidget(this, findWidgetByName(this, 'confidence'), true)
|
||||
this.setSize([300, 260]);
|
||||
}else{
|
||||
toggleWidget(this, findWidgetByName(this, 'confidence'))
|
||||
this.setSize([300, 500]);
|
||||
}
|
||||
},1)
|
||||
|
||||
return onNodeCreated;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,296 @@
|
||||
// 1.0.3
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { $el } from "../../../../scripts/ui.js";
|
||||
import { $t } from "../common/i18n.js";
|
||||
|
||||
// 获取风格列表
|
||||
let styles_list_cache = {}
|
||||
let styles_image_cache = {}
|
||||
async function getStylesList(name){
|
||||
if(styles_list_cache[name]) return styles_list_cache[name]
|
||||
else{
|
||||
const resp = await api.fetchApi(`/easyuse/prompt/styles?name=${name}`);
|
||||
if (resp.status === 200) {
|
||||
let data = await resp.json();
|
||||
styles_list_cache[name] = data;
|
||||
return data;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
async function getStylesImage(name, styles_name){
|
||||
if(!styles_image_cache[styles_name]) styles_image_cache[styles_name] = {}
|
||||
if(styles_image_cache[styles_name][name]) return styles_image_cache[styles_name][name]
|
||||
else{
|
||||
const resp = await api.fetchApi(`/easyuse/prompt/styles/image?name=${name}&styles_name=${styles_name}`);
|
||||
if (resp.status === 200) {
|
||||
const text = await resp.text()
|
||||
if(text.startsWith('http')){
|
||||
styles_image_cache[styles_name][name] = text
|
||||
return text
|
||||
}
|
||||
const url = `/easyuse/prompt/styles/image?name=${name}&styles_name=${styles_name}`
|
||||
styles_image_cache[styles_name][name] = url
|
||||
return url
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getTagList(tags, styleName, language='en-US') {
|
||||
let rlist=[]
|
||||
tags.forEach((k,i) => {
|
||||
rlist.push($el(
|
||||
"label.easyuse-prompt-styles-tag",
|
||||
{
|
||||
dataset: {
|
||||
tag: k['name'],
|
||||
name: language == 'zh-CN' && k['name_cn'] ? k['name_cn'] : k['name'],
|
||||
imgName: k['imgName'],
|
||||
index: i
|
||||
},
|
||||
$: (el) => {
|
||||
el.children[0].onclick = () => {
|
||||
el.classList.toggle("easyuse-prompt-styles-tag-selected");
|
||||
};
|
||||
el.onmousemove = (e) => {
|
||||
displayImage(el.dataset.imgName, styleName, e)
|
||||
};
|
||||
el.onmouseout = () => {
|
||||
hiddenImage()
|
||||
};
|
||||
el.onmouseover = (e) => {
|
||||
displayImage(el.dataset.imgName, styleName)
|
||||
};
|
||||
},
|
||||
},
|
||||
[
|
||||
$el("input",{
|
||||
type: 'checkbox',
|
||||
name: k['name']
|
||||
}),
|
||||
$el("span",{
|
||||
textContent: language == 'zh-CN' && k['name_cn'] ? k['name_cn'] : k['name'],
|
||||
})
|
||||
]
|
||||
))
|
||||
});
|
||||
return rlist
|
||||
}
|
||||
|
||||
const foocus_base_path = "https://raw.githubusercontent.com/lllyasviel/Fooocus/main/sdxl_styles/samples/"
|
||||
const empty_img = "data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QNLaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA5LjEtYzAwMSA3OS4xNDYyODk5Nzc3LCAyMDIzLzA2LzI1LTIzOjU3OjE0ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjUuMSAoMjAyMzA5MDUubS4yMzE2IDk3OWM4NmQpICAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjA3NEU1QzNCNUJBMTFFRUExMUVDNkZDRjI0NzlBN0QiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjA3NEU1QzRCNUJBMTFFRUExMUVDNkZDRjI0NzlBN0QiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGMDc0RTVDMUI1QkExMUVFQTExRUM2RkNGMjQ3OUE3RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGMDc0RTVDMkI1QkExMUVFQTExRUM2RkNGMjQ3OUE3RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEAAYEBAQFBAYFBQYJBgUGCQsIBgYICwwKCgsKCgwQDAwMDAwMEAwODxAPDgwTExQUExMcGxsbHB8fHx8fHx8fHx8BBwcHDQwNGBAQGBoVERUaHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fH//AABEIAIAAgAMBEQACEQEDEQH/xACLAAEAAgMBAQEAAAAAAAAAAAAABAUCAwYBBwgBAQADAQEBAAAAAAAAAAAAAAABAgMEBQYQAAEEAgECAwUHAwUAAAAAAAEAAgMEEQUhEgYxEwdBYSIyFFFxgVJyIxWRoTOxwdFiJBEBAAICAQQBBAIDAAAAAAAAAAECEQMxIUESBBOB0SIyUXGCIwX/2gAMAwEAAhEDEQA/AP1SgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICDXJYgj+d4afsVopM8KWvEcy8it1pXdMcjXO/Lnn+im2u0cwV2VniW1UXEBAQEBAQEBAQEBAQRNlc+mgyDh7zhv+5WunX5Sw37fCHM2dh48r06ank7N6rn2Ja7qa4hw5BBwQV010uK+/DsO29v/J68SOI86Jxjl95HIP4gryPc0fHfHaXu+j7Py68zzHSVquV2iAgICAgICAgICDyTr6HdHz4PTnwypjnqic46OauNbY6mGX99p+L8w9xaeV6OufHt0eXtr59M9VFb194E9LmuH3kf6rv17avO2ets7YVcuuuk/uOa3PgBlxP4BdMbq9nLPqbJ5xDbSM9azFXpyujuSO+Bo5kcf0NPyj25We2YtEzaPxdfr6519Kz+UvqEIlELBKQZQ0eYRwC7HOPxXzVsZ6cPpK5x15ZKEiAgICAgICAgICCNc1tG40CzA2XHg4j4h9zhyFpr22p+s4Z7NNL/ALRlTX+1dVFBJOJrcTI2lxZHYcBx+sldWv3bzOMVn6fZy39OkRnNo+v3aoOx9JOxks8tqwHDPS+1IW8+IzGWZVrf9DZHSMR/j9yvo656zMz9V1rdLqdYwsoVIqwd87mNAc79Tvmd+JXJt332ftMy6temlP1jCasmggICAgICAgICAgwlmiib1SPDB7zhWrWZ4VtaI5QXb2l5ojYHvLjjIGB/dbR61sZlhPtVziFb3PYdd0luCvAZbXludVZ1huZQPgyTx4/atvWj4rxaZ6d/6Ye1/t1zSI6zx/bzti5YqaOpBeg8u41n/oa14cA4ccH7lPs1jZebVn8eyPUtOrXFbR+XdYx9xa90pjeXROaSCXDj+oysZ9S+Mx1bR7uvOJ6LGOWKVgfG8PafAtOQueazHLqraJjMMlCRAQEBAQEBAQRLNp4HTFx/2/4WtKR3Y32T2Udl8j3knk/aeSu6kREPPvaZlpY3DmyY8DyrzPZWv8tkvmFv7bg12RyR1DGeeMj2KnjE9JaeUx1hi1sgaet/U7JIOMcE8Dj7FMREcK2zPKMasr5XO6fmOVt5xEOadVplYU45IAOhxa72kLm2TFuXXqrNeF1WtlwDZeHfmHguO+vHDupszylLJsICAgICAg8cMjCQiYR5IVpFmc1Q5qLXHPgfbhbV2MLaYlqNQAYA4V/kV+PDA1fcp81fjYurtYMu4CmLZRNYhtZWBAI8CqzdaKN8df3LObtIokxwe5ZzZrFUloIGFnLWHqhIgICAgICAgxMbSpyjDAwAq3kr4MTWCnzR4MX02PGHDISNmETqieWba7QABwB4KJumKNgjaFXK0VZYChYQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEHzvuv1G7k1W9s6/Xamtaq15oaonmnsCR008HntaI4K8/s4HOeEGXZXqTud7uqtG7r6kNa5HdMU9aaw9zZde+FkrHsnr1+M2MZBPIKDRe9cO2K2mjs/V0m7X61lWzq32W+ZFEbfkSSO4B+GL9zw4QWm99TqFVmjsaSu7fUtxeNM2aTmSMBbHI9zWHqHVJlnDTxjPKCJL6sea502t1D7Ouhr0rNqxNM2CSNuwnkgjAi6ZOotdEc/Egibf1j/j+7JNL9DWdWg84TWn2ywtdFKyMZb5Tg0nLyG55x48IJ3bXqe/ea/a26dFtyTXtldDUqyOdNL5VqaDHS5gwXRxMe3xz1Y9iDKP1Sa7uefUnR7TyYqUVoEU5jY6pJZIz1RY4ZiMYd7TkexBA749Wr2gtCKlrIpGs17NjK29LLWmPmMsyiFkbIZsPEdKQu6y0eAQWdD1E2L93W1tzRyCDY3paev2NaxVlhIjidMfMb5vmse1kbi9pZ7MeKDt0BAQEBAQfEPU+lFY2++q2K1uSSezTnrReVsTTmiZVYHOd9LVuQyubIwANkbxz4FA7FsQ0NrrLNXX7N0eo1+3darGDYPjb5j6prxVRajjDetsRAjj4yM4CDre2uxO7q2hqtm7nua6w9rp5tfXgoSxwyTOMr42PlrPe4Nc8jJJQRDb3Oz1fYFrcV7As0mu3u7nbWkBZ9LSfG5nlxs/yySWRiNozwcBBx9EXadGTXz62+LG41+jZS6adhzS6vfnlkEjgzEZax7T8ePFBu3nbPdUXqJZsw6S5cqbCW1YdIY2lxhhfEGMjfHtoG9HxucwPEZy4/A7kMC87aq2Kmv7mdvxuqGmklFjUU4G2Yp21rdyW00t+kJkFl88pY9vDgwNDvEoK9np73FBcHdkrt2+rZd5FjQx7O0b8WvbzDKZhN1SSse573QdeAHkN+Ichj3p2rBvZq9vUnY2tcNQPqpZYZpJ44GxXqzHdVlzZZpib73mLHViI85c1BZ6OpsIe/6/XSuntevdsz6+8+pI0/yM1dtWVr2Z644P8rmyuj6S53jxkh9aQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBB/9k="
|
||||
async function displayImage(imgName, styleName) {
|
||||
var e = event || window.event;
|
||||
var img = document.getElementById("show_image_id");
|
||||
var pxy= img.parentElement.getBoundingClientRect();
|
||||
if(imgName) {
|
||||
const url = await getStylesImage(imgName, styleName)
|
||||
img.src = url
|
||||
img.onerror = _ =>{
|
||||
img.src = empty_img
|
||||
}
|
||||
}
|
||||
var scale = app?.canvas?.ds?.scale || 1;
|
||||
var x = (e.pageX-pxy.x-100)/scale;
|
||||
var y = (e.pageY-pxy.y+25)/scale;
|
||||
img.style.left = x+"px";
|
||||
img.style.top = y+"px";
|
||||
img.style.display = "block";
|
||||
img.style.borderRadius = "10px";
|
||||
img.style.borderColor = "var(--fg-color)"
|
||||
img.style.borderWidth = "1px";
|
||||
img.style.borderStyle = "solid";
|
||||
}
|
||||
function hiddenImage(){ //theEvent用来传入事件,Firefox的方式
|
||||
var img = document.getElementById('show_image_id');
|
||||
img.style.display = "none";
|
||||
}
|
||||
|
||||
// StylePromptSelector
|
||||
app.registerExtension({
|
||||
name: 'comfy.easyUse.styleSelector',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
|
||||
if(nodeData.name == 'easy stylesSelector'){
|
||||
// 创建时
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
onNodeCreated ? onNodeCreated?.apply(this, arguments) : undefined;
|
||||
const styles_id = this.widgets.findIndex((w) => w.name == 'styles');
|
||||
const language = localStorage['AGL.Locale'] || localStorage['Comfy.Settings.AGL.Locale'] || 'en-US'
|
||||
const list = $el("ul.easyuse-prompt-styles-list",[]);
|
||||
let styles_values = ''
|
||||
this.setProperty("values", [])
|
||||
|
||||
let selector = this.addDOMWidget('select_styles',"btn",$el('div.easyuse-prompt-styles',[$el('div.tools', [
|
||||
$el('button.delete',{
|
||||
textContent: $t('Empty All'),
|
||||
style:{},
|
||||
onclick:()=>{
|
||||
selector.element.children[0].querySelectorAll(".search").forEach(el=>{
|
||||
el.value = ''
|
||||
})
|
||||
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag-selected").forEach(el => {
|
||||
el.classList.remove("easyuse-prompt-styles-tag-selected");
|
||||
el.children[0].checked = false
|
||||
})
|
||||
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
el.classList.remove('hide')
|
||||
})
|
||||
this.setProperty("values", [])
|
||||
}}
|
||||
),
|
||||
$el('textarea.search',{
|
||||
dir:"ltr",
|
||||
style:{"overflow-y": "scroll"},
|
||||
rows:1,
|
||||
placeholder:$t("🔎 Type here to search styles ..."),
|
||||
oninput:(e)=>{
|
||||
let value = e.target.value
|
||||
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
const name = el.dataset.name.toLowerCase()
|
||||
const tag = el.dataset.tag.toLowerCase()
|
||||
const lower_value = value.toLowerCase()
|
||||
if(name.indexOf(lower_value) != -1 || tag.indexOf(lower_value) != -1 || el.classList.value.indexOf("easyuse-prompt-styles-tag-selected")!=-1){
|
||||
el.classList.remove('hide')
|
||||
}
|
||||
else{
|
||||
el.classList.add('hide')
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]),list,
|
||||
$el('img',{id:'show_image_id',
|
||||
style:{display:'none',position:'absolute'},
|
||||
src:``,
|
||||
onerror:()=>{
|
||||
this.src = empty_img
|
||||
}
|
||||
})
|
||||
]));
|
||||
|
||||
Object.defineProperty(this.widgets[styles_id],'value',{
|
||||
set:(value)=>{
|
||||
styles_values = value
|
||||
if(styles_values){
|
||||
getStylesList(styles_values).then(_=>{
|
||||
selector.element.children[1].innerHTML=''
|
||||
if(styles_list_cache[styles_values]){
|
||||
let tags = styles_list_cache[styles_values]
|
||||
// 重新排序
|
||||
if(selector.value) tags = tags.sort((a,b)=> selector.value.includes(b.name) - selector.value.includes(a.name))
|
||||
this.properties["values"] = []
|
||||
let list = getTagList(tags, value, language);
|
||||
selector.element.children[1].append(...list)
|
||||
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
if (this.properties["values"].includes(el.dataset.tag)) {
|
||||
el.classList.add("easyuse-prompt-styles-tag-selected");
|
||||
}
|
||||
if(this.size?.[0]<150 || this.size?.[1]<150) this.setSize([425, 500]);
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
get: () => {
|
||||
return styles_values
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let style_select_values = ''
|
||||
Object.defineProperty(selector, "value", {
|
||||
set: (value) => {
|
||||
setTimeout(_=>{
|
||||
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
let arr = value.split(',')
|
||||
if (arr.includes(el.dataset.tag)) {
|
||||
el.classList.add("easyuse-prompt-styles-tag-selected");
|
||||
el.children[0].checked = true
|
||||
}
|
||||
})
|
||||
},300)
|
||||
},
|
||||
get: () => {
|
||||
selector.element.children[1].querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
if(el.classList.value.indexOf("easyuse-prompt-styles-tag-selected")>=0){
|
||||
if(!this.properties["values"].includes(el.dataset.tag)){
|
||||
this.properties["values"].push(el.dataset.tag);
|
||||
}
|
||||
}else{
|
||||
if(this.properties["values"].includes(el.dataset.tag)){
|
||||
this.properties["values"]= this.properties["values"].filter(v=>v!=el.dataset.tag);
|
||||
}
|
||||
}
|
||||
});
|
||||
style_select_values = this.properties["values"].join(',');
|
||||
return style_select_values;
|
||||
}
|
||||
});
|
||||
|
||||
let old_values = ''
|
||||
let style_lists_dom = selector.element.children[1]
|
||||
style_lists_dom.addEventListener('mouseenter', function (e) {
|
||||
let value = ''
|
||||
style_lists_dom.querySelectorAll(".easyuse-prompt-styles-tag-selected").forEach(el=> value+=el.dataset.tag)
|
||||
old_values = value
|
||||
})
|
||||
style_lists_dom.addEventListener('mouseleave', function (e) {
|
||||
let value = ''
|
||||
style_lists_dom.querySelectorAll(".easyuse-prompt-styles-tag-selected").forEach(el=> value+=el.dataset.tag)
|
||||
let new_values = value
|
||||
if(old_values != new_values){
|
||||
// console.log("选项发生了变化")
|
||||
// 获取搜索值
|
||||
const search_value = document.getElementsByClassName('search')[0]['value']
|
||||
// 重新排序
|
||||
const tags = styles_list_cache[styles_values].sort((a,b)=> new_values.includes(b.name) - new_values.includes(a.name))
|
||||
style_lists_dom.innerHTML = ''
|
||||
let list = getTagList(tags, styles_values, language);
|
||||
style_lists_dom.append(...list)
|
||||
style_lists_dom.querySelectorAll(".easyuse-prompt-styles-tag").forEach(el => {
|
||||
if (new_values.includes(el.dataset.tag)) {
|
||||
el.classList.add("easyuse-prompt-styles-tag-selected");
|
||||
el.children[0].checked = true;
|
||||
}
|
||||
if(search_value){
|
||||
if(el.dataset.name.indexOf(search_value) != -1 || el.dataset.tag.indexOf(search_value) != -1 || el.classList.value.indexOf("easyuse-prompt-styles-tag-selected")!=-1){
|
||||
el.classList.remove('hide')
|
||||
}
|
||||
else{
|
||||
el.classList.add('hide')
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 初始化
|
||||
setTimeout(_=>{
|
||||
if(!styles_values) {
|
||||
styles_values = 'fooocus_styles'
|
||||
getStylesList(styles_values).then(_=>{
|
||||
selector.element.children[1].innerHTML=''
|
||||
if(styles_list_cache[styles_values]){
|
||||
let tags = styles_list_cache[styles_values]
|
||||
// 重新排序
|
||||
if(selector.value) tags = tags.sort((a,b)=> selector.value.includes(b.name) - selector.value.includes(a.name))
|
||||
let list = getTagList(tags, styles_values, language);
|
||||
selector.element.children[1].append(...list)
|
||||
}
|
||||
})
|
||||
}
|
||||
if(this.size?.[0]<150 || this.size?.[1]<150) this.setSize([425, 500]);
|
||||
//
|
||||
},100)
|
||||
|
||||
return onNodeCreated;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,173 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { api } from "../../../../scripts/api.js";
|
||||
import { $el } from "../../../../scripts/ui.js";
|
||||
import { $t } from "../common/i18n.js";
|
||||
import { sleep } from "../common/utils.js";
|
||||
|
||||
|
||||
const calculatePercent = (value, min, max) => ((value-min)/(max-min)*100)
|
||||
|
||||
const getLayerDefaultValue = (index) => {
|
||||
switch (index){
|
||||
case 3:
|
||||
return 2.5
|
||||
case 6:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const addLayer = (_this, layer_total, arrays, sliders, i) => {
|
||||
let scroll = $el('div.easyuse-slider-item-scroll')
|
||||
let value = $el('div.easyuse-slider-item-input', {textContent: arrays[i]['value']})
|
||||
let label = $el('div.easyuse-slider-item-label', {textContent: 'L'+i})
|
||||
let girdTotal = (arrays[i]['max'] - arrays[i]['min']) / arrays[i]['step']
|
||||
let area = $el('div.easyuse-slider-item-area', {style:{ height: calculatePercent(arrays[i]['default'],arrays[i]['min'],arrays[i]['max']) + '%'}})
|
||||
let bar = $el('div.easyuse-slider-item-bar', {
|
||||
style:{ top: (100-calculatePercent(arrays[i]['default'],arrays[i]['min'],arrays[i]['max'])) + '%'},
|
||||
onmousedown: (e) => {
|
||||
let event = e || window.event;
|
||||
var y = event.clientY - bar.offsetTop;
|
||||
document.onmousemove = (e) => {
|
||||
let event = e || window.event;
|
||||
let top = event.clientY - y;
|
||||
if(top < 0){
|
||||
top = 0;
|
||||
}
|
||||
else if(top > scroll.offsetHeight - bar.offsetHeight){
|
||||
top = scroll.offsetHeight - bar.offsetHeight;
|
||||
}
|
||||
// top到最近的girdHeight值
|
||||
let girlHeight = (scroll.offsetHeight - bar.offsetHeight)/ girdTotal
|
||||
top = Math.round(top / girlHeight) * girlHeight;
|
||||
bar.style.top = Math.floor(top/(scroll.offsetHeight - bar.offsetHeight)* 100) + '%';
|
||||
area.style.height = Math.floor((scroll.offsetHeight - bar.offsetHeight - top)/(scroll.offsetHeight - bar.offsetHeight)* 100) + '%';
|
||||
value.innerText = parseFloat(parseFloat(arrays[i]['max'] - (arrays[i]['max']-arrays[i]['min']) * (top/(scroll.offsetHeight - bar.offsetHeight))).toFixed(2))
|
||||
arrays[i]['value'] = value.innerText
|
||||
_this.properties['values'][i] = i+':'+value.innerText
|
||||
window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
|
||||
}
|
||||
},
|
||||
ondblclick:_=>{
|
||||
bar.style.top = (100-calculatePercent(arrays[i]['default'],arrays[i]['min'],arrays[i]['max'])) + '%'
|
||||
area.style.height = calculatePercent(arrays[i]['default'],arrays[i]['min'],arrays[i]['max']) + '%'
|
||||
value.innerText = arrays[i]['default']
|
||||
arrays[i]['value'] = arrays[i]['default']
|
||||
_this.properties['values'][i] = i+':'+value.innerText
|
||||
}
|
||||
})
|
||||
document.onmouseup = _=> document.onmousemove = null;
|
||||
|
||||
scroll.replaceChildren(bar,area)
|
||||
let item_div = $el('div.easyuse-slider-item',[
|
||||
value,
|
||||
scroll,
|
||||
label
|
||||
])
|
||||
if(i == 3 ) layer_total == 12 ? item_div.classList.add('negative') : item_div.classList.remove('negative')
|
||||
else if(i == 6) layer_total == 12 ? item_div.classList.add('positive') : item_div.classList.remove('positive')
|
||||
sliders.push(item_div)
|
||||
return item_div
|
||||
}
|
||||
|
||||
const setSliderValue = (_this, type, refresh=false, values_div, sliders_value) => {
|
||||
let layer_total = type == 'sdxl' ? 12 : 16
|
||||
let sliders = []
|
||||
let arrays = Array.from({length: layer_total}, (v, i) => ({default: layer_total == 12 ? getLayerDefaultValue(i) : 0, min: -1, max: 3, step: 0.05, value:layer_total == 12 ? getLayerDefaultValue(i) : 0}))
|
||||
_this.setProperty("values", Array.from({length: layer_total}, (v, i) => i+':'+arrays[i]['value']))
|
||||
for (let i = 0; i < layer_total; i++) {
|
||||
addLayer(_this, layer_total, arrays, sliders, i)
|
||||
}
|
||||
if(refresh) values_div.replaceChildren(...sliders)
|
||||
else{
|
||||
values_div = $el('div.easyuse-slider', sliders)
|
||||
sliders_value = _this.addDOMWidget('values',"btn",values_div)
|
||||
}
|
||||
|
||||
Object.defineProperty(sliders_value, 'value', {
|
||||
set: function() {},
|
||||
get: function() {
|
||||
return _this.properties.values.join(',');
|
||||
}
|
||||
});
|
||||
return {sliders, arrays, values_div, sliders_value}
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'comfy.easyUse.sliderControl',
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if(nodeData.name == 'easy sliderControl'){
|
||||
// 创建时
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
onNodeCreated && onNodeCreated.call(this);
|
||||
const mode = this.widgets[0];
|
||||
const model_type = this.widgets[1];
|
||||
let layer_total = model_type.value == 'sdxl' ? 12 : 16
|
||||
let _this = this
|
||||
let values_div = null
|
||||
let sliders_value = null
|
||||
mode.callback = async()=>{
|
||||
switch (mode.value) {
|
||||
case 'ipadapter layer weights':
|
||||
nodeData.output_name = ['layer_weights']
|
||||
_this.outputs[0]['name'] = 'layer_weights'
|
||||
_this.outputs[0]['label'] = 'layer_weights'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
model_type.callback = async()=>{
|
||||
if(values_div) {
|
||||
let r2 = setSliderValue(_this, model_type.value, true, values_div, sliders_value)
|
||||
values_div = r2.values_div
|
||||
sliders_value = r2.sliders_value
|
||||
}
|
||||
_this.setSize(model_type.value == 'sdxl' ? [375,320] : [455,320])
|
||||
}
|
||||
|
||||
let r1 = setSliderValue(_this, model_type.value, false, values_div, sliders_value)
|
||||
let sliders = r1.sliders
|
||||
let arrays = r1.arrays
|
||||
values_div = r1.values_div
|
||||
sliders_value = r1.sliders_value
|
||||
setTimeout(_=>{
|
||||
let values_widgets_index = this.widgets.findIndex((w) => w.name == 'values');
|
||||
if(values_widgets_index != -1){
|
||||
let old_values_widget = this.widgets[values_widgets_index];
|
||||
let old_value = old_values_widget.value.split(',')
|
||||
let layer_total = _this.widgets[1].value == 'sdxl' ? 12 : 16
|
||||
for (let i = 0; i < layer_total; i++) {
|
||||
let value = parseFloat(parseFloat(old_value[i].split(':')[1]).toFixed(2))
|
||||
let item_div = sliders[i] || null
|
||||
// 存在层即修改
|
||||
if(arrays[i]){
|
||||
arrays[i]['value'] = value
|
||||
_this.properties['values'][i] = old_value[i]
|
||||
}else{
|
||||
arrays.push({default: layer_total == 12 ? getLayerDefaultValue(i) : 0, min: -1, max: 3, step: 0.05, value:layer_total == 12 ? getLayerDefaultValue(i) : 0})
|
||||
_this.properties['values'].push(i+':'+arrays[i]['value'])
|
||||
// 添加缺失层
|
||||
item_div = addLayer(_this, layer_total, arrays, sliders, i)
|
||||
values_div.appendChild(item_div)
|
||||
}
|
||||
// todo: 修改bar位置等
|
||||
let input = item_div.getElementsByClassName('easyuse-slider-item-input')[0]
|
||||
let bar = item_div.getElementsByClassName('easyuse-slider-item-bar')[0]
|
||||
let area = item_div.getElementsByClassName('easyuse-slider-item-area')[0]
|
||||
if(i == 3 ) layer_total == 12 ? item_div.classList.add('negative') : item_div.classList.remove('negative')
|
||||
else if(i == 6) layer_total == 12 ? item_div.classList.add('positive') : item_div.classList.remove('positive')
|
||||
input.textContent = value
|
||||
bar.style.top = (100-calculatePercent(value,arrays[i]['min'],arrays[i]['max'])) + '%'
|
||||
area.style.height = calculatePercent(value,arrays[i]['min'],arrays[i]['max']) + '%'
|
||||
}
|
||||
}
|
||||
_this.setSize(model_type.value == 'sdxl' ? [375,320] : [455,320])
|
||||
},1)
|
||||
return onNodeCreated;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,554 @@
|
||||
import {app} from "../../../../scripts/app.js";
|
||||
import {api} from "../../../../scripts/api.js";
|
||||
import {$el} from "../../../../scripts/ui.js";
|
||||
|
||||
const propmts = ["easy wildcards", "easy positive", "easy negative", "easy stylesSelector", "easy promptConcat", "easy promptReplace"]
|
||||
const loaders = ["easy a1111Loader", "easy comfyLoader", "easy fullLoader", "easy svdLoader", "easy cascadeLoader", "easy sv3dLoader"]
|
||||
const preSamplingNodes = ["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingNoiseIn", "easy preSamplingCustom", "easy preSamplingDynamicCFG","easy preSamplingSdTurbo", "easy preSamplingLayerDiffusion"]
|
||||
const kSampler = ["easy kSampler", "easy kSamplerTiled","easy kSamplerInpainting", "easy kSamplerDownscaleUnet", "easy kSamplerSDTurbo"]
|
||||
const controlNetNodes = ["easy controlnetLoader", "easy controlnetLoaderADV"]
|
||||
const instantIDNodes = ["easy instantIDApply", "easy instantIDApplyADV"]
|
||||
const ipadapterNodes = ["easy ipadapterApply", "easy ipadapterApplyADV" ,"easy ipadapterApplyFaceIDKolors", "easy ipadapterStyleComposition"]
|
||||
const pipeNodes = ['easy pipeIn','easy pipeOut', 'easy pipeEdit']
|
||||
const xyNodes = ['easy XYPlot', 'easy XYPlotAdvanced']
|
||||
const extraNodes = ['easy setNode']
|
||||
const modelNormalNodes = [...['RescaleCFG','LoraLoaderModelOnly','LoraLoader','FreeU','FreeU_v2'],...ipadapterNodes,...extraNodes]
|
||||
const suggestions = {
|
||||
// prompt
|
||||
"easy seed":{
|
||||
"from":{
|
||||
"INT": [...preSamplingNodes,...['easy fullkSampler']]
|
||||
}
|
||||
},
|
||||
"easy positive":{
|
||||
"from":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy negative":{
|
||||
"from":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy wildcards":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
"easy stylesSelector":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
"easy promptConcat":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
"easy promptReplace":{
|
||||
"from":{
|
||||
"STRING": [...["Reroute","easy showAnything"],...propmts,]
|
||||
}
|
||||
},
|
||||
// sd相关
|
||||
"easy fullLoader": {
|
||||
"from":{
|
||||
"PIPE_LINE": [...preSamplingNodes,...['easy fullkSampler'],...pipeNodes,...extraNodes],
|
||||
"MODEL":modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy a1111Loader": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy comfyLoader": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy svdLoader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy zero123Loader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy sv3dLoader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced", "easy preSamplingDynamicCFG"], ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"STRING": [...propmts]
|
||||
}
|
||||
},
|
||||
"easy preSampling": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
},
|
||||
},
|
||||
"easy preSamplingAdvanced": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingDynamicCFG": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingCustom": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingLayerDiffusion": {
|
||||
"from": {
|
||||
"PIPE_LINE": [...["easy kSamplerLayerDiffusion"], ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingNoiseIn": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...kSampler, ...pipeNodes, ...controlNetNodes, ...xyNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
// ksampler
|
||||
"easy fullkSampler": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix'], ...preSamplingNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy kSampler": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...pipeNodes.reverse(), ...['easy preDetailerFix', 'easy preMaskDetailerFix', 'easy hiresFix'], ...preSamplingNodes, ...extraNodes],
|
||||
}
|
||||
},
|
||||
// cn
|
||||
"easy controlnetLoader": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy controlnetLoaderADV":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
// instant
|
||||
"easy instantIDApply": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy instantIDApplyADV":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...preSamplingNodes, ...controlNetNodes, ...instantIDNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes
|
||||
},
|
||||
"to":{
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterApply":{
|
||||
"to":{
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterApplyADV":{
|
||||
"to":{
|
||||
"STRING": [...["easy sliderControl"], ...propmts],
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
"easy ipadapterStyleComposition":{
|
||||
"to":{
|
||||
"COMBO": [...["easy promptLine"]]
|
||||
}
|
||||
},
|
||||
// fix
|
||||
"easy preDetailerFix":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["easy detailerFix"], ...pipeNodes, ...extraNodes]
|
||||
},
|
||||
"to":{
|
||||
"PIPE_LINE": [...["easy ultralyticsDetectorPipe", "easy samLoaderPipe", "easy kSampler", "easy fullkSampler"]]
|
||||
}
|
||||
},
|
||||
"easy preMaskDetailerFix":{
|
||||
"from": {
|
||||
"PIPE_LINE": [...["easy detailerFix"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy samLoaderPipe": {
|
||||
"from":{
|
||||
"PIPE_LINE": [...["easy preDetailerFix"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy ultralyticsDetectorPipe": {
|
||||
"from":{
|
||||
"PIPE_LINE": [...["easy preDetailerFix"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
// cascade相关
|
||||
"easy cascadeLoader":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy fullCascadeKSampler", 'easy preSamplingCascade'], ...controlNetNodes, ...pipeNodes, ...extraNodes],
|
||||
"MODEL": modelNormalNodes.filter(cate => !ipadapterNodes.includes(cate))
|
||||
}
|
||||
},
|
||||
"easy fullCascadeKSampler":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy preSamplingCascade":{
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy cascadeKSampler",], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
"easy cascadeKSampler": {
|
||||
"from": {
|
||||
"PIPE_LINE": [ ...["easy preSampling", "easy preSamplingAdvanced"], ...pipeNodes, ...extraNodes]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
class NullGraphError extends Error {
|
||||
constructor(message="Attempted to access LGraph reference that was null or undefined.", cause) {
|
||||
super(message, {cause})
|
||||
this.name = "NullGraphError"
|
||||
}
|
||||
}
|
||||
app.registerExtension({
|
||||
name: "comfy.easyuse.suggestions",
|
||||
async setup() {
|
||||
const createDefaultNodeForSlot = LGraphCanvas.prototype.createDefaultNodeForSlot;
|
||||
LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection
|
||||
const opts = Object.assign({ nodeFrom: null // input
|
||||
,slotFrom: null // input
|
||||
,nodeTo: null // output
|
||||
,slotTo: null // output
|
||||
,position: [] // pass the event coords
|
||||
,nodeType: null // choose a nodetype to add, AUTO to set at first good
|
||||
,posAdd:[0,0] // adjust x,y
|
||||
,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h
|
||||
}
|
||||
, optPass || {}
|
||||
);
|
||||
const { afterRerouteId } = opts
|
||||
const that = this;
|
||||
|
||||
const isFrom = opts.nodeFrom && opts.slotFrom!==null;
|
||||
const isTo = !isFrom && opts.nodeTo && opts.slotTo!==null;
|
||||
const node = isFrom ? opts.nodeFrom : opts.nodeTo
|
||||
// Not an Easy Use node, skip showConnectionMenu hijack
|
||||
if(!node || !Object.keys(suggestions).includes(node.type)){
|
||||
return createDefaultNodeForSlot.call(this, optPass)
|
||||
}
|
||||
|
||||
if (!isFrom && !isTo){
|
||||
console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo);
|
||||
return false;
|
||||
}
|
||||
if (!opts.nodeType){
|
||||
console.warn("No type to createDefaultNodeForSlot");
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
|
||||
const nodeType = nodeX.type
|
||||
let slotX = isFrom ? opts.slotFrom : opts.slotTo;
|
||||
|
||||
let iSlotConn = false;
|
||||
switch (typeof slotX){
|
||||
case "string":
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
case "object":
|
||||
// ok slotX
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);
|
||||
break;
|
||||
case "number":
|
||||
iSlotConn = slotX;
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
case "undefined":
|
||||
default:
|
||||
// bad ?
|
||||
//iSlotConn = 0;
|
||||
console.warn("Cant get slot information "+slotX);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slotX===false || iSlotConn===false){
|
||||
console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn);
|
||||
}
|
||||
|
||||
// check for defaults nodes for this slottype
|
||||
var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type;
|
||||
var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;
|
||||
if(slotTypesDefault && slotTypesDefault[fromSlotType]){
|
||||
if (slotX.link !== null) {
|
||||
// is connected
|
||||
}else{
|
||||
// is not not connected
|
||||
}
|
||||
let nodeNewType = false;
|
||||
const fromOrTo = isFrom ? 'from' : 'to'
|
||||
if(suggestions[nodeType] && suggestions[nodeType][fromOrTo] && suggestions[nodeType][fromOrTo][fromSlotType]?.length>0){
|
||||
for(var typeX in suggestions[nodeType][fromOrTo][fromSlotType]){
|
||||
if (opts.nodeType == suggestions[nodeType][fromOrTo][fromSlotType][typeX] || opts.nodeType == "AUTO") {
|
||||
nodeNewType = suggestions[nodeType][fromOrTo][fromSlotType][typeX];
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){
|
||||
for(var typeX in slotTypesDefault[fromSlotType]){
|
||||
if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){
|
||||
nodeNewType = slotTypesDefault[fromSlotType][typeX];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType];
|
||||
}
|
||||
if (nodeNewType) {
|
||||
var nodeNewOpts = false;
|
||||
if (typeof nodeNewType == "object" && nodeNewType.node){
|
||||
nodeNewOpts = nodeNewType;
|
||||
nodeNewType = nodeNewType.node;
|
||||
}
|
||||
|
||||
//that.graph.beforeChange();
|
||||
|
||||
var newNode = LiteGraph.createNode(nodeNewType);
|
||||
if(newNode){
|
||||
// if is object pass options
|
||||
if (nodeNewOpts){
|
||||
if (nodeNewOpts.properties) {
|
||||
for (var i in nodeNewOpts.properties) {
|
||||
newNode.addProperty( i, nodeNewOpts.properties[i] );
|
||||
}
|
||||
}
|
||||
if (nodeNewOpts.inputs) {
|
||||
newNode.inputs = [];
|
||||
for (var i in nodeNewOpts.inputs) {
|
||||
newNode.addOutput(
|
||||
nodeNewOpts.inputs[i][0],
|
||||
nodeNewOpts.inputs[i][1]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (nodeNewOpts.outputs) {
|
||||
newNode.outputs = [];
|
||||
for (var i in nodeNewOpts.outputs) {
|
||||
newNode.addOutput(
|
||||
nodeNewOpts.outputs[i][0],
|
||||
nodeNewOpts.outputs[i][1]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (nodeNewOpts.title) {
|
||||
newNode.title = nodeNewOpts.title;
|
||||
}
|
||||
if (nodeNewOpts.json) {
|
||||
newNode.configure(nodeNewOpts.json);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// add the node
|
||||
that.graph.add(newNode);
|
||||
newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0)
|
||||
,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/
|
||||
|
||||
// connect the two!
|
||||
if (isFrom){
|
||||
opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType );
|
||||
}else{
|
||||
opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType );
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}else{
|
||||
console.log("failed creating "+nodeNewType);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
let showConnectionMenu = LGraphCanvas.prototype.showConnectionMenu
|
||||
LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection
|
||||
const opts = Object.assign({
|
||||
nodeFrom: null, // input
|
||||
slotFrom: null, // input
|
||||
nodeTo: null, // output
|
||||
slotTo: null, // output
|
||||
e: undefined,
|
||||
allow_searchbox: this.allow_searchbox,
|
||||
showSearchBox: this.showSearchBox,
|
||||
}
|
||||
,optPass || {}
|
||||
);
|
||||
const that = this;
|
||||
const { graph } = this
|
||||
const { afterRerouteId } = opts
|
||||
const isFrom = opts.nodeFrom && opts.slotFrom;
|
||||
const isTo = !isFrom && opts.nodeTo && opts.slotTo;
|
||||
const node = isFrom ? opts.nodeFrom : opts.nodeTo
|
||||
|
||||
// Not an Easy Use node, skip showConnectionMenu hijack
|
||||
if(!node || !Object.keys(suggestions).includes(node.type)){
|
||||
return showConnectionMenu.call(this, optPass)
|
||||
}
|
||||
|
||||
if (!isFrom && !isTo){
|
||||
console.warn("No data passed to showConnectionMenu");
|
||||
return false;
|
||||
}
|
||||
|
||||
const nodeX = isFrom ? opts.nodeFrom : opts.nodeTo;
|
||||
if (!nodeX) throw new TypeError("nodeX was null when creating default node for slot.")
|
||||
let slotX = isFrom ? opts.slotFrom : opts.slotTo;
|
||||
|
||||
let iSlotConn = false;
|
||||
switch (typeof slotX){
|
||||
case "string":
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false);
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
case "object":
|
||||
// ok slotX
|
||||
iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name);
|
||||
break;
|
||||
case "number":
|
||||
iSlotConn = slotX;
|
||||
slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX];
|
||||
break;
|
||||
default:
|
||||
// bad ?
|
||||
//iSlotConn = 0;
|
||||
console.warn("Cant get slot information "+slotX);
|
||||
return false;
|
||||
}
|
||||
|
||||
const options = ["Add Node", "Add Reroute", null]
|
||||
if (opts.allow_searchbox){
|
||||
options.push("Search");
|
||||
options.push(null);
|
||||
}
|
||||
|
||||
// get defaults nodes for this slottype
|
||||
var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type;
|
||||
var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in;
|
||||
var nodeType = nodeX.type
|
||||
if(slotTypesDefault && slotTypesDefault[fromSlotType]){
|
||||
const fromOrTo = isFrom ? 'from' : 'to'
|
||||
if(suggestions[nodeType] && suggestions[nodeType][fromOrTo] && suggestions[nodeType][fromOrTo][fromSlotType]?.length>0){
|
||||
for(var typeX in suggestions[nodeType][fromOrTo][fromSlotType]){
|
||||
options.push(suggestions[nodeType][fromOrTo][fromSlotType][typeX]);
|
||||
}
|
||||
}
|
||||
else if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){
|
||||
for(var typeX in slotTypesDefault[fromSlotType]){
|
||||
options.push(slotTypesDefault[fromSlotType][typeX]);
|
||||
}
|
||||
}else{
|
||||
options.push(slotTypesDefault[fromSlotType]);
|
||||
}
|
||||
}
|
||||
|
||||
// build menu
|
||||
var menu = new LiteGraph.ContextMenu(options, {
|
||||
event: opts.e,
|
||||
extra: slotX,
|
||||
title:
|
||||
(slotX && slotX.name != ""
|
||||
? slotX.name + (fromSlotType ? " | " : "")
|
||||
: "") + (slotX && fromSlotType ? fromSlotType : ""),
|
||||
callback: inner_clicked,
|
||||
});
|
||||
|
||||
// callback
|
||||
function inner_clicked(v,options,e) {
|
||||
//console.log("Process showConnectionMenu selection");
|
||||
switch (v) {
|
||||
case "Add Node":
|
||||
LGraphCanvas.onMenuAdd(null, null, e, menu, function (node) {
|
||||
if (!node) return
|
||||
|
||||
if (isFrom) {
|
||||
opts.nodeFrom?.connectByType(iSlotConn, node, fromSlotType, { afterRerouteId })
|
||||
} else {
|
||||
opts.nodeTo?.connectByTypeOutput(iSlotConn, node, fromSlotType, { afterRerouteId })
|
||||
}
|
||||
})
|
||||
break;
|
||||
case "Add Reroute":
|
||||
const node = isFrom ? opts.nodeFrom : opts.nodeTo
|
||||
const slot = options.extra
|
||||
if (!graph) throw new NullGraphError()
|
||||
if (!node) throw new TypeError("Cannot add reroute: node was null")
|
||||
if (!slot) throw new TypeError("Cannot add reroute: slot was null")
|
||||
if (!opts.e) throw new TypeError("Cannot add reroute: CanvasPointerEvent was null")
|
||||
|
||||
const reroute = node.connectFloatingReroute([opts.e.canvasX, opts.e.canvasY], slot, afterRerouteId)
|
||||
if (!reroute) throw new Error("Failed to create reroute")
|
||||
|
||||
that.dirty_canvas = true
|
||||
that.dirty_bgcanvas = true
|
||||
break
|
||||
case "Search":
|
||||
if(isFrom){
|
||||
opts.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType});
|
||||
}else{
|
||||
opts.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
const customProps = {
|
||||
position: [opts.e?.canvasX ?? 0, opts.e?.canvasY ?? 0],
|
||||
nodeType: v,
|
||||
afterRerouteId,
|
||||
}
|
||||
// check for defaults nodes for this slottype
|
||||
that.createDefaultNodeForSlot(Object.assign(opts, customProps))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,400 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import { ComfyWidgets } from "../../../../scripts/widgets.js";
|
||||
|
||||
const KEY_CODES = { ENTER: 13, ESC: 27, ARROW_DOWN: 40, ARROW_UP: 38 };
|
||||
const WIDGET_GAP = -4;
|
||||
|
||||
function hideInfoWidget(e, node, widget) {
|
||||
let dropdownShouldBeRemoved = false;
|
||||
let selectionIndex = -1;
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
displayDropdown(widget);
|
||||
} else {
|
||||
hideWidget(widget, node);
|
||||
}
|
||||
|
||||
function createDropdownElement() {
|
||||
const dropdown = document.createElement('ul');
|
||||
dropdown.id = 'hideinfo-dropdown';
|
||||
dropdown.setAttribute('role', 'listbox');
|
||||
dropdown.classList.add('hideInfo-dropdown');
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
function createDropdownItem(textContent, action) {
|
||||
const listItem = document.createElement('li');
|
||||
listItem.id = `hideInfo-item-${textContent.replace(/ /g, '')}`;
|
||||
listItem.classList.add('hideInfo-item');
|
||||
listItem.setAttribute('role', 'option');
|
||||
listItem.textContent = textContent;
|
||||
listItem.addEventListener('mousedown', (event) => {
|
||||
event.preventDefault();
|
||||
action(widget, node); // perform the action when dropdown item is clicked
|
||||
removeDropdown();
|
||||
dropdownShouldBeRemoved = false;
|
||||
});
|
||||
listItem.dataset.action = textContent.replace(/ /g, ''); // store the action in a data attribute
|
||||
return listItem;
|
||||
}
|
||||
|
||||
function displayDropdown(widget) {
|
||||
removeDropdown();
|
||||
|
||||
const dropdown = createDropdownElement();
|
||||
const listItemHide = createDropdownItem('Hide info Widget', hideWidget);
|
||||
const listItemHideAll = createDropdownItem('Hide for all of this node-type', hideWidgetForNodetype);
|
||||
|
||||
dropdown.appendChild(listItemHide);
|
||||
dropdown.appendChild(listItemHideAll);
|
||||
|
||||
const inputRect = widget.inputEl.getBoundingClientRect();
|
||||
dropdown.style.top = `${inputRect.top + inputRect.height}px`;
|
||||
dropdown.style.left = `${inputRect.left}px`;
|
||||
dropdown.style.width = `${inputRect.width}px`;
|
||||
|
||||
document.body.appendChild(dropdown);
|
||||
dropdownShouldBeRemoved = true;
|
||||
|
||||
widget.inputEl.removeEventListener('keydown', handleKeyDown);
|
||||
widget.inputEl.addEventListener('keydown', handleKeyDown);
|
||||
document.addEventListener('click', handleDocumentClick);
|
||||
}
|
||||
|
||||
function removeDropdown() {
|
||||
const dropdown = document.getElementById('hideinfo-dropdown');
|
||||
if (dropdown) {
|
||||
dropdown.remove();
|
||||
widget.inputEl.removeEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
document.removeEventListener('click', handleDocumentClick);
|
||||
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
const dropdownItems = document.querySelectorAll('.hideInfo-item');
|
||||
|
||||
if (event.keyCode === KEY_CODES.ENTER && dropdownShouldBeRemoved) {
|
||||
event.preventDefault();
|
||||
if (selectionIndex !== -1) {
|
||||
const selectedAction = dropdownItems[selectionIndex].dataset.action;
|
||||
if (selectedAction === 'HideinfoWidget') {
|
||||
hideWidget(widget, node);
|
||||
} else if (selectedAction === 'Hideforall') {
|
||||
hideWidgetForNodetype(widget, node);
|
||||
}
|
||||
removeDropdown();
|
||||
dropdownShouldBeRemoved = false;
|
||||
}
|
||||
} else if (event.keyCode === KEY_CODES.ARROW_DOWN && dropdownShouldBeRemoved) {
|
||||
event.preventDefault();
|
||||
if (selectionIndex !== -1) {
|
||||
dropdownItems[selectionIndex].classList.remove('selected');
|
||||
}
|
||||
selectionIndex = (selectionIndex + 1) % dropdownItems.length;
|
||||
dropdownItems[selectionIndex].classList.add('selected');
|
||||
} else if (event.keyCode === KEY_CODES.ARROW_UP && dropdownShouldBeRemoved) {
|
||||
event.preventDefault();
|
||||
if (selectionIndex !== -1) {
|
||||
dropdownItems[selectionIndex].classList.remove('selected');
|
||||
}
|
||||
selectionIndex = (selectionIndex - 1 + dropdownItems.length) % dropdownItems.length;
|
||||
dropdownItems[selectionIndex].classList.add('selected');
|
||||
} else if (event.keyCode === KEY_CODES.ESC && dropdownShouldBeRemoved) {
|
||||
event.preventDefault();
|
||||
removeDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
function hideWidget(widget, node) {
|
||||
node.properties['infoWidgetHidden'] = true;
|
||||
widget.type = "esayHidden";
|
||||
widget.computeSize = () => [0, WIDGET_GAP];
|
||||
node.setSize([node.size[0], node.size[1]]);
|
||||
}
|
||||
|
||||
function hideWidgetForNodetype(widget, node) {
|
||||
hideWidget(widget, node)
|
||||
const hiddenNodeTypes = JSON.parse(localStorage.getItem('hiddenWidgetNodeTypes') || "[]");
|
||||
if (!hiddenNodeTypes.includes(node.constructor.type)) {
|
||||
hiddenNodeTypes.push(node.constructor.type);
|
||||
}
|
||||
localStorage.setItem('hiddenWidgetNodeTypes', JSON.stringify(hiddenNodeTypes));
|
||||
}
|
||||
|
||||
function handleDocumentClick(event) {
|
||||
const dropdown = document.getElementById('hideinfo-dropdown');
|
||||
|
||||
// If the click was outside the dropdown and the dropdown should be removed, remove it
|
||||
if (dropdown && !dropdown.contains(event.target) && dropdownShouldBeRemoved) {
|
||||
removeDropdown();
|
||||
dropdownShouldBeRemoved = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var styleElement = document.createElement("style");
|
||||
const cssCode = `
|
||||
.easy-info_widget {
|
||||
background-color: var(--comfy-input-bg);
|
||||
color: var(--input-text);
|
||||
overflow: hidden;
|
||||
padding: 2px;
|
||||
resize: none;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
font-size: 10px;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
}
|
||||
.hideInfo-dropdown {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
background-color: #121212;
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 2px 4px rgba(255, 255, 255, .25);
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
z-index: 1000;
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.hideInfo-dropdown li {
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
font-family: system-ui;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.hideInfo-dropdown li:hover,
|
||||
.hideInfo-dropdown li.selected {
|
||||
background-color: #e5e5e5;
|
||||
border-radius: 7px;
|
||||
}
|
||||
`
|
||||
styleElement.innerHTML = cssCode
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
const InfoSymbol = Symbol();
|
||||
const InfoResizeSymbol = Symbol();
|
||||
|
||||
|
||||
|
||||
|
||||
// WIDGET FUNCTIONS
|
||||
function addInfoWidget(node, name, opts, app) {
|
||||
const INFO_W_SIZE = 50;
|
||||
|
||||
node.addProperty('infoWidgetHidden', false)
|
||||
|
||||
function computeSize(size) {
|
||||
if (node.widgets[0].last_y == null) return;
|
||||
|
||||
let y = node.widgets[0].last_y;
|
||||
|
||||
// Compute the height of all non easyInfo widgets
|
||||
let widgetHeight = 0;
|
||||
const infoWidges = [];
|
||||
for (let i = 0; i < node.widgets.length; i++) {
|
||||
const w = node.widgets[i];
|
||||
if (w.type === "easyInfo") {
|
||||
infoWidges.push(w);
|
||||
} else {
|
||||
if (w.computeSize) {
|
||||
widgetHeight += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
widgetHeight += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let infoWidgetSpace = infoWidges.length * INFO_W_SIZE; // Height for all info widgets
|
||||
|
||||
// Check if there's enough space for all widgets
|
||||
if (size[1] < y + widgetHeight + infoWidgetSpace) {
|
||||
// There isn't enough space for all the widgets, increase the size of the node
|
||||
node.size[1] = y + widgetHeight + infoWidgetSpace;
|
||||
node.graph.setDirtyCanvas(true);
|
||||
}
|
||||
|
||||
// Position each of the widgets
|
||||
for (const w of node.widgets) {
|
||||
w.y = y;
|
||||
if (w.type === "easyInfo") {
|
||||
y += INFO_W_SIZE;
|
||||
} else if (w.computeSize) {
|
||||
y += w.computeSize()[1] + 4;
|
||||
} else {
|
||||
y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const widget = {
|
||||
type: "easyInfo",
|
||||
name,
|
||||
get value() {
|
||||
return this.inputEl.value;
|
||||
},
|
||||
set value(x) {
|
||||
this.inputEl.value = x;
|
||||
},
|
||||
draw: function (ctx, _, widgetWidth, y, widgetHeight) {
|
||||
if (!this.parent.inputHeight) {
|
||||
// If we are initially offscreen when created we wont have received a resize event
|
||||
// Calculate it here instead
|
||||
computeSize(node.size);
|
||||
}
|
||||
const visible = app.canvas.ds.scale > 0.5 && this.type === "easyInfo";
|
||||
const margin = 10;
|
||||
const elRect = ctx.canvas.getBoundingClientRect();
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(elRect.width / ctx.canvas.width, elRect.height / ctx.canvas.height)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(margin, margin + y);
|
||||
|
||||
Object.assign(this.inputEl.style, {
|
||||
transformOrigin: "0 0",
|
||||
transform: transform,
|
||||
left: "0px",
|
||||
top: "0px",
|
||||
width: `${widgetWidth - (margin * 2)}px`,
|
||||
height: `${this.parent.inputHeight - (margin * 2)}px`,
|
||||
position: "absolute",
|
||||
background: (!node.color)?'':node.color,
|
||||
color: (!node.color)?'':'white',
|
||||
zIndex: app.graph._nodes.indexOf(node),
|
||||
});
|
||||
this.inputEl.hidden = !visible;
|
||||
},
|
||||
};
|
||||
widget.inputEl = document.createElement("textarea");
|
||||
widget.inputEl.className = "easy-info_widget";
|
||||
widget.inputEl.value = opts.defaultVal;
|
||||
widget.inputEl.placeholder = opts.placeholder || "";
|
||||
widget.inputEl.readOnly = true;
|
||||
widget.parent = node;
|
||||
|
||||
document.body.appendChild(widget.inputEl);
|
||||
|
||||
node.addCustomWidget(widget);
|
||||
|
||||
app.canvas.onDrawBackground = function () {
|
||||
// Draw node isnt fired once the node is off the screen
|
||||
// if it goes off screen quickly, the input may not be removed
|
||||
// this shifts it off screen so it can be moved back if the node is visible.
|
||||
for (let n in app.graph._nodes) {
|
||||
n = app.graph._nodes[n];
|
||||
for (let w in n.widgets) {
|
||||
let wid = n.widgets[w];
|
||||
if (Object.hasOwn(wid, "inputEl")) {
|
||||
wid.inputEl.style.left = -8000 + "px";
|
||||
wid.inputEl.style.position = "absolute";
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
node.onRemoved = function () {
|
||||
// When removing this node we need to remove the input from the DOM
|
||||
for (let y in this.widgets) {
|
||||
if (this.widgets[y].inputEl) {
|
||||
this.widgets[y].inputEl.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
widget.onRemove = () => {
|
||||
widget.inputEl?.remove();
|
||||
|
||||
// Restore original size handler if we are the last
|
||||
if (!--node[InfoSymbol]) {
|
||||
node.onResize = node[InfoResizeSymbol];
|
||||
delete node[InfoSymbol];
|
||||
delete node[InfoResizeSymbol];
|
||||
}
|
||||
};
|
||||
|
||||
if (node[InfoSymbol]) {
|
||||
node[InfoSymbol]++;
|
||||
} else {
|
||||
node[InfoSymbol] = 1;
|
||||
const onResize = (node[InfoResizeSymbol] = node.onResize);
|
||||
|
||||
node.onResize = function (size) {
|
||||
computeSize(size);
|
||||
|
||||
// Call original resizer handler
|
||||
if (onResize) {
|
||||
console.log(this, arguments)
|
||||
onResize.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return { widget };
|
||||
}
|
||||
|
||||
// WIDGETS
|
||||
const easyCustomWidgets = {
|
||||
INFO(node, inputName, inputData, app) {
|
||||
const defaultVal = inputData[1].default || "";
|
||||
return addInfoWidget(node, inputName, { defaultVal, ...inputData[1] }, app);
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easy.widgets",
|
||||
getCustomWidgets(app) {
|
||||
return easyCustomWidgets;
|
||||
},
|
||||
nodeCreated(node) {
|
||||
if (node.widgets) {
|
||||
// Locate info widgets
|
||||
const widgets = node.widgets.filter((n) => (n.type === "easyInfo"));
|
||||
for (const widget of widgets) {
|
||||
widget.inputEl.addEventListener('contextmenu', function(e) {
|
||||
hideInfoWidget(e, node, widget);
|
||||
});
|
||||
widget.inputEl.addEventListener('click', function(e) {
|
||||
hideInfoWidget(e, node, widget);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
const hiddenNodeTypes = JSON.parse(localStorage.getItem('hiddenWidgetNodeTypes') || "[]");
|
||||
const origOnConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function () {
|
||||
const r = origOnConfigure ? origOnConfigure.apply(this, arguments) : undefined;
|
||||
if (this.properties['infoWidgetHidden']) {
|
||||
for (let i in this.widgets) {
|
||||
if (this.widgets[i].type == "easyInfo") {
|
||||
hideInfoWidget(null, this, this.widgets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
};
|
||||
const origOnAdded = nodeType.prototype.onAdded;
|
||||
nodeType.prototype.onAdded = function () {
|
||||
const r = origOnAdded ? origOnAdded.apply(this, arguments) : undefined;
|
||||
if (hiddenNodeTypes.includes(this.type)) {
|
||||
for (let i in this.widgets) {
|
||||
if (this.widgets[i].type == "easyInfo") {
|
||||
this.properties['infoWidgetHidden'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,212 @@
|
||||
import { app } from "../../../../scripts/app.js";
|
||||
import {removeDropdown, createDropdown} from "../common/dropdown.js";
|
||||
|
||||
function generateNumList(dictionary) {
|
||||
const minimum = dictionary["min"] || 0;
|
||||
const maximum = dictionary["max"] || 0;
|
||||
const step = dictionary["step"] || 1;
|
||||
|
||||
if (step === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = [];
|
||||
let currentValue = minimum;
|
||||
|
||||
while (currentValue <= maximum) {
|
||||
if (Number.isInteger(step)) {
|
||||
result.push(Math.round(currentValue) + '; ');
|
||||
} else {
|
||||
let formattedValue = currentValue.toFixed(3);
|
||||
if(formattedValue == -0.000){
|
||||
formattedValue = '0.000';
|
||||
}
|
||||
if (!/\.\d{3}$/.test(formattedValue)) {
|
||||
formattedValue += "0";
|
||||
}
|
||||
result.push(formattedValue + "; ");
|
||||
}
|
||||
currentValue += step;
|
||||
}
|
||||
|
||||
if (maximum >= 0 && minimum >= 0) {
|
||||
//low to high
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
//high to low
|
||||
return result.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
let plotDict = {};
|
||||
let currentOptionsDict = {};
|
||||
|
||||
function getCurrentOptionLists(node, widget) {
|
||||
const nodeId = String(node.id);
|
||||
const widgetName = widget.name;
|
||||
const widgetValue = widget.value.replace(/^(loader|preSampling):\s/, '');
|
||||
|
||||
if (!currentOptionsDict[widgetName]) {
|
||||
currentOptionsDict = {...currentOptionsDict, [widgetName]: plotDict[widgetValue]};
|
||||
} else if (currentOptionsDict[widgetName] != plotDict[widgetValue]) {
|
||||
currentOptionsDict[widgetName] = plotDict[widgetValue];
|
||||
}
|
||||
}
|
||||
|
||||
function addGetSetters(node) {
|
||||
if (node.widgets)
|
||||
for (const w of node.widgets) {
|
||||
if (w.name === "x_axis" ||
|
||||
w.name === "y_axis") {
|
||||
let widgetValue = w.value;
|
||||
|
||||
// Define getters and setters for widget values
|
||||
Object.defineProperty(w, 'value', {
|
||||
|
||||
get() {
|
||||
return widgetValue;
|
||||
},
|
||||
set(newVal) {
|
||||
if (newVal !== widgetValue) {
|
||||
widgetValue = newVal;
|
||||
getCurrentOptionLists(node, w);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dropdownCreator(node) {
|
||||
if (node.widgets) {
|
||||
const widgets = node.widgets.filter(
|
||||
(n) => (n.type === "customtext" && n.dynamicPrompts !== false) || n.dynamicPrompts
|
||||
);
|
||||
|
||||
for (const w of widgets) {
|
||||
function replaceOptionSegments(selectedOption, inputSegments, cursorSegmentIndex, optionsList) {
|
||||
if (selectedOption) {
|
||||
inputSegments[cursorSegmentIndex] = selectedOption;
|
||||
}
|
||||
|
||||
return inputSegments.map(segment => verifySegment(segment, optionsList))
|
||||
.filter(item => item !== '')
|
||||
.join('');
|
||||
}
|
||||
|
||||
function verifySegment(segment, optionsList) {
|
||||
segment = cleanSegment(segment);
|
||||
|
||||
if (isInOptionsList(segment, optionsList)) {
|
||||
return segment + '; ';
|
||||
}
|
||||
|
||||
let matchedOptions = findMatchedOptions(segment, optionsList);
|
||||
|
||||
if (matchedOptions.length === 1 || matchedOptions.length === 2) {
|
||||
return matchedOptions[0];
|
||||
}
|
||||
|
||||
if (isInOptionsList(formatNumberSegment(segment), optionsList)) {
|
||||
return formatNumberSegment(segment) + '; ';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function cleanSegment(segment) {
|
||||
return segment.replace(/(\n|;| )/g, '');
|
||||
}
|
||||
|
||||
function isInOptionsList(segment, optionsList) {
|
||||
return optionsList.includes(segment + '; ');
|
||||
}
|
||||
|
||||
function findMatchedOptions(segment, optionsList) {
|
||||
return optionsList.filter(option => option.toLowerCase().includes(segment.toLowerCase()));
|
||||
}
|
||||
|
||||
function formatNumberSegment(segment) {
|
||||
if (Number(segment)) {
|
||||
return Number(segment).toFixed(3);
|
||||
}
|
||||
|
||||
if (['0', '0.', '0.0', '0.00', '00'].includes(segment)) {
|
||||
return '0.000';
|
||||
}
|
||||
return segment;
|
||||
}
|
||||
|
||||
|
||||
const onInput = function () {
|
||||
const axisWidgetName = w.name[0] + '_axis';
|
||||
let optionsList = currentOptionsDict?.[axisWidgetName] || [];
|
||||
if (optionsList.length === 0) {return}
|
||||
|
||||
const inputText = w.inputEl.value;
|
||||
const cursorPosition = w.inputEl.selectionStart;
|
||||
let inputSegments = inputText.split('; ');
|
||||
|
||||
const cursorSegmentIndex = inputText.substring(0, cursorPosition).split('; ').length - 1;
|
||||
const currentSegment = inputSegments[cursorSegmentIndex];
|
||||
const currentSegmentLower = currentSegment.replace(/\n/g, '').toLowerCase();
|
||||
const filteredOptionsList = optionsList.filter(option => option.toLowerCase().includes(currentSegmentLower)).map(option => option.replace(/; /g, ''));
|
||||
|
||||
if (filteredOptionsList.length > 0) {
|
||||
createDropdown(w.inputEl, filteredOptionsList, (selectedOption) => {
|
||||
const verifiedText = replaceOptionSegments(selectedOption, inputSegments, cursorSegmentIndex, optionsList);
|
||||
w.inputEl.value = verifiedText;
|
||||
});
|
||||
}
|
||||
else {
|
||||
removeDropdown();
|
||||
const verifiedText = replaceOptionSegments(null, inputSegments, cursorSegmentIndex, optionsList);
|
||||
w.inputEl.value = verifiedText;
|
||||
}
|
||||
};
|
||||
|
||||
w.inputEl.removeEventListener('input', onInput);
|
||||
w.inputEl.addEventListener('input', onInput);
|
||||
w.inputEl.removeEventListener('mouseup', onInput);
|
||||
w.inputEl.addEventListener('mouseup', onInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "comfy.easy.xyPlot",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "easy XYPlot") {
|
||||
plotDict = nodeData.input.hidden.plot_dict[0];
|
||||
|
||||
for (const key in plotDict) {
|
||||
const value = plotDict[key];
|
||||
if (Array.isArray(value)) {
|
||||
let updatedValues = [];
|
||||
for (const v of value) {
|
||||
updatedValues.push(v + '; ');
|
||||
}
|
||||
plotDict[key] = updatedValues;
|
||||
} else if (typeof(value) === 'object') {
|
||||
if(key == 'seed'){
|
||||
plotDict[key] = value + '; ';
|
||||
}
|
||||
else {
|
||||
plotDict[key] = generateNumList(value);
|
||||
}
|
||||
} else {
|
||||
plotDict[key] = value + '; ';
|
||||
}
|
||||
}
|
||||
plotDict["None"] = [];
|
||||
plotDict["---------------------"] = [];
|
||||
}
|
||||
},
|
||||
nodeCreated(node) {
|
||||
if (node.comfyClass === "easy XYPlot") {
|
||||
addGetSetters(node);
|
||||
dropdownCreator(node);
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user