diff --git a/.gitignore b/.gitignore index 68bc17f..2dc53ca 100644 --- a/.gitignore +++ b/.gitignore @@ -157,4 +157,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/README.md b/README.md index 3737df7..0f0e189 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # comfyui-easyapi-nodes 针对api接口开发补充的一些自定义节点和功能。 -转成base64的节点都是输出节点,websocket消息中会包含base64Images和imageType属性(具体格式请查看ImageNode.py中的ImageToBase64Advanced类源代码,或者自己搭建简单流程运行在浏览器开发者工具-->网络中查看) +转成base64的节点都是输出节点,websocket消息中会包含base64Images和base64Type属性(具体格式请查看ImageNode.py中的ImageToBase64Advanced类源代码,或者自己搭建简单流程运行在浏览器开发者工具-->网络中查看) ## 节点 | 名称 | 说明 | @@ -19,4 +19,9 @@ 支持保存api格式workflow时,把LoadImage替换成Base64ToImage节点,把PreviewImage和SaveImage替换成ImageToBase64节点 - ![save api extended](docs/menu.gif) \ No newline at end of file + ![save api extended](docs/menu.gif) +- Setting中支持历史记录最大条数修改 + + 图片使用base64时,数据存在内存中,默认最大历史记录条数是10000,为防止内存溢出,所以新增此配置项。 + + 配置路径:Setting -> Maximum History Size diff --git a/__init__.py b/__init__.py index 0027f08..afc0c33 100644 --- a/__init__.py +++ b/__init__.py @@ -2,17 +2,21 @@ import glob import importlib.util import sys import os +from .easyapi import api, logScript extension_folder = os.path.dirname(os.path.realpath(__file__)) NODE_CLASS_MAPPINGS = {} NODE_DISPLAY_NAME_MAPPINGS = {} -py = os.path.join(extension_folder, 'easyapi') +pyPath = os.path.join(extension_folder, 'easyapi') sys.path.append(extension_folder) +logScript.log_wrap() +api.init() + def loadCustomNodes(): - files = glob.glob(os.path.join(py, "*Node.py"), recursive=True) + files = glob.glob(os.path.join(pyPath, "*Node.py"), recursive=True) for file in files: name = os.path.splitext(file)[0] spec = importlib.util.spec_from_file_location(name, file) diff --git a/easyapi/api.py b/easyapi/api.py new file mode 100644 index 0000000..5b4207a --- /dev/null +++ b/easyapi/api.py @@ -0,0 +1,68 @@ +import os +import json +from server import PromptServer +from aiohttp import web +import execution + +extension_folder = os.path.dirname(os.path.realpath(__file__)) + + +def reset_history_size(max_size=execution.MAXIMUM_HISTORY_SIZE): + configDataFilePath = os.path.join(extension_folder, 'config') + if not os.path.exists(configDataFilePath): + os.mkdir(configDataFilePath) + configFile = os.path.join(configDataFilePath, "easyapi.json") + with open(configFile, 'w+', encoding="utf-8") as file: + json.dump({"history_max_size": max_size}, file, indent=2) + else: + configFile = os.path.join(configDataFilePath, "easyapi.json") + if not os.path.exists(configFile): + with open(configFile, 'w+', encoding="utf-8") as file: + json.dump({"history_max_size": max_size}, file, indent=2) + else: + with open(configFile, 'r+', encoding="UTF-8") as file: + data = json.load(file) + data['history_max_size'] = max_size + + with open(configFile, 'w+', encoding="UTF-8") as file: + json.dump(data, file, indent=2) + + +def register_routes(): + @PromptServer.instance.routes.post("/easyapi/history/size") + async def set_history_size(request): + json_data = await request.json() + size = json_data["maxSize"] + if size is not None: + promptQueue = PromptServer.instance.prompt_queue + with promptQueue.mutex: + maxSize = int(size) + execution.MAXIMUM_HISTORY_SIZE = maxSize + history = promptQueue.history + end = len(history) - maxSize + i = 0 + for key in list(history.keys()): + if i >= end: + break + history.pop(key) + i = i + 1 + reset_history_size(maxSize) + return web.Response(status=200) + + return web.Response(status=400) + + @PromptServer.instance.routes.get("/easyapi/history/maxSize") + async def get_history_size(request): + maxSize = execution.MAXIMUM_HISTORY_SIZE + with open(os.path.join(extension_folder, 'config/easyapi.json'), 'r', encoding="UTF-8") as file: + data = json.load(file) + if data['history_max_size'] is not None: + maxSize = data['history_max_size'] + + return web.json_response({"maxSize": maxSize}) + + +def init(): + reset_history_size() + register_routes() + diff --git a/easyapi/logScript.py b/easyapi/logScript.py new file mode 100644 index 0000000..04f56da --- /dev/null +++ b/easyapi/logScript.py @@ -0,0 +1,43 @@ +import sys + +from datetime import datetime as dt + +old_stdout = sys.stdout +old_stderr = sys.stderr + + +class StdTimeFilter: + def __init__(self, is_stdout): + self.is_stdout = is_stdout + self.newline = True + self.encoding = "utf-8" + + def write(self, message): + if message == '\n': + if self.is_stdout: + old_stdout.write(message) + else: + old_stderr.write(message) + self.newline = True + elif self.newline: + if self.is_stdout: + old_stdout.write('%s %s' % (str(dt.now()), message)) + else: + old_stderr.write('%s %s' % (str(dt.now()), message)) + self.newline = False + else: + if self.is_stdout: + old_stdout.write(message) + else: + old_stderr.write(message) + + def flush(self): + if self.is_stdout: + old_stdout.flush() + else: + old_stderr.flush() + + +def log_wrap(): + sys.stdout = StdTimeFilter(True) + sys.stderr = StdTimeFilter(False) diff --git a/static/js/debounce.js b/static/js/debounce.js new file mode 100644 index 0000000..1e82705 --- /dev/null +++ b/static/js/debounce.js @@ -0,0 +1,46 @@ +/** + * 防抖函数 + * @param fn + * @param delay + * @param immediate + * @returns {function(...[*]): Promise} + * @source https://juejin.cn/post/7075890311649558541 + */ +export function debounce(fn, delay, immediate = false) { + // 1.定义一个定时器, 保存上一次的定时器 + let timer = null + let isInvoke = false + + // 2.真正执行的函数 + const _debounce = function (...args) { + return new Promise((resolve, reject) => { + // 取消上一次的定时器 + if (timer) clearTimeout(timer) + + // 判断是否需要立即执行 + if (immediate && !isInvoke) { + const result = fn.apply(this, args) + resolve(result) + isInvoke = true + } else { + // 延迟执行 + timer = setTimeout(() => { + // 外部传入的真正要执行的函数, 拿到函数返回值并调用resolve + const result = fn.apply(this, args) + resolve(result) + isInvoke = false + timer = null + }, delay) + } + }) + } + + // 封装取消功能 + _debounce.cancel = function () { + if (timer) clearTimeout(timer) + timer = null + isInvoke = false + } + + return _debounce +} diff --git a/static/js/easyapi.js b/static/js/easyapi.js new file mode 100644 index 0000000..df11641 --- /dev/null +++ b/static/js/easyapi.js @@ -0,0 +1,35 @@ +import { app } from "/scripts/app.js"; +import { api } from "/scripts/api.js"; +import { debounce } from "./debounce.js" + +app.registerExtension({ + name: "Comfy.EasyApi.Setting", + async setup(app) { + const res = await api.fetchApi("/easyapi/history/maxSize") + const jsonData = await res.json() || {}; + const max = jsonData['maxSize'] || 10000 + const changeFun = debounce((n, o) => api.fetchApi("/easyapi/history/size", { + method: 'POST', + headers: { + "content-type": "application/json", + }, + body: JSON.stringify({ + maxSize: n + }) + }), 1000, false) + const setting = app.ui.settings.addSetting({ + id: name, + name: "Maximum History Size", + defaultValue: max, + type: "slider", + attrs: { + min: 1, + max: 10000, + step: 1, + }, + onChange: (newVal, oldVal) => { + changeFun.apply(null, [newVal, oldVal]) + } + }); + }, +}) \ No newline at end of file