diff --git a/py/KY_VideoCompare.py b/py/KY_VideoCompare.py
index b9a82fb..8b62179 100644
--- a/py/KY_VideoCompare.py
+++ b/py/KY_VideoCompare.py
@@ -4,9 +4,38 @@ from PIL import Image
import folder_paths
import os
import json
+import urllib.parse
_CATEGORY = 'KYNode/video'
+def get_ky_url(path):
+ if not path:
+ return ""
+ filename = os.path.basename(path)
+ fullpath_encoded = urllib.parse.quote(path.replace('\\', '/'))
+ return f"/ky_utils/stream/{filename}?path={fullpath_encoded}"
+
+def resolve_path(filename, subfolder="", type_="input"):
+ try:
+ # handle special subfolder names if they come from the weird ComfyUI logic
+ if subfolder == 'input' or subfolder == 'output' or subfolder == 'temp':
+ subfolder = ""
+
+ base_dir = folder_paths.get_directory_by_type(type_)
+ if not base_dir:
+ return ""
+
+ # If subfolder is not empty, join it
+ if subfolder:
+ full_path = os.path.join(base_dir, subfolder, filename)
+ else:
+ full_path = os.path.join(base_dir, filename)
+
+ return os.path.abspath(full_path)
+ except Exception as e:
+ print(f"Error resolving path: {e}")
+ return ""
+
def process_video_object(video_obj):
"""
@@ -27,72 +56,86 @@ def process_video_object(video_obj):
file_attr = getattr(video_obj, '_VideoFromFile__file', None)
# 如果是字符串路径
if isinstance(file_attr, str):
+ # It might be a relative path or just a filename depending on how it was created
+ # Usually ComfyUI internally stores relative path or filename for input directory
+ # Let's try to resolve it.
+
+ # Check if it has input/output/temp in path to guess type
filename = os.path.basename(file_attr)
subfolder = os.path.dirname(file_attr)
- # 尝试确定文件类型
+
if 'input' in file_attr:
type_ = 'input'
elif 'output' in file_attr:
type_ = 'output'
else:
- type_ = 'input' # 默认为input
- source = f"/api/view?type={type_}&filename={filename}&subfolder={subfolder}"
- return source
+ type_ = 'input' # default
+
+ # If absolute path, use it directly
+ if os.path.isabs(file_attr) and os.path.exists(file_attr):
+ return get_ky_url(file_attr)
+
+ full_path = resolve_path(filename, subfolder, type_)
+ if os.path.exists(full_path):
+ return get_ky_url(full_path)
+
+ # fallback if resolution failed but it might be a direct path
+ return get_ky_url(file_attr)
+
# 如果是BytesIO对象,我们无法直接获取文件名
elif hasattr(file_attr, 'name'):
# BytesIO可能有name属性
- filename = os.path.basename(getattr(file_attr, 'name', 'unknown.mp4'))
- source = f"/api/view?filename={filename}&type=input"
- return source
+ # Warning: this might be a temporary file that doesn't exist on disk in a standard location
+ filename = getattr(file_attr, 'name', 'unknown.mp4')
+ if os.path.isabs(filename):
+ return get_ky_url(filename)
+ # Try to find it in input
+ full_path = resolve_path(os.path.basename(filename), "", "input")
+ return get_ky_url(full_path)
else:
# 其他情况,转换为字符串
source = str(file_attr)
+ if os.path.exists(source):
+ return get_ky_url(source)
return source
- # 处理其他可能的属性
- if hasattr(video_obj, 'filename'):
- filename = getattr(video_obj, 'filename', '')
- subfolder = getattr(video_obj, 'subfolder', '')
- type_ = getattr(video_obj, 'type', 'input')
- source = f"/api/view?type={type_}&filename={filename}&subfolder={subfolder}"
- return source
+ # helper to extract fields
+ def get_info(obj):
+ fname = getattr(obj, 'filename', None) or obj.get('filename') if isinstance(obj, dict) else None
+ sub = getattr(obj, 'subfolder', None) or obj.get('subfolder', "") if isinstance(obj, dict) else ""
+ typ = getattr(obj, 'type', None) or obj.get('type', "input") if isinstance(obj, dict) else "input"
+ path = getattr(obj, 'path', None) or obj.get('path', None) if isinstance(obj, dict) else None
+ return fname, sub, typ, path
+
+ fname, sub, typ, path = get_info(video_obj)
+
+ if path:
+ return get_ky_url(path)
- if isinstance(video_obj, dict):
- # 检查常见的键
- if "filename" in video_obj:
- filename = video_obj["filename"]
- subfolder = video_obj.get("subfolder", "")
- type_ = video_obj.get("type", "input")
- source = f"/api/view?type={type_}&filename={filename}&subfolder={subfolder}"
- return source
- elif "path" in video_obj:
- path = video_obj["path"]
- filename = os.path.basename(path)
- subfolder = os.path.dirname(path)
- source = f"/api/view?filename={filename}&subfolder={subfolder}&type=input"
- return source
- else:
- # 尝试其他可能的键
- source = str(video_obj)
- return source
- elif isinstance(video_obj, str):
+ if fname:
+ full_path = resolve_path(fname, sub, typ)
+ return get_ky_url(full_path)
+
+ if isinstance(video_obj, str):
if os.path.exists(video_obj):
- filename = os.path.basename(video_obj)
- subfolder = os.path.dirname(video_obj)
- source = f"/api/view?filename={filename}&subfolder={subfolder}&type=input"
- return source
+ return get_ky_url(video_obj)
else:
- source = video_obj
- return source
- else:
- # 其他类型,尝试转换为字符串并检查是否包含有用信息
- source = str(video_obj)
-
- # 如果字符串中包含filename信息,尝试提取
- if 'filename=' in source and 'type=' in source:
- return source
- else:
- return source
+ # check if it looks like a relative path in input
+ full_path = resolve_path(os.path.basename(video_obj), os.path.dirname(video_obj), "input")
+ if os.path.exists(full_path):
+ return get_ky_url(full_path)
+ return video_obj
+
+ # fallback
+ source = str(video_obj)
+ # check if it already has filename/type info from some string representation
+ if 'filename=' in source and 'type=' in source:
+ # It's an API View URL probably, we can't easily convert it back without parsing
+ # But if the user wants to use ky_utils, we should try.
+ # But for safety, maybe just return it if we can't parse?
+ # Let's leave it for now.
+ return source
+ return source
class VideoCompareNode:
@@ -241,39 +284,11 @@ class KY_ToVideoUrl:
if match:
video_path = match.group(1)
- # 如果是文件路径,转换为API视图URL
if os.path.exists(video_path):
- filename = os.path.basename(video_path)
- # 获取相对于ComfyUI目录的子目录
- try:
- # 获取ComfyUI根目录
- comfyui_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- # 计算相对路径
- relative_path = os.path.relpath(video_path, comfyui_root)
- subfolder = os.path.dirname(relative_path).replace('\\', '/')
- # 从路径中确定type
- path_parts = relative_path.split(os.sep)
- if 'temp' in path_parts:
- type_ = 'temp'
- elif 'input' in path_parts:
- type_ = 'input'
- elif 'output' in path_parts:
- type_ = 'output'
- else:
- type_ = 'input' # 默认值
- except:
- # 如果计算相对路径失败,使用原来的逻辑
- subfolder = os.path.dirname(video_path).replace('\\', '/')
- type_ = 'input'
- if subfolder == 'input' or subfolder == 'output' or subfolder == 'temp':
- subfolder = ''
- # 构建完整的URL参数
- import urllib.parse
- fullpath_encoded = urllib.parse.quote(video_path.replace('\\', '/'))
- return f"/api/view?filename={filename}&subfolder={subfolder}&type={type_}&format=video%2Fh264-mp4&fullpath={fullpath_encoded}"
+ return get_ky_url(video_path)
- # 如果已经是API视图URL,直接返回
- if '/api/view' in s and ('filename=' in s or 'type=' in s):
+ # 如果已经是API视图URL,尝试转换 (Optional, but good for compatibility if input is already a view url)
+ if '/ky_utils/stream' in s:
return s
# 尝试从JSON字符串中提取信息
@@ -281,47 +296,24 @@ class KY_ToVideoUrl:
try:
data = json.loads(s)
if isinstance(data, dict):
- if "filename" in data:
- filename = data["filename"]
- subfolder = data.get("subfolder", "")
- type_ = data.get("type", "input")
- return f"/api/view?type={type_}&filename={filename}&subfolder={subfolder}"
- elif "path" in data:
- path = data["path"]
- filename = os.path.basename(path)
- subfolder = os.path.dirname(path)
- return f"/api/view?filename={filename}&subfolder={subfolder}&type=input"
+ # try to get absolute path
+ fname = data.get("filename")
+ sub = data.get("subfolder", "")
+ typ = data.get("type", "input")
+ path = data.get("path")
+
+ if path and os.path.exists(path):
+ return get_ky_url(path)
+ if fname:
+ full_path = resolve_path(fname, sub, typ)
+ if os.path.exists(full_path):
+ return get_ky_url(full_path)
except:
pass
- # 如果是文件路径,转换为API视图URL
+ # 如果是文件路径
if os.path.exists(s):
- filename = os.path.basename(s)
- try:
- # 获取ComfyUI根目录
- comfyui_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- # 计算相对路径
- relative_path = os.path.relpath(s, comfyui_root)
- subfolder = os.path.dirname(relative_path).replace('\\', '/')
- # 从路径中确定type
- path_parts = relative_path.split(os.sep)
- if 'temp' in path_parts:
- type_ = 'temp'
- elif 'input' in path_parts:
- type_ = 'input'
- elif 'output' in path_parts:
- type_ = 'output'
- else:
- type_ = 'input' # 默认值
- except:
- # 如果计算相对路径失败,使用原来的逻辑
- subfolder = os.path.dirname(s).replace('\\', '/')
- type_ = 'input'
-
- # 构建完整的URL参数
- import urllib.parse
- fullpath_encoded = urllib.parse.quote(s.replace('\\', '/'))
- return f"/api/view?filename={filename}&subfolder={subfolder}&type={type_}&format=video%2Fh264-mp4&fullpath={fullpath_encoded}"
+ return get_ky_url(s)
# 否则返回原字符串
return s
@@ -394,40 +386,42 @@ def save_image_to_output(img, name_prefix="img"):
Image.fromarray(_to_uint8_rgb(arr)).save(fullpath)
else:
return ""
- return f"/api/view?filename={filename}&type=output&subfolder={subfolder}"
+ return get_ky_url(fullpath)
def process_image_object(image_obj):
if image_obj is None:
return ""
- # if has typical attributes
- if hasattr(image_obj, 'filename'):
- filename = getattr(image_obj, 'filename', '')
- subfolder = getattr(image_obj, 'subfolder', '')
- type_ = getattr(image_obj, 'type', 'input')
- return f"/api/view?type={type_}&filename={filename}&subfolder={subfolder}"
+
+ # helper to check dict
+ fname = getattr(image_obj, 'filename', None) or image_obj.get('filename') if isinstance(image_obj, dict) else None
+ sub = getattr(image_obj, 'subfolder', None) or image_obj.get('subfolder', "") if isinstance(image_obj, dict) else ""
+ typ = getattr(image_obj, 'type', None) or image_obj.get('type', "input") if isinstance(image_obj, dict) else "input"
+ path = getattr(image_obj, 'path', None) or image_obj.get('path', None) if isinstance(image_obj, dict) else None
+
+ if path:
+ return get_ky_url(path)
+
+ if fname:
+ full_path = resolve_path(fname, sub, typ)
+ return get_ky_url(full_path)
+
if isinstance(image_obj, dict):
- if "filename" in image_obj:
- filename = image_obj["filename"]
- subfolder = image_obj.get("subfolder", "")
- type_ = image_obj.get("type", "input")
- return f"/api/view?type={type_}&filename={filename}&subfolder={subfolder}"
- if "path" in image_obj:
- path = image_obj["path"]
- filename = os.path.basename(path)
- subfolder = os.path.dirname(path)
- return f"/api/view?filename={filename}&subfolder={subfolder}&type=input"
if "image" in image_obj:
return save_image_to_output(image_obj["image"])
if "images" in image_obj:
imgs = image_obj["images"]
if isinstance(imgs, (list, tuple)) and imgs:
return save_image_to_output(imgs[0])
+
if isinstance(image_obj, str):
if os.path.exists(image_obj):
- filename = os.path.basename(image_obj)
- subfolder = os.path.dirname(image_obj)
- return f"/api/view?filename={filename}&subfolder={subfolder}&type=input"
+ return get_ky_url(image_obj)
+ # Try resolving as input file
+ full_path = resolve_path(os.path.basename(image_obj), os.path.dirname(image_obj), "input")
+ if os.path.exists(full_path):
+ return get_ky_url(full_path)
return image_obj
+
# save arbitrary object to output and return view url
return save_image_to_output(image_obj)
diff --git a/web/python-editor.js b/web/python-editor.js
index 0b9fd3a..e40bfe0 100644
--- a/web/python-editor.js
+++ b/web/python-editor.js
@@ -1,5 +1,4 @@
import { app } from "../../scripts/app.js";
-import { api } from "../../scripts/api.js";
// Constants
const MAX_CHAR_VARNAME = 50;
@@ -319,7 +318,8 @@ app.registerExtension({
name: "KYNode.KY_Eval_Python",
async setup() {
// 🔥 监听后端事件
- app.socket.on("python_editor_error", (data) => {
+ if (app.socket) {
+ app.socket.on("python_editor_error", (data) => {
console.log("收到错误事件:", data);
// 获取当前所有节点,找到我们自定义的那个
@@ -333,6 +333,9 @@ app.registerExtension({
}
}
});
+ } else {
+ console.warn("[KY_Eval_Python] app.socket undefined during setup");
+ }
},
getCustomWidgets(app) {
return {
diff --git a/web/python-editor.js.bak b/web/python-editor.js.bak
deleted file mode 100644
index 743d13d..0000000
--- a/web/python-editor.js.bak
+++ /dev/null
@@ -1,500 +0,0 @@
-import { app } from "../../scripts/app.js";
-import { api } from "../../scripts/api.js";
-
-// Constants
-const MAX_CHAR_VARNAME = 50;
-const LIST_THEMES = ["monokai"];
-const varTypes = [
- "int",
- "boolean",
- "string",
- "float",
- "json",
- "list",
- "dict",
-];
-const typeMap = {
- int: 'int',
- boolean: 'bool',
- string: 'str',
- float: 'float',
- json: 'json',
- list: 'list',
- dict: 'dict',
-}
-const DEFAULT_TEMPLATES = {
- py: `import re, json, os, traceback
-from time import strftime
-def runCode():
- nowDataTime = strftime("%Y-%m-%d %H:%M:%S")
- return f"Hello ComfyUI with us today {nowDataTime}!"
-r0 = runCode()
-
-`,
-};
-
-function getPostition(node, ctx, w_width, y, n_height) {
- const margin = 5;
-
- const rect = ctx.canvas.getBoundingClientRect();
- const transform = new DOMMatrix()
- .scaleSelf(rect.width / ctx.canvas.width, rect.height / ctx.canvas.height)
- .multiplySelf(ctx.getTransform())
- .translateSelf(margin, margin + y);
- const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
-
- return {
- transformOrigin: "0 0",
- transform: scale,
- left: `${transform.a + transform.e + rect.left}px`,
- top: `${transform.d + transform.f + rect.top}px`,
- maxWidth: `${w_width - margin * 2}px`,
- maxHeight: `${n_height - margin * 2 - y - 15}px`,
- width: `${w_width - margin * 2}px`,
- height: "90%",
- position: "absolute",
- scrollbarColor: "var(--descrip-text) var(--bg-color)",
- scrollbarWidth: "thin",
- zIndex: app.graph._nodes.indexOf(node),
- };
-}
-
-function findWidget(node, value, attr = "name", func = "find") {
- return node?.widgets
- ? node.widgets[func]((w) =>
- Array.isArray(value) ? value.includes(w[attr]) : w[attr] === value
- )
- : null;
-}
-
-// Create editor code
-function codeEditor(node, inputName, inputData) {
- const widget = {
- type: "pycode",
- name: inputName,
- options: { hideOnZoom: true },
- value:
- inputData[1]?.default ||
- `def my(a, b=1):
- return a * b
-
-r0 = str(my(23, 9))`,
- draw(ctx, node, widget_width, y, widget_height) {
- const hidden =
- node.flags?.collapsed ||
- (!!widget.options.hideOnZoom && app.canvas.ds.scale < 0.5) ||
- widget.type === "converted-widget" ||
- widget.type === "hidden";
-
- widget.codeElement.hidden = hidden;
-
- if (hidden) {
- widget.options.onHide?.(widget);
- return;
- }
-
- Object.assign(
- this.codeElement.style,
- getPostition(node, ctx, widget_width, y, node.size[1])
- );
- },
- computeSize(...args) {
- return [500, 250];
- },
- };
-
- // 创建一个标准的 textarea 元素
- widget.codeElement = document.createElement('div');
- widget.codeElement.className = 'python-editor-container';
- widget.codeElement.style.width = '100%';
- widget.codeElement.style.height = '100%';
- widget.codeElement.style.position = 'relative';
-
- // 创建 textarea 作为编辑器
- const textarea = document.createElement('textarea');
- textarea.value = widget.value;
- textarea.style.width = '100%';
- textarea.style.height = '80%';
- textarea.style.fontFamily = 'cursive';
- textarea.style.fontSize = '1.1em';
- textarea.style.boxSizing = 'border-box';
- textarea.style.backgroundColor = '#000';
- textarea.style.color = '#2bb356ff';
- textarea.style.resize = 'none';
- textarea.style.wordBreak = 'break-word';
- textarea.wrap = 'soft';
- textarea.spellcheck = false;
-
- // 创建错误显示区域
- const errorDiv = document.createElement('div');
- errorDiv.className = 'error-message';
- errorDiv.style.background = 'rgba(255, 0, 0, 0.1)';
- errorDiv.style.color = '#ff4444';
- errorDiv.style.padding = '4px';
- errorDiv.style.marginTop = '4px';
- errorDiv.style.borderRadius = '4px';
- errorDiv.style.fontFamily = 'monospace';
- errorDiv.style.fontSize = '12px';
- errorDiv.style.display = 'none';
-
- // 创建输出区域
- const outputDiv = document.createElement('div');
- outputDiv.style.background = '#2a2a2a';
- outputDiv.style.color = '#ffffff';
- outputDiv.style.padding = '8px';
- outputDiv.style.marginTop = '4px';
- outputDiv.style.borderRadius = '4px';
- outputDiv.style.fontFamily = 'monospace';
- outputDiv.style.fontSize = '12px';
- outputDiv.style.height = '20%';
- outputDiv.style.overflowY = 'auto';
-
- // 将元素添加到容器
- widget.codeElement.appendChild(textarea);
- widget.codeElement.appendChild(errorDiv);
- widget.codeElement.appendChild(outputDiv);
-
- // 设置编辑器引用
- widget.editor = {
- getValue: () => textarea.value,
- setValue: (value) => {
- textarea.value = value;
- },
- clearOutput: () => {
- outputDiv.innerHTML = '';
- },
- showError: (message) => {
- errorDiv.textContent = message;
- errorDiv.style.display = 'block';
- setTimeout(() => {
- errorDiv.style.display = 'none';
- }, 5000);
- },
- addOutput: (text) => {
- const outputLine = document.createElement('div');
- outputLine.textContent = text;
- outputDiv.appendChild(outputLine);
- outputDiv.scrollTop = outputDiv.scrollHeight;
- }
- };
-
- // 添加事件监听
- textarea.addEventListener('input', () => {
- widget.value = textarea.value;
- if (node) {
- node.setDirtyCanvas(true);
- }
- });
-
- // 支持Tab键缩进
- textarea.addEventListener('keydown', (e) => {
- if (e.key === 'Tab') {
- e.preventDefault();
- const start = textarea.selectionStart;
- const end = textarea.selectionEnd;
-
- // 在光标位置插入4个空格
- textarea.value = textarea.value.substring(0, start) + ' ' + textarea.value.substring(end);
-
- // 将光标位置移动到插入空格后的位置
- textarea.selectionStart = textarea.selectionEnd = start + 4;
-
- // 触发 input 事件以更新 widget.value
- textarea.dispatchEvent(new Event('input'));
- }
- });
-
- widget.codeElement.hidden = true;
- document.body.appendChild(widget.codeElement);
-
- const collapse = node.collapse;
- node.collapse = function () {
- collapse.apply(this, arguments);
- if (this.flags?.collapsed) {
- widget.codeElement.hidden = true;
- } else {
- if (this.flags?.collapsed === false) {
- widget.codeElement.hidden = false;
- }
- }
- };
-
- return widget;
-}
-
-
-// Save data to workflow forced!
-function saveValue() {
- app?.extensionManager?.workflow?.activeWorkflow?.changeTracker?.checkState();
-}
-
-// Register extensions
-app.registerExtension({
- name: "KYNode.KY_Eval_Python",
- getCustomWidgets(app) {
- return {
- PYCODE: (node, inputName, inputData, app) => {
- const widget = codeEditor(node, inputName, inputData);
-
-
-
- const varTypeList = node.addWidget(
- "combo",
- "select_type",
- "string",
- (v) => {
- // widget.editor.setTheme(`ace/theme/${varTypeList.value}`);
- },
- {
- values: varTypes,
- serialize: false,
- }
- );
-
- // 6. 使用 addDOMWidget 将容器添加到节点上
- // - 第一个参数是 widget 的名称,在节点内部需要是唯一的。
- // - 第二个参数是 widget 的类型,对于自定义 DOM 元素,通常是 "div"。
- // - 第三个参数是您创建的 DOM 元素。
- // - 第四个参数是一个选项对象,可以用来配置 widget。
- // node.addDOMWidget("rowOfButtons", "div", container, {
- // });
- node.addWidget(
- "button",
- "Add Input variable",
- "add_input_variable",
- async () => {
- // Input name variable and check
- let nameInput = node?.inputs?.length
- ? `p${node.inputs.length - 1}`
- : "p0";
-
- const currentWidth = node.size[0];
- let tp = varTypeList.value
- nameInput = nameInput + '_' + typeMap[tp]
- node.addInput(nameInput, "*");
- node.setSize([currentWidth, node.size[1]]);
- let cv = widget.editor.getValue();
- if(tp == 'json') {
- cv = cv + '\n' + nameInput + ' = json.loads('+ nameInput + ')'
- } else if(tp == 'list') {
- cv = cv + '\nfor i in '+ nameInput +':\n print(i)'
- } else if(tp == 'dict') {
- cv = cv + '\nval = ' + nameInput + '["key"]'
- } else {
- cv = cv + '\n' + nameInput + ' = ' + typeMap[tp] + '('+ nameInput + ')'
- }
- widget.editor.setValue(cv)
- saveValue();
- }
- );
-
- node.addWidget(
- "button",
- "Add Output variable",
- "add_output_variable",
- async () => {
- const currentWidth = node.size[0];
- // Output name variable
- let nameOutput = node?.outputs?.length
- ? `r${node.outputs.length}`
- : "r0";
- let tp = varTypeList.value
- nameOutput = nameOutput + '_' + typeMap[tp]
- node.addOutput(nameOutput, tp);
- node.setSize([currentWidth, node.size[1]]);
- let cv = widget.editor.getValue();
- if(tp == 'json') {
- cv = cv + '\n' + nameOutput + ' = json.dumps('+ nameOutput + ')'
- } else if(tp == 'list') {
- cv = cv + '\n' + nameOutput + ' = []'
- } else if(tp == 'dict') {
- cv = cv + '\n' + nameOutput + ' = {}'
- } else {
- cv = cv + '\n' + nameOutput + ' = ' + typeMap[tp] + '('+ nameOutput + ')'
- }
- widget.editor.setValue(cv)
- saveValue();
- }
- );
-
- node.onRemoved = function () {
- for (const w of node?.widgets) {
- if (w?.codeElement) w.codeElement.remove();
- }
- };
-
- node.addCustomWidget(widget);
-
- return widget;
- },
- };
- },
-
- async beforeRegisterNodeDef(nodeType, nodeData, app) {
- // --- IDENode
- if (nodeData.name === "KY_Eval_Python") {
- // Node Created
- const onNodeCreated = nodeType.prototype.onNodeCreated;
- nodeType.prototype.onNodeCreated = async function () {
- const ret = onNodeCreated
- ? onNodeCreated.apply(this, arguments)
- : undefined;
-
- const node_title = await this.getTitle();
- const nodeName = `${nodeData.name}_${this.id}`;
-
- console.log(`Create ${nodeData.name}: ${nodeName}`);
- this.name = nodeName;
-
- // Create default inputs, when first create node
- if (this?.inputs?.length<2) {
- ["p0_str"].forEach((inputName) => {
- const currentWidth = this.size[0];
- this.addInput(inputName, "*");
- this.setSize([currentWidth, this.size[1]]);
- });
- }
-
- // const widgetEditor = findWidget(this, "pycode", "type");
-
- this.setSize([530, this.size[1]]);
-
- return ret;
- };
-
- const onDrawForeground = nodeType.prototype.onDrawForeground;
- nodeType.prototype.onDrawForeground = function (ctx) {
- const r = onDrawForeground?.apply?.(this, arguments);
-
- // if (this.flags?.collapsed) return r;
-
- if (this?.outputs?.length) {
- for (let o = 0; o < this.outputs.length; o++) {
- const { name, type } = this.outputs[o];
- const colorType = LGraphCanvas.link_type_colors[type.toUpperCase()];
- const nameSize = ctx.measureText(name);
- const typeSize = ctx.measureText(
- `[${type === "*" ? "any" : type.toLowerCase()}]`
- );
-
- ctx.fillStyle = colorType === "" ? "#AAA" : colorType;
- ctx.font = "12px Arial, sans-serif";
- ctx.textAlign = "right";
- ctx.fillText(
- `[${type === "*" ? "any" : type.toLowerCase()}]`,
- this.size[0] - nameSize.width - typeSize.width,
- o * 20 + 19
- );
- }
- }
-
- if (this?.inputs?.length) {
- const not_showing = ["select_type", "pycode"];
- for (let i = 1; i < this.inputs.length; i++) {
- const { name, type } = this.inputs[i];
- if (not_showing.includes(name)) continue;
- const colorType = LGraphCanvas.link_type_colors[type.toUpperCase()];
- const nameSize = ctx.measureText(name);
-
- ctx.fillStyle = !colorType || colorType === "" ? "#AAA" : colorType;
- ctx.font = "12px Arial, sans-serif";
- ctx.textAlign = "left";
- ctx.fillText(
- `[${type === "*" ? "any" : type.toLowerCase()}]`,
- nameSize.width + 25,
- i * 20
- );
- }
- }
- return r;
- };
-
- // Node Configure
- const onConfigure = nodeType.prototype.onConfigure;
- nodeType.prototype.onConfigure = function (node) {
- onConfigure?.apply(this, arguments);
- if (node?.widgets_values?.length) {
- const widget_code_id = findWidget(
- this,
- "pycode",
- "type",
- "findIndex"
- );
- const widget_theme_id = findWidget(
- this,
- "varTypeList",
- "name",
- "findIndex"
- );
- const widget_language_id = findWidget(
- this,
- "language",
- "name",
- "findIndex"
- );
-
- const editor = this.widgets[widget_code_id]?.editor;
-
- if (editor) {
- // 适配 textarea 编辑器
- if (editor.setValue) {
- editor.setValue(this.widgets_values[widget_code_id]);
- }
- }
- }
- };
-
- // ExtraMenuOptions
- const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
- nodeType.prototype.getExtraMenuOptions = function (_, options) {
- getExtraMenuOptions?.apply(this, arguments);
-
- const past_index = options.length - 1;
- const past = options[past_index];
-
- if (!!past) {
- // Inputs remove
- for (const input_idx in this.inputs) {
- const input = this.inputs[input_idx];
-
- if (["language", "select_type"].includes(input.name)) continue;
-
- options.splice(past_index + 1, 0, {
- content: `Remove Input ${input.name}`,
- callback: (e) => {
- const currentWidth = this.size[0];
- if (input.link) {
- app.graph.removeLink(input.link);
- }
- this.removeInput(input_idx);
- this.setSize([80, this.size[1]]);
- saveValue();
- },
- });
- }
-
- // Output remove
- for (const output_idx in this.outputs) {
- const output = this.outputs[output_idx];
-
- if (output.name === "r0") continue;
-
- options.splice(past_index + 1, 0, {
- content: `Remove Output ${output.name}`,
- callback: (e) => {
- const currentWidth = this.size[0];
- if (output.link) {
- app.graph.removeLink(output.link);
- }
- this.removeOutput(output_idx);
- this.setSize([currentWidth, this.size[1]]);
- saveValue();
- },
- });
- }
- }
- };
- // end - ExtraMenuOptions
- }
- },
-});
\ No newline at end of file
diff --git a/web/video_compare.html b/web/video_compare.html
index 6cffd90..be77b84 100644
--- a/web/video_compare.html
+++ b/web/video_compare.html
@@ -1,145 +1,241 @@
-