Compare commits

...
Author SHA1 Message Date
Xiuhan Hu 90d706296c fix node small bugs 2024-01-13 22:45:51 +08:00
Hu Xiuhan a36c74714c Merge pull request #18 from talesofai/xyz
Add xyz_plot node
2024-01-13 21:02:27 +08:00
tzwm 1aa9a8335a clean unused code for xyz_plot 2024-01-13 20:40:16 +08:00
tzwm da98222269 update generated HTML for xyz plot 2024-01-13 20:25:21 +08:00
tzwm 9ff9a37a39 generate gird HTML for xyz plot result 2024-01-13 19:37:19 +08:00
tzwm 8c98bb95e1 finish basic xyz_plot generates 2024-01-13 17:27:58 +08:00
Samwise Wang bf7a45a46c Merge pull request #17 from xinlijia/main
Fix README typos and add more guidance
2024-01-12 15:30:19 +08:00
Xinli Jia faf00d91e2 Fix README typos and add more guidance 2024-01-11 22:58:14 -05:00
tzwm 5604063aaf fix missing argument for select_inputs node 2024-01-11 22:51:48 +08:00
Samwise Wang b31209b310 Merge pull request #15 from talesofai/feat/add-select-inputs
add select inputs node
2024-01-11 10:26:19 +08:00
7 changed files with 161 additions and 20 deletions
+10 -6
View File
@@ -38,9 +38,9 @@ https://github.com/talesofai/comfyui-browser/assets/828837/803ce57a-1cf2-4e1c-be
## Installation
### Comfy Manager
### ComfyUI Manager
Search for `comfyui-browser` and install it.
Install [ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager), search `comfyui-browser` in Install Custom Node and install it.
### Manually
@@ -58,26 +58,30 @@ cd custom_nodes && git clone https://github.com/tzwm/comfyui-browser.git
## Development
- Prerequisite
- Install [Node](https://nodejs.org/en/download/current)
- Framework
- Frontend: [Svelte](https://kit.svelte.dev/)
- Backend: [aiohttp](https://docs.aiohttp.org/)(the same with ComfyUI)
- Backend: [aiohttp](https://docs.aiohttp.org/)(the same as ComfyUI)
- Project Structure
```
├── __init__.py (Backend Server)
├── web (Frontend code loaded by ComfyUI)
├── build (Built by Svelte)
├── build (Built in Svelte)
└── index.js (Frontend that interact with ComfyUI)
├── svelte (Frontend in the Modal as a iframe, written by Svelte)
├── svelte (Frontend in the Modal as a iframe, written in Svelte)
```
- Build and Run
- Copy or link `comfyui-browser` to `ComfyUI/custom_nodes/`
- Start backend by `cd ComfyUI && python main.py --enable-cors-header`
- Start frontend by `cd ComfyUI/comfyui-browser/svelte && npm i && npm run dev`
- Start frontend by `cd ComfyUI/custom_nodes/comfyui-browser/svelte && npm i && npm run dev`
- Open and debug by `http://localhost:5173/?comfyUrl=http://localhost:8188`
- It will use `localhost:8188` as ComfyUI server
- `localhost:5173` is a Vite dev server
+6 -4
View File
@@ -29,9 +29,8 @@ https://www.bilibili.com/video/BV1qc411m7Gp/
## 安装方式
### Comfy Manager
搜索 `comfyui-browser` 来安装。
### ComfyUI Manager
安装[ComfyUI Manager](https://github.com/ltdrdata/ComfyUI-Manager), 在 Install Custom Node 中搜索 `comfyui-browser` 来安装。
### 手动
@@ -43,6 +42,9 @@ cd custom_nodes && git clone https://github.com/tzwm/comfyui-browser.git
## 开发
- 前置需求
- 安装[Node](https://nodejs.org/en/download/current)
- 使用的框架
- 前端: [Svelte](https://kit.svelte.dev/)
@@ -62,7 +64,7 @@ cd custom_nodes && git clone https://github.com/tzwm/comfyui-browser.git
- 复制或者链接 `comfyui-browser` 到 `ComfyUI/custom_nodes/`
- 启动服务端: `cd ComfyUI && python main.py --enable-cors-header`
- 启动前端: `cd ComfyUI/comfyui-browser/svelte && npm i && npm run dev`
- 启动前端: `cd ComfyUI/custom_nodes/comfyui-browser/svelte && npm i && npm run dev`
- 调试地址 `http://localhost:5173/?comfyUrl=http://localhost:8188`
- `localhost:8188` 是 ComfyUI server 地址
- `localhost:5173` 是 Vite dev server
+3 -1
View File
@@ -5,7 +5,7 @@ import server
from .utils import collections_path, browser_path, sources_path, download_logs_path
from .routes import sources, collections, config, files, downloads
from .nodes import select_inputs, load_image_by_url
from .nodes import select_inputs, load_image_by_url, xyz_plot
browser_app = web.Application()
browser_app.add_routes([
@@ -43,8 +43,10 @@ WEB_DIRECTORY = "web"
NODE_CLASS_MAPPINGS = {
"LoadImageByUrl //Browser": load_image_by_url.LoadImageByUrl,
"SelectInputs //Browser": select_inputs.SelectInputs,
"XyzPlot //Browser": xyz_plot.XyzPlot,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"LoadImageByUrl //Browser": "Load Image By URL",
"SelectInputs //Browser": "Select Node Inputs",
"XyzPlot //Browser": "XYZ Plot",
}
-1
View File
@@ -7,7 +7,6 @@ import torch
import folder_paths
from ..utils import log
class LoadImageByUrl:
CATEGORY = "Browser"
+6 -6
View File
@@ -15,12 +15,12 @@ class SelectInputs:
}
@classmethod
def VALIDATE_INPUTS(s, input_1, input_2, input_3, input_4):
def VALIDATE_INPUTS(s, input_1, input_2, input_3, input_4, preview):
return True
# {
# "node_id": 4,
# "node_type": "CheckpointLoaderSimple",
# "node_title": "CheckpointLoaderSimple",
# "widget_name": "ckpt_name",
# }
RETURN_TYPES = ("INPUT", "INPUT", "INPUT", "INPUT",)
@@ -33,13 +33,13 @@ class SelectInputs:
CATEGORY = "Browser"
def run(self, input_1, input_2, input_3, input_4):
def run(self, input_1, input_2, input_3, input_4, preview):
ret = ()
for input in [input_1, input_2, input_3, input_3]:
node_id, node_type, widget_name = input.split("::")
for input in [input_1, input_2, input_3, input_4]:
node_id, node_title, widget_name = input.split("::")
ret = ret + ({
"node_id": node_id[1:],
"node_type": node_type,
"node_title": node_title,
"widget_name": widget_name,
},)
+133
View File
@@ -0,0 +1,133 @@
import requests
import json
from PIL import Image
import numpy as np
import os
import copy
import pandas as pd
import folder_paths
from ..utils import SERVER_BASE_URL
class XyzPlot:
CATEGORY = "Browser"
RETURN_TYPES = ()
RETURN_NAMES = ()
FUNCTION = "run"
OUTPUT_NODE = True
# xyz_data = {
# "source_unique_id": "",
# "output_folder_name": "",
# "x_index": 0,
# "y_index": 0,
# }
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ["IMAGE", {}],
"input_x": ["INPUT", {}],
"input_y": ["INPUT", {}],
"value_x": ["STRING", {"multiline": True, "placeholder": 'X values split by semicolon such as "1girl; 1boy"'}],
"value_y": ["STRING", {"multiline": True, "placeholder": 'Y values split by semicolon such as "1girl; 1boy"'}],
"output_folder_name": ["STRING", {"default": "xyz_plot"}],
},
"hidden": {
"prompt": "PROMPT",
"unique_id": "UNIQUE_ID",
# "xyz_data": "XYZ",
},
}
@classmethod
def IS_CHANGED(s, *args):
return True
def __init__(self) -> None:
self.output_folder_name = os.path.join(folder_paths.get_output_directory(), "xyz_plot")
self.x_index = 0
self.y_index = 0
@staticmethod
def get_filename(ix, iy, i):
return f"x{ix}_y{iy}_{i}.jpeg"
@staticmethod
def get_preview_url(folder_name, filename):
return f"{SERVER_BASE_URL}/browser/files/view?folder_type=outputs&filename={filename}&folder_path={folder_name}"
def save_images(self, images):
if not os.path.exists(self.output_folder_name):
os.mkdir(self.output_folder_name)
for index, image in enumerate(images):
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
img = img.convert('RGB')
filename = self.get_filename(self.x_index, self.y_index, index)
target_path = os.path.join(self.output_folder_name, filename)
img.save(target_path, 'JPEG', quality=90)
def run(self, images, input_x, input_y, value_x, value_y, output_folder_name, prompt, unique_id):
self.output_folder_name = os.path.join(folder_paths.get_output_directory(), output_folder_name)
if 'xyz_data' in prompt[unique_id]['inputs']:
self.x_index = prompt[unique_id]['inputs']['xyz_data']['x_index']
self.y_index = prompt[unique_id]['inputs']['xyz_data']['y_index']
self.save_images(images)
return ()
batch_size = len(images)
values_x = value_x.split(";")
values_y = value_y.split(";")
new_prompt = copy.deepcopy(prompt)
ret = {}
for ix, vx in enumerate(values_x):
row = {}
for iy, vy in enumerate(values_y):
new_prompt[input_x["node_id"]]["inputs"][input_x["widget_name"]] = vx
new_prompt[input_y["node_id"]]["inputs"][input_y["widget_name"]] = vy
new_prompt[unique_id]['inputs']['xyz_data'] = {
"source_unique_id": unique_id,
"output_folder_name": output_folder_name,
"x_index": ix,
"y_index": iy,
}
data = json.dumps({
'prompt': new_prompt
}).encode('utf-8')
r = requests.post(SERVER_BASE_URL + '/prompt', data=data)
row[vy] = ""
for i in range(batch_size):
filename = self.get_filename(ix, iy, i)
preview_url = self.get_preview_url(output_folder_name, filename)
row[vy] += f'<img src="{preview_url}" width="100">'
ret[vx] = row
# To generate grid HTML
def gird_title(input):
return f"#{input['node_id']} {input['node_title']} - {input['widget_name']}"
df = pd.DataFrame(ret)
html = df.to_html(escape=False)
html = f"<h4>X: {gird_title(input_x)}</h4><h4>Y: {gird_title(input_y)}</h4>" + html
target_path = f"{self.output_folder_name}/result.html"
# Check if the directory exists
if not os.path.exists(os.path.dirname(target_path)):
try:
os.makedirs(os.path.dirname(target_path))
except Exception as e:
raise Exception(f"Failed to create directory: {e}")
with open(target_path, 'w') as f:
f.write(html)
return ()
+3 -2
View File
@@ -6,8 +6,9 @@ const SPLITTER = '::';
function getGraphInputs(graph) {
let inputs = [];
graph._nodes?.forEach(n => {
console.log(n);
n.widgets?.forEach(w => {
inputs.push([`#${n.id}`, n.type, w.name].join(SPLITTER));
inputs.push([`#${n.id}`, n.title, w.name].join(SPLITTER));
});
});
@@ -21,7 +22,7 @@ function refreshPreview(node) {
const v = w.value.split(SPLITTER);
values.push({
node_id: v[0].substring(1),
node_type: v[1],
node_title: v[1],
widget_name: v[2],
});
}