Compare commits

...
16 Commits
Author SHA1 Message Date
shadowcz007 ed61ca761a v0.2.4
Clicking on the floating window image can copy it to the clipboard.
2023-12-02 11:55:15 +08:00
shadowcz007 95ca17d816 点击悬浮窗图片可以拷贝到剪切板 2023-12-02 11:54:30 +08:00
shadowcz007 db6c721a8f 单击图片可复制到剪切板 2023-12-02 11:35:27 +08:00
shadow b5c68751aa Merge pull request #17 from shadowcz007/v0.3-psd读取分层
V0.3 psd读取分层
2023-12-02 00:42:46 +08:00
shadowcz007 7780bfd671 v0.2.3 2023-12-02 00:42:21 +08:00
shadow a56970693a Merge pull request #15 from shadowcz007/main
1
2023-12-01 23:37:02 +08:00
shadowcz007 c8b24fe84b Update Watcher.py 2023-12-01 23:00:45 +08:00
shadowcz007 798aabf333 bugfix 2023-12-01 22:10:27 +08:00
shadow d38a7aa558 Merge pull request #14 from shadowcz007/v0.3-psd读取分层
v0.2.2
2023-12-01 19:54:15 +08:00
shadowcz007 0bdf1e47a6 v0.2.2
- 本地读取节点也可以更新prompt了
2023-12-01 19:53:50 +08:00
shadowcz007 a44016e57c Update README.md 2023-12-01 18:20:03 +08:00
shadowcz007 35a9351e53 1 2023-12-01 17:51:31 +08:00
shadowcz007 c1b2112bb8 Update requirements.txt 2023-12-01 16:39:16 +08:00
shadowcz007 42a3dd261f v0.2.1 2023-12-01 13:31:00 +08:00
shadowcz007 e14487fab1 优化体验
更友好的https提示
版本更新提示
缺失节点提示
2023-12-01 13:30:19 +08:00
shadowcz007 c16df1a473 Update checkVersion.js 2023-11-30 23:50:43 +08:00
11 changed files with 663 additions and 185 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
__pycache__/
https/
nodes/config.json
nodes/config.json
workflow/my_workflow.json
+4 -3
View File
@@ -1,6 +1,5 @@
##
In progress.
!!
![screenshare](./assets/screenshare.png)
@@ -63,7 +62,7 @@ pip3 install -r requirements.txt
>LoadImagesFromLocal
> LoadImagesFromLocal
![watch](./assets/load-watch.png)
@@ -94,6 +93,8 @@ Add edges to an image.
#### Thanks:
[ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
#### discussions:
[discussions](https://github.com/shadowcz007/comfyui-mixlab-nodes/discussions)
### TODO:
- vector https://github.com/GeorgLegato/stable-diffusion-webui-vectorstudio
+78 -10
View File
@@ -5,6 +5,9 @@ import importlib.util
import sys,json
import urllib
import datetime
python = sys.executable
@@ -95,23 +98,64 @@ def create_key(key_p,crt_p):
return
def create_for_https():
# print("#####path::", current_path)
https_key_path=os.path.join(current_path, "https")
crt=os.path.join(https_key_path, "certificate.crt")
key=os.path.join(https_key_path, "private.key")
# print("##https_key_path", crt,key)
print('\033[91mhttps_key: ', crt,key)
if not os.path.exists(https_key_path):
# 使用mkdir()方法创建新目录
os.mkdir(https_key_path)
if not os.path.exists(crt):
create_key(key,crt)
print('https_key OK: ', crt,key)
return (crt,key)
# workflow
def read_workflow_json_files(folder_path):
json_files = []
for filename in os.listdir(folder_path):
if filename.endswith('.json'):
json_files.append(filename)
data = []
for file in json_files:
file_path = os.path.join(folder_path, file)
try:
with open(file_path) as json_file:
json_data = json.load(json_file)
creation_time=datetime.datetime.fromtimestamp(os.path.getctime(file_path))
numeric_timestamp = creation_time.timestamp()
file_info = {
'filename': file,
'data': json_data,
'date': numeric_timestamp
}
data.append(file_info)
except Exception as e:
print(e)
sorted_data = sorted(data, key=lambda x: x['date'], reverse=True)
return sorted_data
def get_workflows():
# print("#####path::", current_path)
workflow_path=os.path.join(current_path, "workflow")
print('workflow_path: ',workflow_path)
if not os.path.exists(workflow_path):
# 使用mkdir()方法创建新目录
os.mkdir(workflow_path)
workflows=read_workflow_json_files(workflow_path)
return workflows
def save_workflow_json(data):
workflow_path=os.path.join(current_path, "workflow/my_workflow.json")
with open(workflow_path, 'w') as file:
json.dump(data, file)
return workflow_path
# https
async def new_start(self, address, port, verbose=True, call_on_start=None):
runner = web.AppRunner(self.app, access_log=None)
@@ -152,13 +196,37 @@ routes = web.RouteTableDef()
async def mixlab_hander(request):
config=os.path.join(current_path, "nodes/config.json")
data={}
# print(config)
if os.path.exists(config):
with open(config, 'r') as f:
data = json.load(f)
# print(data)
try:
if os.path.exists(config):
with open(config, 'r') as f:
data = json.load(f)
# print(data)
except Exception as e:
print(e)
return web.json_response(data)
@routes.post('/mixlab/workflow')
async def mixlab_workflow_hander(request):
data = await request.json()
result={}
try:
if 'task' in data:
if data['task']=='save':
file_path=save_workflow_json(data['data'])
result={
'status':'success',
'file_path':file_path
}
elif data['task']=='list':
result={
'data':get_workflows(),
'status':'success',
}
except Exception as e:
print(e)
return web.json_response(result)
def new_add_routes(self):
import nodes
self.app.add_routes(routes)
@@ -210,8 +278,8 @@ NODE_CLASS_MAPPINGS = {
"VAEDecodeConsistencyDecoder":VAEDecode,
"ScreenShare":ScreenShareNode,
"FloatingVideo":FloatingVideo,
"CLIPSeg":CLIPSeg,
"CombineMasks":CombineMasks
"CLIPSeg_":CLIPSeg,
"CombineMasks_":CombineMasks
}
# 一个包含节点友好/可读的标题的字典
+70 -41
View File
@@ -1,6 +1,6 @@
import numpy as np
import torch
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence
from PIL.PngImagePlugin import PngInfo
import base64,os
from io import BytesIO
@@ -153,25 +153,48 @@ def get_not_transparent_area(image):
return (x, y, w, h)
# 读取不了分层
def load_psd(image):
layers=[]
print('load_psd',image.format)
if image.format=='PSD':
layers = [frame.copy() for frame in ImageSequence.Iterator(image)]
print('#PSD',len(layers))
else:
image = ImageOps.exif_transpose(image) #校对方向
layers.append(image)
return layers
def load_image(fp,white_bg=False):
i = Image.open(fp)
i = ImageOps.exif_transpose(i)
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
if white_bg==True:
nw = mask.unsqueeze(0).unsqueeze(-1).repeat(1, 1, 1, 3)
# 将mask的黑色部分对image进行白色处理
image[nw == 1] = 1.0
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
return (image,mask)
im = Image.open(fp)
# ims=load_psd(im)
im = ImageOps.exif_transpose(im) #校对方向
ims=[im]
images=[]
for i in ims:
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
if 'A' in i.getbands():
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
mask = 1. - torch.from_numpy(mask)
if white_bg==True:
nw = mask.unsqueeze(0).unsqueeze(-1).repeat(1, 1, 1, 3)
# 将mask的黑色部分对image进行白色处理
image[nw == 1] = 1.0
else:
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
images.append({
"image":image,
"mask":mask
})
return images
# 获取图片s
@@ -183,23 +206,27 @@ def get_images_filepath(f,white_bg=False):
for file in files:
file_path = os.path.join(root, file)
try:
(im,mask)=load_image(file_path,white_bg)
images.append({
"image":im,
"mask":mask,
"file_path":file_path
})
imgs=load_image(file_path,white_bg)
for img in imgs:
images.append({
"image":img['image'],
"mask":img['mask'],
"file_path":file_path,
"psd":len(imgs)>1
})
except:
print('非图片',file_path)
elif os.path.isfile(f):
try:
(im,mask)=load_image(f,white_bg)
images.append({
"image":im,
"mask":mask,
"file_path":f
})
imgs=load_image(f,white_bg)
for img in imgs:
images.append({
"image":img['image'],
"mask":img['mask'],
"file_path":file_path,
"psd":len(imgs)>1
})
except:
print('非图片',f)
else:
@@ -583,43 +610,45 @@ class LoadImagesFromPath:
"white_bg": (["disable","enable"],),
"newest_files": (["enable", "disable"],),
"index_variable":("INT", {
"default": -1,
"default": 0,
"min": -1, #Minimum value
"max": 2048, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"watcher":(["disable","enable"],),
"result": ("WATCHER",),
"result": ("WATCHER",),#为了激活本节点运行
"prompt": ("PROMPT",),
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ('IMAGE','MASK',)
RETURN_TYPES = ('IMAGE','MASK','STRING')
FUNCTION = "run"
CATEGORY = "Mixlab/image"
# INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,)
OUTPUT_IS_LIST = (True,True,False,)
global watcher_folder
watcher_folder=None
# 运行的函数
def run(self,file_path,white_bg,newest_files,index_variable,watcher,result):
def run(self,file_path,white_bg,newest_files,index_variable,watcher,result,prompt):
global watcher_folder
# print('###监听:',watcher_folder,watcher,file_path,result)
if watcher=='enable':
if watcher_folder==None:
watcher_folder = FolderWatcher(file_path)
if watcher_folder==None:
watcher_folder = FolderWatcher(file_path)
watcher_folder.set_folder_path(file_path)
if watcher=='enable':
# 在这里可以进行其他操作,监听会在后台持续
watcher_folder.set_folder_path(file_path)
watcher_folder.start()
else:
if watcher_folder!=None:
watcher_folder.stop()
@@ -642,8 +671,8 @@ class LoadImagesFromPath:
imgs=[imgs[index_variable]] if index_variable < len(imgs) else None
masks=[masks[index_variable]] if index_variable < len(masks) else None
return (imgs,masks,)
print('#prompt::::',prompt)
return (imgs,masks,prompt,)
# TODO 扩大选区的功能,重新输出mask
+27 -17
View File
@@ -8,25 +8,34 @@ import os
# print('Watcher:',current_directory)
def save_to_json(file_path, data):
with open(file_path, 'w') as f:
json.dump(data, f)
try:
with open(file_path, 'w') as f:
json.dump(data, f)
except Exception as e:
print(e)
def read_from_json(file_path):
with open(file_path, 'r') as f:
data = json.load(f)
data={}
try:
with open(file_path, 'r') as f:
data = json.load(f)
except Exception as e:
print(e)
return data
# read_from_json()
current_path = os.path.abspath(os.path.dirname(__file__))
config_json=os.path.join(current_path,'config.json')
print('Watcher:',config_json)
# print('Watcher:',config_json)
def read_config():
config={}
if os.path.exists(config_json):
# print('exists')
config=read_from_json(config_json)
try:
if os.path.exists(config_json):
config=read_from_json(config_json)
except Exception as e:
print(e)
return config
@@ -39,10 +48,10 @@ class FolderWatcher:
config['folder_path']=folder_path
save_to_json(config_json,config)
# self.observer = Observer()
self.observer = None
self.event_handler = self._create_event_handler()
self.status = "Not started"
self.event_type=''
self.event_type='-'
def _create_event_handler(self):
@@ -77,22 +86,23 @@ class FolderWatcher:
config=read_config()
config['folder_path']=new_folder_path
save_to_json(config_json,config)
self.event_type=''
self.event_type='-'
def start(self):
self.observer = Observer()
self.observer.schedule(self.event_handler, self.folder_path, recursive=True)
self.observer.start()
self.status = "Listening"
self.event_type=''
self.event_type='-'
print('Listening')
def stop(self):
self.observer.stop()
self.observer.join()
self.observer=None
self.status = "Stopped"
self.event_type=''
if self.observer!=None:
self.observer.stop()
self.observer.join()
self.observer=None
self.status = "Stopped"
self.event_type='-'
print('Stopped')
+3 -1
View File
@@ -1,3 +1,5 @@
numpy
pyOpenSSL
watchdog
watchdog
opencv-python-headless
matplotlib
+24 -8
View File
@@ -1,20 +1,36 @@
import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version='v0.1'
const version='v0.2.4'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
.then(data => {
const latestVersion = data.tag_name
console.log('Latest release version:', latestVersion)
console.log('Latest release version:', latestVersion )
if(latestVersion!=version){
window.alert(
`Please proceed to the official repository to download the latest version.https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/`
)
window.open(
'https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/'
)
app.ui.dialog.show(`<h4 style="font-size: 18px;">${repoName} <br>
Latest release version: ${latestVersion}</h4>
<p>Please proceed to the official repository to download the latest version.</p>
<a style=" color: #2196F3;
font-size: 18px;
font-weight: 800;
letter-spacing: 2px;
}"
href="https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/">https://github.com/shadowcz007/comfyui-mixlab-nodes/releases</a>
`)
// window.alert(
// `Please proceed to the official repository to download the latest version.https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/`
// )
// window.open(
// 'https://github.com/shadowcz007/comfyui-mixlab-nodes/releases/'
// )
}
})
.catch(error => {
+294 -46
View File
@@ -29,6 +29,36 @@ async function interrupt () {
})
}
async function clipboardWriteImage (win, url) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
// console.log(url)
const img = await createImage(url)
// console.log(img)
canvas.width = img.naturalWidth
canvas.height = img.naturalHeight
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, 0, 0)
// 将canvas转为blob
canvas.toBlob(async blob => {
const data = [
new ClipboardItem({
[blob.type]: blob
})
]
win.navigator.clipboard
.write(data)
.then(() => {
console.log('Image copied to clipboard')
})
.catch(error => {
console.error('Failed to copy image to clipboard:', error)
})
})
}
async function uploadFile (file) {
try {
const body = new FormData()
@@ -332,7 +362,6 @@ async function createBlobFromVideo (webcamVideo) {
// if (!window._mixlab_screen_imagePath) {
// window._mixlab_screen_imagePath = currentImage
// }
}
async function blobToBase64 (blob) {
@@ -403,6 +432,7 @@ const base64Df =
app.registerExtension({
name: 'Mixlab.image.ScreenShareNode',
async getCustomWidgets (app) {
console.log('#Mixlab.image.ScreenShareNode', app)
return {
CHEESE (node, inputName, inputData, app) {
// We return an object containing a field CHEESE which has a function (taking node, name, data, app)
@@ -420,8 +450,8 @@ app.registerExtension({
return window._mixlab_screen_imagePath || base64Df
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
// console.log('#Mixlab.image.ScreenShareNode',widget)
node.addCustomWidget(widget)
return widget // and returns it.
},
PROMPT (node, inputName, inputData, app) {
@@ -440,7 +470,7 @@ app.registerExtension({
return window._mixlab_screen_prompt || ''
}
}
console.log('###widget', widget)
// console.log('###widget', widget)
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
@@ -546,7 +576,7 @@ app.registerExtension({
widget.liveBtn,
widget.previewArea
)
widget.shareBtn.innerText = 'Stop Share';
widget.shareBtn.innerText = 'Stop Share'
console.log('视频已暂停')
if (window._mixlab_stopLive) {
@@ -748,6 +778,36 @@ function setArea (src) {
}
}
async function save_workflow (json) {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
const res = await fetch(`${url}/mixlab/workflow`, {
method: 'POST',
body: JSON.stringify({
data: json,
task: 'save'
})
})
return await res.json()
}
async function get_my_workflow () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
const res = await fetch(`${url}/mixlab/workflow`, {
method: 'POST',
body: JSON.stringify({
task: 'list'
})
})
let result = await res.json()
return result.data
}
app.registerExtension({
name: 'Mixlab.image.FloatingVideo',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
@@ -771,6 +831,7 @@ app.registerExtension({
widget.preview = $el('video', {
controls: true,
draggable: true,
style: {
width: '100%'
},
@@ -778,6 +839,28 @@ app.registerExtension({
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
})
// })
// const dropTarget = document.getElementById('your-drop-target-id')
// dropTarget.addEventListener('dragover', event => {
// event.preventDefault()
// })
// dropTarget.addEventListener('drop', event => {
// event.preventDefault()
// const imageUrl = event.dataTransfer.getData('text/plain')
// navigator.clipboard
// .writeText(imageUrl)
// .then(() => {
// console.log('Image URL copied to clipboard')
// })
// .catch(error => {
// console.error('Failed to copy image URL to clipboard:', error)
// })
// })
widget.canvas = $el('canvas', {
style: {
display: 'none'
@@ -788,7 +871,6 @@ app.registerExtension({
innerText: 'PictureInPicture',
style: {
display: 'pictureInPictureEnabled' in document ? 'block' : 'none',
cursor: 'pointer',
padding: '8px 0',
fontWeight: '300',
@@ -801,19 +883,44 @@ app.registerExtension({
widget.card.appendChild(widget.preview)
widget.card.appendChild(widget.canvas)
widget.preview.addEventListener('click', event => {
const imageUrl = window._mixlab_screen_result || ''
// console.log(imageUrl)
try {
if (imageUrl) clipboardWriteImage(pipWindow, imageUrl)
} catch (error) {
console.log(error)
if (imageUrl) clipboardWriteImage(window, imageUrl)
}
// pipWindow.navigator.permissions
// .query({ name: 'clipboard-write' })
// .then(result => {
// if (result.state === 'granted' || result.state === 'prompt') {
// // 执行复制操作
// } else {
// console.error('Clipboard write permission denied')
// }
// })
})
// widget.card.appendChild(widget.PictureInPicture)
widget.PictureInPicture.addEventListener('click', async () => {
if (window.location.protocol != 'https:') {
let http_workflow = app.graph.serialize()
await save_workflow(http_workflow)
window.alert(
`About to redirect to HTTPS access.https://${
`Redirecting to HTTPS access due to the requirement of the floating window. https://${
window.location.hostname
}:${~~window.location.port + 1}`
)
window.open(
`https://${window.location.hostname}:${
~~window.location.port + 1
}`
}?workflow=1`
)
}
// Open a Picture-in-Picture window.
@@ -841,7 +948,17 @@ app.registerExtension({
width: calc(100% - 24px);
margin: 12px;`
// console.log(pipWindow.document)
let inputDiv = document.createElement('div')
// inputDiv.style = ``
let infoDiv = document.createElement('div')
infoDiv.style = ` width: 100%;
height: 16px;
color: white;
margin-bottom: 4px;
font-size: 12px;
text-shadow: 1px 1px gray;`
infoDiv.id = 'info'
// Move the player to the Picture-in-Picture window.
let input = document.createElement('textarea')
input.style = `
@@ -895,8 +1012,14 @@ app.registerExtension({
} else {
input.style.display = 'none'
}
try {
pipWindow.document.querySelector('#info').innerText = ''
} catch (error) {
console.log(error)
}
})
// TODO 需要判断是否有screenshare节点,没有的话,不需要添加
let pauseBtn = document.createElement('butotn')
pauseBtn.innerText = '⏸'
pauseBtn.style = `cursor: pointer;height: 24px;margin:4px;
@@ -917,6 +1040,13 @@ app.registerExtension({
if (w) {
w.liveBtn.innerText = 'Live Run'
}
try {
pipWindow.document.querySelector('#info').innerText =
'Stop Live'
} catch (error) {
console.log(error)
}
} else {
pauseBtn.innerText = '⏸'
let node = this.graph._nodes.filter(
@@ -928,6 +1058,12 @@ app.registerExtension({
window._mixlab_stopLive = await startLive(w.liveBtn)
console.log('window._mixlab_stopLive', window._mixlab_stopLive)
}
try {
pipWindow.document.querySelector('#info').innerText = 'Live'
} catch (error) {
console.log(error)
}
}
})
@@ -939,30 +1075,92 @@ app.registerExtension({
window._mixlab_screen_prompt =
window._mixlab_screen_prompt_input || window._mixlab_screen_prompt
document.querySelector('#queue-button').click()
try {
pipWindow.document.querySelector('#info').innerText =
'Update Prompt'
} catch (error) {
console.log(error)
}
})
widget.preview.addEventListener('click', event => {
const imageUrl = window._mixlab_screen_result || ''
// console.log(imageUrl)
try {
if (imageUrl) clipboardWriteImage(pipWindow, imageUrl)
} catch (error) {
console.log(error)
if (imageUrl) clipboardWriteImage(window, imageUrl)
}
try {
pipWindow.document.querySelector('#info').innerText =
'Image copied to clipboard'
setTimeout(
() =>
(pipWindow.document.querySelector('#info').innerText = ''),
8000
)
} catch (error) {
console.log(error)
}
// pipWindow.navigator.permissions
// .query({ name: 'clipboard-write' })
// .then(result => {
// if (result.state === 'granted' || result.state === 'prompt') {
// // 执行复制操作
// } else {
// console.error('Clipboard write permission denied')
// }
// })
})
pipWindow.document.body.append(widget.preview)
pipWindow.document.body.append(div)
// console.log(pipWindow)
div.appendChild(btnDiv)
btnDiv.appendChild(btn)
btnDiv.appendChild(pauseBtn)
btnDiv.appendChild(promptFinishBtn)
div.appendChild(input)
// 输入框
div.appendChild(inputDiv)
inputDiv.appendChild(infoDiv)
inputDiv.appendChild(input)
input.addEventListener('input', () => {
window._mixlab_screen_prompt_input = input.value
try {
pipWindow.document.querySelector('#info').innerText = ''
} catch (error) {
console.log(error)
}
})
input.addEventListener('keydown', handleKeyDown)
function handleKeyDown (event) {
if (event.key === 'Enter') {
console.log('##更新Prompt')
window._mixlab_screen_prompt =
window._mixlab_screen_prompt_input ||
window._mixlab_screen_prompt
document.querySelector('#queue-button').click()
if (!event.shiftKey) {
// 回车键被按下且未同时按下Shift键,执行你的操作
event.preventDefault() // 阻止默认行为(如提交表单)
// 在这里添加你的代码
console.log('##更新Prompt')
window._mixlab_screen_prompt =
window._mixlab_screen_prompt_input ||
window._mixlab_screen_prompt
document.querySelector('#queue-button').click()
try {
pipWindow.document.querySelector('#info').innerText =
'Update Prompt'
} catch (error) {
console.log(error)
}
}
}
}
@@ -997,7 +1195,11 @@ app.registerExtension({
const videoTrack = stream.getVideoTracks()[0]
video.preview.srcObject = new MediaStream([videoTrack])
video.preview.play()
try {
video.preview.play()
} catch (error) {
console.log(error)
}
// 检查浏览器是否支持画中画模式
if ('pictureInPictureEnabled' in document) {
@@ -1030,7 +1232,7 @@ app.registerExtension({
const context = canvas.getContext('2d')
if (message?.images_) {
const base64 = message.images_[0]
window._mixlab_screen_result = `data:image/png;base64,${message.images_[0]}`
const image = new Image()
image.onload = function () {
canvas.width = image.width
@@ -1038,7 +1240,7 @@ app.registerExtension({
context.drawImage(image, 0, 0)
}
// console.log(`data:image/jpeg;base64,${base64}`)
image.src = `data:image/jpeg;base64,${base64}`
image.src = window._mixlab_screen_result
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
@@ -1376,31 +1578,77 @@ const importWorkflow = my => {
app.canvas.pasteFromClipboard()
}
function getURLParameters (url) {
var params = {}
var paramStr = url.split('?')[1]
if (paramStr) {
var paramArr = paramStr.split('&')
for (var i = 0; i < paramArr.length; i++) {
var param = paramArr[i].split('=')
var paramName = decodeURIComponent(param[0])
var paramValue = decodeURIComponent(param[1] || '')
if (paramName) {
if (params[paramName]) {
params[paramName] = Array.isArray(params[paramName])
? [...params[paramName], paramValue]
: [params[paramName], paramValue]
} else {
params[paramName] = paramValue
}
}
}
}
return params
}
// var url = "http://example.com/page?param1=value1&param2=value2&param3=value3";
// var params = getURLParameters(url);
// console.log(params);
const node = {
name: 'RandomPrompt',
async init (app) {
// Any initial setup to run as soon as the page loads
console.log('[logging]', 'extension init')
if (window.location.href.match('/?')) {
const { workflow } = getURLParameters(window.location.href)
if (workflow)
get_my_workflow().then(data => {
console.log('#get_my_workflow', data)
let my_workflow = data.filter(
d => d.filename == 'my_workflow.json'
)[0]
if (my_workflow?.data) {
// app.loadGraphData(my_workflow.data)
localStorage.setItem('workflow', JSON.stringify(my_workflow.data))
}
})
}
},
async setup (a) {
for (const node of app.graph._nodes) {
if (node.comfyClass === 'RandomPrompt') {
console.log('#setup', node)
console.log('#setup', node)
if (node.type === 'RandomPrompt') {
updateUI(node)
}
}
console.log('[logging]', 'loaded graph node: ', exportGraph(app.graph))
// console.log('[logging]', 'loaded graph node: ', exportGraph(app.graph))
},
addCustomNodeDefs (defs, app) {
console.log(
'[logging]',
'add custom node definitions',
'current nodes:',
defs
)
// console.log(
// '[logging]',
// 'add custom node definitions',
// 'current nodes:',
// defs
// )
// 在这里进行 语言切换
for (const nodeName in defs) {
if (nodeName === 'RandomPrompt') {
// defs[nodeName].category
// defs[nodeName].display_name
}
}
// for (const nodeName in defs) {
// if (nodeName === 'RandomPrompt') {
// // defs[nodeName].category
// // defs[nodeName].display_name
// }
// }
},
loadedGraphNode (node, app) {
// Fires for each node when loading/dragging/etc a workflow json or png
@@ -1409,11 +1657,11 @@ const node = {
// console.log("[logging]", "loaded graph node: ", exportGraph(node.graph));
},
async nodeCreated (node) {
if (node.comfyClass === 'RandomPrompt') {
if (node.type === 'RandomPrompt') {
updateUI(node)
}
if (node.comfyClass === 'RunWorkflow') {
if (node.type === 'RunWorkflow') {
const pw = node.widgets.filter(w => w.name === 'workflow')[0]
console.log('nodeCreated', pw)
// if (pw) {
@@ -1433,17 +1681,17 @@ const node = {
// 汉化
// app.graph._nodes // title ='123'
if (nodeData.name === 'SaveTransparentImage') {
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted?.apply?.(this, arguments)
console.log('executed', message)
const { image_path } = message
if (image_path) {
}
return r
}
}
// if (nodeData.name === 'SaveTransparentImage') {
// const onExecuted = nodeType.prototype.onExecuted
// nodeType.prototype.onExecuted = function (message) {
// const r = onExecuted?.apply?.(this, arguments)
// console.log('executed', message)
// const { image_path } = message
// if (image_path) {
// }
// return r
// }
// }
if (nodeData.name === 'WSServer') {
// Create the button widget for selecting the files
+39
View File
@@ -0,0 +1,39 @@
import { app } from '../../../scripts/app.js'
const missingNodeGithub = missingNodeTypes => {
return Array.from(
new Set(missingNodeTypes)
,n=>{
const url = `https://github.com/search?q=${n}&type=code`;
return `<li style="color: white;
background: black;
padding: 8px;
font-size: 12px;">${n}<a href="${url}" target="_blank"> 🔗</a></li>`;
})
}
app.showMissingNodesError = function (missingNodeTypes, hasAddedNodes = true) {
// console.log('###MIXLAB', missingNodeTypes, hasAddedNodes)
this.ui.dialog.show(
`When loading the graph, the following node types were not found: <ul>${ missingNodeGithub(missingNodeTypes)
.join('')}</ul>${
hasAddedNodes
? 'Nodes that have failed to load will show as red on the graph.'
: ''
}`
)
this.logging.addEntry('Comfy.App', 'warn', {
MissingNodes: missingNodeTypes
})
}
// app.ui.dialog.show = function (html) {
// console.log('###MIXLAB', html)
// if (typeof html === 'string') {
// this.textElement.innerHTML = html
// } else {
// this.textElement.replaceChildren(html)
// }
// this.element.style.display = 'flex'
// }
+47 -8
View File
@@ -14,6 +14,10 @@ async function getConfig () {
return await res.json()
}
if (!window._mixlab_screen_prompt)
window._mixlab_screen_prompt =
'beautiful scenery nature glass bottle landscape,under water'
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
@@ -57,7 +61,28 @@ app.registerExtension({
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
// // 绘制文件图标的函数
// function drawFileIcon () {
// // 清空画布
// // ctx.clearRect(0, 0, canvas.width, canvas.height)
// // 绘制文件外框
// ctx.fillStyle = '#000'
// ctx.fillRect(5, 5, 40, 40)
// // 绘制文件夹图标
// ctx.fillStyle = '#f00'
// ctx.fillRect(10, 15, 30, 20)
// // 绘制监听符号
// ctx.beginPath()
// ctx.arc(30, 35, 5, 0, 2 * Math.PI)
// ctx.fillStyle = '#00f'
// ctx.fill()
// }
// // 调用绘制函数
// drawFileIcon()
},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
@@ -69,6 +94,26 @@ app.registerExtension({
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
},
PROMPT (node, inputName, inputData, app) {
// console.log('node', inputName, inputData[0])
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
return window._mixlab_screen_prompt || ''
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
}
},
@@ -79,11 +124,7 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
// console.log(
// 'watch widtget',
// this.widgets.filter(w => w.name == 'watcher')[0]
// )
console.log('watch widtget', this.widgets)
const watcher = this.widgets.filter(w => w.name == 'watcher')[0]
@@ -111,7 +152,6 @@ app.registerExtension({
}
}
// 上次路径填充
getConfig().then(json => {
let w = this.widgets.filter(w => w.name == 'file_path')[0]
@@ -120,7 +160,6 @@ app.registerExtension({
}
// console.log(json.event_type)
window._mixlab_file_path_watcher = json.event_type
})
/*
+75 -50
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 23,
"last_link_id": 47,
"last_link_id": 48,
"nodes": [
{
"id": 11,
@@ -94,7 +94,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
882790958612696,
1088992701378297,
"randomize",
4,
1.6,
@@ -110,10 +110,10 @@
-470,
840
],
"size": [
430.924072265625,
253.48239135742188
],
"size": {
"0": 430.924072265625,
"1": 253.48239135742188
},
"flags": {},
"order": 0,
"mode": 0,
@@ -212,7 +212,7 @@
"1": 58
},
"flags": {},
"order": 9,
"order": 8,
"mode": 0,
"inputs": [
{
@@ -271,45 +271,6 @@
"control_scribble-fp16.safetensors"
]
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
678,
-77
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 32
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
39
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"superman,"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
@@ -322,7 +283,7 @@
"1": 200
},
"flags": {},
"order": 6,
"order": 5,
"mode": 0,
"inputs": [
{
@@ -361,7 +322,7 @@
"1": 126
},
"flags": {},
"order": 7,
"order": 6,
"mode": 0,
"inputs": [
{
@@ -465,7 +426,7 @@
"1": 246
},
"flags": {},
"order": 8,
"order": 7,
"mode": 0,
"inputs": [
{
@@ -512,6 +473,53 @@
1
]
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
678,
-77
],
"size": [
400,
200
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 32
},
{
"name": "text",
"type": "STRING",
"link": 48,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
39
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"superman,"
]
},
{
"id": 22,
"type": "LoadImagesFromPath",
@@ -521,7 +529,7 @@
],
"size": {
"0": 315,
"1": 202
"1": 238
},
"flags": {},
"order": 4,
@@ -542,6 +550,15 @@
"type": "MASK",
"links": null,
"shape": 6
},
{
"name": "STRING",
"type": "STRING",
"links": [
48
],
"shape": 3,
"slot_index": 2
}
],
"properties": {
@@ -813,6 +830,14 @@
18,
0,
"IMAGE"
],
[
48,
22,
2,
8,
1,
"STRING"
]
],
"groups": [