From 2ba760b88c980e3dcd42e9c3b23bc65e31299f3c Mon Sep 17 00:00:00 2001 From: Joey Ballentine <34788790+joeyballentine@users.noreply.github.com> Date: Sat, 16 Apr 2022 00:52:45 -0400 Subject: [PATCH] v0.5.2 -- Bug fixes, new Preview Image node, etc minor features (#50) * Fix cuda issue in ONNX convert * Add relative paths to image iteration (theflyingzamboni) (#46) * Added Image Directory output to Load image * Add relative pathing to Iterator Load Image and Save Image * Lock DirectoryInput when connected * Fixed validation bug * Fixed minor relative path bug of no consequence * Removed unused import * Added 0.5.2 migration * Updated dependency requirements and incremented version * Split settings into its own context Fix snapToGrid import * Use env vars to kill upscaling during tiling * Potential fix for checking if image file exists * Change where extra data processing goes * Automatic param/bin file detection on change * Fix for consistent ncnn gpu index (will make it selectable in future) * Fix position problem, update some packages, fix iterator file types * eslint updates * Linting changes * Fix eslint cli not reading subdirs * Fix prop sorting * Attempt to fix iterator dropping on an edge * disable auto-split killing as it crashes backend * V0.5.2 dev (#48) * Fixed supported extensions bug * Reverted imread output to list to fix bug * Fixed image support fix * Fixed Typing bug preventing nodes from importing (#49) * Better nvidia-smi check * Fix casing issue and filetype double dot issue * Just check linting on pre-commit rather than fixing * Downgrade react-flow-renderer due to zIndex issue * Add new image preview external node and maybe fix load image file exists check for real * Fix iterator progress updates * Fix new image preview on linux, remove old image preview on linux * Replace original image show with new one Co-authored-by: theflyingzamboni <55669985+theflyingzamboni@users.noreply.github.com> --- .eslintrc.js | 29 ++ .eslintrc.json | 14 +- backend/nodes/image_iterator_nodes.py | 111 +++-- backend/nodes/image_nodes.py | 171 +++---- backend/nodes/ncnn_nodes.py | 3 +- backend/nodes/node_base.py | 4 + .../nodes/properties/inputs/file_inputs.py | 10 +- .../nodes/properties/inputs/generic_inputs.py | 4 +- .../nodes/properties/inputs/opencv_inputs.py | 2 +- .../nodes/properties/outputs/file_outputs.py | 9 +- backend/nodes/pytorch_nodes.py | 2 +- backend/nodes/utility_nodes.py | 2 +- backend/nodes/utils/image_utils.py | 39 +- backend/nodes/utils/ncnn_auto_split.py | 10 + backend/nodes/utils/pytorch_auto_split.py | 8 + backend/process.py | 14 +- backend/requirements.txt | 12 - backend/run.py | 34 +- package-lock.json | 458 +++++++++++------- package.json | 15 +- src/app.jsx | 12 +- src/{helpers => components}/CustomEdge.jsx | 160 +++--- src/components/CustomIcons.jsx | 122 +++-- src/components/DependencyManager.jsx | 177 ++++--- src/components/Header.jsx | 97 +++- src/components/NodeSelectorPanel.jsx | 108 +++-- src/components/ReactFlowBox.jsx | 62 ++- src/components/SettingsModal.jsx | 238 +++++++-- src/components/SystemStats.jsx | 28 +- src/components/chaiNNerLogo.jsx | 42 +- src/components/inputs/DirectoryInput.jsx | 23 +- src/components/inputs/DropDownInput.jsx | 21 +- src/components/inputs/FileInput.jsx | 75 ++- src/components/inputs/GenericInput.jsx | 18 +- src/components/inputs/InputContainer.jsx | 23 +- src/components/inputs/NumberInput.jsx | 18 +- src/components/inputs/SliderInput.jsx | 40 +- src/components/inputs/TextAreaInput.jsx | 16 +- src/components/inputs/TextInput.jsx | 14 +- .../inputs/previews/ImagePreview.jsx | 25 +- src/components/node/IteratorHelperNode.jsx | 32 +- .../node/IteratorHelperNodeFooter.jsx | 29 +- src/components/node/IteratorNode.jsx | 82 +++- src/components/node/IteratorNodeBody.jsx | 71 +-- src/components/node/IteratorNodeHeader.jsx | 45 +- src/components/node/Node.jsx | 43 +- src/components/node/NodeBody.jsx | 36 +- src/components/node/NodeFooter.jsx | 60 ++- src/components/node/NodeHeader.jsx | 34 +- src/components/node/NodeInputs.jsx | 13 +- src/components/node/NodeOutputs.jsx | 12 +- src/components/node/RepresentativeNode.jsx | 48 +- src/components/outputs/GenericOutput.jsx | 18 +- src/components/outputs/ImageOutput.jsx | 7 +- src/components/outputs/OutputContainer.jsx | 16 +- src/helpers/checkNodeValidity.js | 2 +- .../{ => contexts}/GlobalNodeState.jsx | 37 +- src/helpers/contexts/SettingsContext.jsx | 40 ++ src/helpers/dependencies.js | 9 +- src/helpers/getNodeAccentColors.js | 2 +- src/helpers/migrations.js | 49 ++ src/helpers/pipInstallWithProgress.js | 2 +- src/hooks.js | 1 + src/index.jsx | 1 - src/main.js | 44 +- src/pages/main.jsx | 103 ++-- src/setupIntegratedPython.js | 1 + src/splash.jsx | 35 +- webpack.rules.js | 6 +- 69 files changed, 2012 insertions(+), 1136 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 backend/requirements.txt rename src/{helpers => components}/CustomEdge.jsx (53%) rename src/helpers/{ => contexts}/GlobalNodeState.jsx (95%) create mode 100644 src/helpers/contexts/SettingsContext.jsx diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..8d549b66f --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,29 @@ +module.exports = { + extends: [ + 'airbnb', + 'plugin:react/jsx-runtime', + ], + globals: { + MAIN_WINDOW_WEBPACK_ENTRY: true, + SPLASH_SCREEN_WEBPACK_ENTRY: true, + }, + env: { + browser: true, + node: true, + }, + parserOptions: { + ecmaVersion: 2020, + }, + rules: { + 'react/jsx-sort-props': ['error', { + callbacksLast: true, + shorthandFirst: true, + }], + 'react/jsx-max-props-per-line': ['error', { maximum: 1, when: 'always' }], + 'import/extensions': 'off', + 'react/prop-types': 'off', + }, + settings: { + 'import/core-modules': ['electron'], + }, +}; diff --git a/.eslintrc.json b/.eslintrc.json index 91993363c..fec241881 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,8 @@ { - "extends": "airbnb", + "extends": [ + "airbnb", + "plugin:react/jsx-runtime" + ], "globals": { "MAIN_WINDOW_WEBPACK_ENTRY": true, "SPLASH_SCREEN_WEBPACK_ENTRY": true @@ -10,5 +13,14 @@ }, "parserOptions": { "ecmaVersion": 2020 + }, + "rules": { + "react/jsx-sort-props": [2, { + "callbacksLast": true, + "shorthandFirst": true + }], + "react/jsx-max-props-per-line": [2, { "maximum": 1, "when": "always" }], + "import/extensions": "off", + "react/prop-types": "off" } } \ No newline at end of file diff --git a/backend/nodes/image_iterator_nodes.py b/backend/nodes/image_iterator_nodes.py index 45415fa00..7f9dd2ca6 100644 --- a/backend/nodes/image_iterator_nodes.py +++ b/backend/nodes/image_iterator_nodes.py @@ -12,6 +12,7 @@ from .node_factory import NodeFactory from .properties.inputs import * from .properties.outputs import * +from .utils.image_utils import get_available_image_formats IMAGE_ITERATOR_DEFAULT_NODE_NAME = "Load Image (Iterator)" @@ -26,15 +27,26 @@ def __init__(self): self.description = "" self.inputs = [IteratorInput()] self.outputs = ImReadNode().get_outputs() - + self.outputs.insert( + 2, TextOutput("Relative Path") + ) # Add relative path to outputs outside ImReadNode self.icon = "MdSubdirectoryArrowRight" self.sub = "Iteration" self.type = "iteratorHelper" - def run(self, directory: str = "") -> any: + def run(self, directory: str = "", root_dir: str = "") -> any: imread = ImReadNode() - return imread.run(directory) + imread_output = imread.run(directory) + + # Get relative path from root directory passed by Iterator directory input + rel_path = os.path.relpath(imread_output[1], root_dir) + + # Set ImRead directory output to root/base directory and insert relative path into outputs + imread_output[1] = root_dir + imread_output.insert(2, rel_path) + + return imread_output @NodeFactory.register("Image", "Image File Iterator") @@ -67,7 +79,7 @@ async def run( id="", parent_executor=None, percent=0, - ) -> any: + ) -> Any: logger.info(f"Iterating over images in directory: {directory}") logger.info(nodes) @@ -84,59 +96,60 @@ async def run( # Set this to false to actually allow processing to happen nodes[k]["child"] = False - supported_filetypes = [ - ".png", - ".jpg", - ".jpeg", - ] # TODO: Make a method to get these dynamically based on the installed deps + supported_filetypes = get_available_image_formats() def walk_error_handler(exception_instance): logger.warn( f"Exception occurred during walk: {exception_instance} Continuing..." ) + just_image_files = [] for root, dirs, files in os.walk( - directory, topdown=False, onerror=walk_error_handler + directory, topdown=True, onerror=walk_error_handler ): if parent_executor.should_stop_running(): return - file_len = len(files) - start_idx = math.ceil(float(percent) * file_len) - for idx, name in enumerate(files): - if parent_executor.should_stop_running(): - return - if idx >= start_idx: - await queue.put( - { - "event": "iterator-progress-update", - "data": { - "percent": idx / file_len, - "iteratorId": id, - "running": child_nodes, - }, - } - ) - filepath = os.path.join(root, name) - base, ext = os.path.splitext(filepath) - if ext.lower() in supported_filetypes: - # Replace the input filepath with the filepath from the loop - nodes[img_path_node_id]["inputs"] = [filepath] - executor = Executor( - nodes, - loop, - queue, - external_cache.copy(), - parent_executor=parent_executor, - ) - await executor.run() - await queue.put( - { - "event": "iterator-progress-update", - "data": { - "percent": (idx + 1) / file_len, - "iteratorId": id, - "running": None, - }, - } - ) + + for name in files: + filepath = os.path.join(root, name) + base, ext = os.path.splitext(filepath) + if ext.lower() in supported_filetypes: + just_image_files.append(filepath) + + file_len = len(just_image_files) + start_idx = math.ceil(float(percent) * file_len) + for idx, filepath in enumerate(just_image_files): + if parent_executor.should_stop_running(): + return + if idx >= start_idx: + await queue.put( + { + "event": "iterator-progress-update", + "data": { + "percent": idx / file_len, + "iteratorId": id, + "running": child_nodes, + }, + } + ) + # Replace the input filepath with the filepath from the loop + nodes[img_path_node_id]["inputs"] = [filepath, directory] + executor = Executor( + nodes, + loop, + queue, + external_cache.copy(), + parent_executor=parent_executor, + ) + await executor.run() + await queue.put( + { + "event": "iterator-progress-update", + "data": { + "percent": (idx + 1) / file_len, + "iteratorId": id, + "running": None, + }, + } + ) return "" diff --git a/backend/nodes/image_nodes.py b/backend/nodes/image_nodes.py index 06adffecb..de88ae612 100644 --- a/backend/nodes/image_nodes.py +++ b/backend/nodes/image_nodes.py @@ -4,7 +4,10 @@ import math import os -import sys +import platform +import subprocess +import time +from tempfile import TemporaryDirectory, mkdtemp import cv2 import numpy as np @@ -40,17 +43,39 @@ def __init__(self): # IntegerOutput("Height"), # IntegerOutput("Width"), # IntegerOutput("Channels"), + DirectoryOutput(), TextOutput("Image Name"), ] self.icon = "BsFillImageFill" self.sub = "Input & Output" + self.result = [] - def run(self, path: str) -> np.ndarray: + def get_extra_data(self) -> Dict: + img = self.result[0] + + if img.ndim == 2: + h, w, c = img.shape[:2], 1 + else: + h, w, c = img.shape + + import base64 + + _, encoded_img = cv2.imencode(".png", (img * 255).astype("uint8")) + base64_img = base64.b64encode(encoded_img).decode("utf8") + + return { + "image": base64_img, + "height": h, + "width": w, + "channels": c, + } + + def run(self, path: str) -> list[np.ndarray, str, str]: """Reads an image from the specified path and return it as a numpy array""" logger.info(f"Reading image from path: {path}") base, ext = os.path.splitext(path) - if ext.replace(".", "") in get_opencv_formats(): + if ext.lower() in get_opencv_formats(): try: img = cv2.imdecode( np.fromfile(path, dtype=np.uint8), cv2.IMREAD_UNCHANGED @@ -64,7 +89,7 @@ def run(self, path: str) -> np.ndarray: raise RuntimeError( f'Error reading image image from path "{path}". Image may be corrupt.' ) - elif ext.replace(".", "") in get_pil_formats(): + elif ext.lower() in get_pil_formats(): try: from PIL import Image @@ -81,7 +106,9 @@ def run(self, path: str) -> np.ndarray: f'Error reading image image from path "{path}". Image may be corrupt or Pillow not installed.' ) else: - img = None + raise NotImplementedError( + "The image you are trying to read cannot be read by chaiNNer." + ) dtype_max = 1 try: @@ -95,8 +122,9 @@ def run(self, path: str) -> np.ndarray: c = img.shape[2] if img.ndim > 2 else 1 # return img, h, w, c - basename = os.path.splitext(os.path.basename(path))[0] - return img, basename + dirname, basename = os.path.split(os.path.splitext(path)[0]) + self.result = [img, dirname, basename] + return self.result @NodeFactory.register("Image", "Save Image") @@ -109,7 +137,8 @@ def __init__(self): self.description = "Save image to file at a specified directory." self.inputs = [ ImageInput(), - DirectoryInput(), + DirectoryInput(hasHandle=True), + TextInput("Relative Path", optional=True), TextInput("Image Name"), ImageExtensionDropdown(), ] @@ -118,104 +147,80 @@ def __init__(self): self.sub = "Input & Output" def run( - self, img: np.ndarray, directory: str, filename: str, extension: str + self, + img: np.ndarray = None, + base_directory: str = None, + relative_path: str = ".", + filename: str = None, + extension: str = None, ) -> bool: """Write an image to the specified path and return write status""" - fullFile = f"{filename}.{extension}" - fullPath = os.path.join(directory, fullFile) - logger.info(f"Writing image to path: {fullPath}") + # Shift inputs if relative path is missing + if extension is None: + extension = filename + filename = relative_path + relative_path = "." + + full_file = f"{filename}.{extension}" + if relative_path and relative_path != ".": + base_directory = os.path.join(base_directory, relative_path) + full_path = os.path.join(base_directory, full_file) + + logger.info(f"Writing image to path: {full_path}") # Put image back in int range img = (np.clip(img, 0, 1) * 255).round().astype("uint8") - status = cv2.imwrite(fullPath, img) + os.makedirs(base_directory, exist_ok=True) + + status = cv2.imwrite(full_path, img) return status @NodeFactory.register("Image", "Preview Image") -class ImShowNode(NodeBase): - """OpenCV Imshow node""" +class ImOpenNode(NodeBase): + """Image Open Node""" def __init__(self): """Constructor""" super().__init__() - self.description = "Show image preview in a new window." + self.description = "Open the image in your default image viewer." self.inputs = [ImageInput()] self.outputs = [] self.icon = "BsEyeFill" self.sub = "Input & Output" - def checkerboard(self, h, w): - square_size = 8 - new_h = (h // square_size) + 1 - new_w = (w // square_size) + 1 - # Black and white checkerboard - # from https://stackoverflow.com/questions/2169478/how-to-make-a-checkerboard-in-numpy - checkerboard = (np.indices((new_h, new_w)).sum(axis=0) % 2).astype("uint8") - # Modify to a mixed grayish color - checkerboard = ((checkerboard * 127) + 128) // 2 - # Resize to full size - checkerboard = cv2.resize( - checkerboard, - (new_w * square_size, new_h * square_size), - interpolation=cv2.INTER_NEAREST, - ) - # Crop to fit original resolution - checkerboard = checkerboard[:h, :w] - return checkerboard.astype("float32") / 255 - def run(self, img: np.ndarray) -> bool: """Show image""" + + # Theoretically this isn't necessary, but just in case + dtype_max = 1 try: - # Theoretically this isn't necessary, but just in case - dtype_max = 1 - try: - dtype_max = np.iinfo(img.dtype).max - except: - logger.debug("img dtype is not int") - - show_img = img.astype("float32") / dtype_max - # logger.info(dtype_max) - if img.ndim > 2 and img.shape[2] == 4: - h, w, _ = img.shape - checkerboard = self.checkerboard(h, w) - checkerboard = cv2.cvtColor(checkerboard, cv2.COLOR_GRAY2BGR) - alpha = cv2.cvtColor(show_img[:, :, 3], cv2.COLOR_GRAY2BGR) - - foreground = cv2.multiply(alpha, show_img[:, :, :3]) - background = cv2.multiply(1.0 - alpha, checkerboard) - show_img = cv2.add(foreground, background) - - h, w = show_img.shape[:2] - x = int(0.85 * int(os.environ["resolutionX"])) - y = int(0.85 * int(os.environ["resolutionY"])) - if h > y and w > x: - ratio = min(y / h, x / w) - new_h = int(ratio * h) - new_w = int(ratio * w) - show_img = cv2.resize( - show_img, (new_w, new_h), interpolation=cv2.INTER_AREA - ) - elif h > y: - ratio = y / h - new_h = y - new_w = int(ratio * w) - show_img = cv2.resize( - show_img, (new_w, new_h), interpolation=cv2.INTER_AREA - ) - elif w > x: - ratio = x / w - new_h = int(ratio * h) - new_w = x - show_img = cv2.resize( - show_img, (new_w, new_h), interpolation=cv2.INTER_AREA - ) - cv2.imshow("Image Preview", show_img) - cv2.waitKey(0) - except Exception as e: - logger.fatal(e) - logger.fatal("Imshow had a critical error") + dtype_max = np.iinfo(img.dtype).max + except: + logger.debug("img dtype is not int") + + img = img.astype("float32") / dtype_max + + # Put image back in int range + img = (np.clip(img, 0, 1) * 255).round().astype("uint8") + + tempdir = mkdtemp(prefix="chaiNNer-") + logger.info(f"Writing image to temp path: {tempdir}") + imName = f"{time.time()}.png" + tempSaveDir = os.path.join(tempdir, imName) + status = cv2.imwrite( + tempSaveDir, + img, + ) + if status: + if platform.system() == "Darwin": # macOS + subprocess.call(("open", tempSaveDir)) + elif platform.system() == "Windows": # Windows + os.startfile(tempSaveDir) + else: # linux variants + subprocess.call(("xdg-open", tempSaveDir)) @NodeFactory.register("Image (Utility)", "Resize (Factor)") diff --git a/backend/nodes/ncnn_nodes.py b/backend/nodes/ncnn_nodes.py index 64e22b3cc..b4f4a1125 100644 --- a/backend/nodes/ncnn_nodes.py +++ b/backend/nodes/ncnn_nodes.py @@ -84,6 +84,7 @@ def run(self, param_path: str, bin_path: str) -> Any: # Use vulkan compute net.opt.use_vulkan_compute = True + net.set_vulkan_device(ncnn.get_default_gpu_index()) # Load model param and bin net.load_param(param_path) @@ -111,7 +112,7 @@ def __init__(self): def upscale(self, img: np.ndarray, net: tuple, input_name: str, output_name: str): # Try/except block to catch errors try: - vkdev = ncnn.get_gpu_device(0) + vkdev = ncnn.get_gpu_device(ncnn.get_default_gpu_index()) blob_vkallocator = ncnn.VkBlobAllocator(vkdev) staging_vkallocator = ncnn.VkStagingAllocator(vkdev) output, _ = ncnn_auto_split_process( diff --git a/backend/nodes/node_base.py b/backend/nodes/node_base.py index ddfef3950..d57e7a90e 100644 --- a/backend/nodes/node_base.py +++ b/backend/nodes/node_base.py @@ -19,6 +19,10 @@ def run(self, **kwargs) -> Any: """Abstract method to run a node's logic""" return + def get_extra_data(self, **kwargs) -> Any: + """Abstract method for getting extra data the frontend needs""" + return + def get_inputs(self): return self.inputs diff --git a/backend/nodes/properties/inputs/file_inputs.py b/backend/nodes/properties/inputs/file_inputs.py index a1112d228..f22a4e61b 100644 --- a/backend/nodes/properties/inputs/file_inputs.py +++ b/backend/nodes/properties/inputs/file_inputs.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from typing import Dict from ...utils.image_utils import get_available_image_formats from .generic_inputs import DropDownInput @@ -7,8 +7,8 @@ def FileInput( input_type: str, label: str, - accepts: List[str], - filetypes: List[str], + accepts: list[str], + filetypes: list[str], hasHandle: bool = False, ) -> Dict: """Input for submitting a local file""" @@ -42,9 +42,9 @@ def TorchFileInput() -> Dict: return FileInput("pth", "Pretrained Model", None, ["pth", "pt"]) -def DirectoryInput() -> Dict: +def DirectoryInput(hasHandle: bool = False) -> Dict: """Input for submitting a local directory""" - return FileInput("directory", "Directory", None, ["directory"]) + return FileInput("directory", "Base Directory", None, ["directory"], hasHandle) def ImageExtensionDropdown() -> Dict: diff --git a/backend/nodes/properties/inputs/generic_inputs.py b/backend/nodes/properties/inputs/generic_inputs.py index 60864d03c..6406e9ff4 100644 --- a/backend/nodes/properties/inputs/generic_inputs.py +++ b/backend/nodes/properties/inputs/generic_inputs.py @@ -1,8 +1,8 @@ -from typing import Dict, List +from typing import Dict def DropDownInput( - input_type: str, label: str, options: List[str], optional: bool = False + input_type: str, label: str, options: list[str], optional: bool = False ) -> Dict: """Input for a dropdown""" return { diff --git a/backend/nodes/properties/inputs/opencv_inputs.py b/backend/nodes/properties/inputs/opencv_inputs.py index 40ced70f6..20ee8fd2f 100644 --- a/backend/nodes/properties/inputs/opencv_inputs.py +++ b/backend/nodes/properties/inputs/opencv_inputs.py @@ -1,4 +1,4 @@ -from typing import Dict, List +from typing import Dict import cv2 diff --git a/backend/nodes/properties/outputs/file_outputs.py b/backend/nodes/properties/outputs/file_outputs.py index 89fe203c5..0921acc22 100644 --- a/backend/nodes/properties/outputs/file_outputs.py +++ b/backend/nodes/properties/outputs/file_outputs.py @@ -1,7 +1,7 @@ -from typing import Dict, List +from typing import Dict -def FileOutput(input_type: str, label: str, filetypes: List[str]) -> Dict: +def FileOutput(input_type: str, label: str, filetypes: list[str]) -> Dict: """Output for saving a local file""" return { "type": f"file::{input_type}", @@ -17,6 +17,11 @@ def ImageFileOutput(label: str = "image") -> Dict: ) +def DirectoryOutput(label: str = "directory") -> Dict: + """Output for saving to a directory""" + return FileOutput(label, "Image Directory", ["directory"]) + + def OnnxFileOutput() -> Dict: """Output for saving a .onnx file""" return FileOutput("onnx", "ONNX Model", ["onnx"]) diff --git a/backend/nodes/pytorch_nodes.py b/backend/nodes/pytorch_nodes.py index e9a6290fb..d98d16e97 100644 --- a/backend/nodes/pytorch_nodes.py +++ b/backend/nodes/pytorch_nodes.py @@ -430,7 +430,7 @@ def run(self, model: torch.nn.Module, directory: str, model_name: str) -> None: "data": {0: "batch_size", 2: "width", 3: "height"}, "output": {0: "batch_size", 2: "width", 3: "height"}, } - dummy_input = torch.rand(1, model.in_nc, 64, 64).cuda() + dummy_input = torch.rand(1, model.in_nc, 64, 64) if os.environ["device"] == "cuda": dummy_input = dummy_input.cuda() diff --git a/backend/nodes/utility_nodes.py b/backend/nodes/utility_nodes.py index 2bad8e0ea..c14156202 100644 --- a/backend/nodes/utility_nodes.py +++ b/backend/nodes/utility_nodes.py @@ -85,6 +85,6 @@ def run( str2: str, str3: str = None, str4: str = None, - ) -> int: + ) -> str: strings = [x for x in [str1, str2, str3, str4] if x != "" and x is not None] return separator.join(strings) diff --git a/backend/nodes/utils/image_utils.py b/backend/nodes/utils/image_utils.py index 26a7ff098..dc4cb3568 100644 --- a/backend/nodes/utils/image_utils.py +++ b/backend/nodes/utils/image_utils.py @@ -4,25 +4,25 @@ def get_opencv_formats(): import cv2 # Bitmaps - available_formats.extend(["bmp", "dib"]) + available_formats.extend([".bmp", ".dib"]) # JPEG - available_formats.extend(["jpg", "jpeg", "jpe", "jp2"]) + available_formats.extend([".jpg", ".jpeg", ".jpe", ".jp2"]) # PNG, WebP, Tiff - available_formats.extend(["png", "webp", "tiff"]) + available_formats.extend([".png", ".webp", ".tiff"]) # Portable image format - available_formats.extend(["pbm", "pgm", "ppm", "pxm", "pnm"]) + available_formats.extend([".pbm", ".pgm", ".ppm", ".pxm", ".pnm"]) # Sun Rasters - available_formats.extend(["sr", "ras"]) + available_formats.extend([".sr", ".ras"]) # OpenEXR - available_formats.extend(["exr"]) + available_formats.extend([".exr"]) # Radiance HDR - available_formats.extend(["hdr", "pic"]) + available_formats.extend([".hdr", ".pic"]) except: print("OpenCV not installed") return available_formats @@ -34,40 +34,37 @@ def get_pil_formats(): from PIL import Image # Bitmaps - available_formats.extend(["bmp", "dib", "xbm"]) + available_formats.extend([".bmp", ".dib", ".xbm"]) # DDS - available_formats.extend(["dds"]) + available_formats.extend([".dds"]) # EPS - available_formats.extend(["eps"]) + available_formats.extend([".eps"]) # GIF - # available_formats.extend(["gif"]) + # available_formats.extend([".gif"]) # Icons - available_formats.extend(["icns", "ico"]) + available_formats.extend([".icns", ".ico"]) # JPEG - available_formats.extend(["jpg", "jpeg", "jfif", "jp2", "jpx"]) + available_formats.extend([".jpg", ".jpeg", ".jfif", ".jp2", ".jpx"]) # Randoms - available_formats.extend(["msp", "pcx", "sgi"]) + available_formats.extend([".msp", ".pcx", ".sgi"]) # PNG, WebP, TIFF - available_formats.extend(["png", "webp", "tiff"]) + available_formats.extend([".png", ".webp", ".tiff"]) # APNG - # available_formats.extend(["apng"]) + # available_formats.extend([".apng"]) # Portable image format - available_formats.extend(["pbm", "pgm", "ppm", "pnm"]) + available_formats.extend([".pbm", ".pgm", ".ppm", ".pnm"]) # TGA - available_formats.extend(["tga"]) - - # TGA - available_formats.extend(["tga"]) + available_formats.extend([".tga"]) except: print("Pillow not installed") return available_formats diff --git a/backend/nodes/utils/ncnn_auto_split.py b/backend/nodes/utils/ncnn_auto_split.py index e089bc285..e2f863ad0 100644 --- a/backend/nodes/utils/ncnn_auto_split.py +++ b/backend/nodes/utils/ncnn_auto_split.py @@ -1,4 +1,5 @@ import gc +import os from typing import Tuple import numpy as np @@ -33,10 +34,19 @@ def ncnn_auto_split_process( blob_vkallocator=None, staging_vkallocator=None, ) -> Tuple[np.ndarray, int]: + """ + Run NCNN upscaling with automatic recursive tile splitting based on ability to process with current size + """ # Original code: https://github.com/JoeyBallentine/ESRGAN/blob/master/utils/dataops.py + # if os.environ["killed"] == "True": + # ncnn.destroy_gpu_instance() + # gc.collect() + # raise RuntimeError("Upscaling killed mid-processing") + # Prevent splitting from causing an infinite out-of-vram loop if current_depth > 15: + ncnn.destroy_gpu_instance() gc.collect() raise RuntimeError("Splitting stopped to prevent infinite loop") diff --git a/backend/nodes/utils/pytorch_auto_split.py b/backend/nodes/utils/pytorch_auto_split.py index 664a81fb4..8628b88aa 100644 --- a/backend/nodes/utils/pytorch_auto_split.py +++ b/backend/nodes/utils/pytorch_auto_split.py @@ -83,8 +83,16 @@ def auto_split_process( max_depth: int = None, current_depth: int = 1, ) -> Tuple[Tensor, int]: + """ + Run PyTorch upscaling with automatic recursive tile splitting based on ability to process with current size + """ # Original code: https://github.com/JoeyBallentine/ESRGAN/blob/master/utils/dataops.py + # if os.environ["killed"] == "True": + # torch.cuda.empty_cache() + # gc.collect() + # raise RuntimeError("Upscaling killed mid-processing") + # Prevent splitting from causing an infinite out-of-vram loop if current_depth > 15: torch.cuda.empty_cache() diff --git a/backend/process.py b/backend/process.py index 5e0dbb145..84636884b 100644 --- a/backend/process.py +++ b/backend/process.py @@ -1,7 +1,8 @@ import asyncio import functools +import os import uuid -from typing import Dict, List +from typing import Dict from sanic import app from sanic.log import logger @@ -10,9 +11,13 @@ class Executor: + """ + Class for executing chaiNNer's processing logic + """ + def __init__( self, - nodes: List[Dict], + nodes: list[Dict], loop, queue: asyncio.Queue, existing_cache: Dict, @@ -143,6 +148,7 @@ async def resume(self): logger.info(f"Rusuming executor {self.execution_id}") self.paused = False self.resumed = True + os.environ["killed"] = "False" await self.process_nodes() async def check(self): @@ -161,14 +167,18 @@ async def kill(self): """Kill the executor""" logger.info(f"Killing executor {self.execution_id}") self.killed = True + os.environ["killed"] = "True" def is_killed(self): + """Return if the executor is killed""" return self.killed def is_paused(self): + """Return if the executor is paused""" return self.paused def should_stop_running(self): + """Return if the executor should stop running""" return ( self.killed or self.paused diff --git a/backend/requirements.txt b/backend/requirements.txt deleted file mode 100644 index cf380800b..000000000 --- a/backend/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -sanic -websockets==9.1 -numpy -opencv-python -sanic-cors -nuitka -zstandard - -# Pytorch ---find-links https://download.pytorch.org/whl/torch_stable.html -torch==1.9.0+cu111; sys_platform == 'linux' or sys_platform == 'windows' -torch==1.9.0; sys_platform == 'darwin' \ No newline at end of file diff --git a/backend/run.py b/backend/run.py index ff67320f7..4eec09daa 100644 --- a/backend/run.py +++ b/backend/run.py @@ -2,6 +2,7 @@ import functools import gc import os +import platform import sys import traceback from json import dumps as stringify @@ -15,6 +16,10 @@ try: import cv2 + # Remove broken QT env var + if platform.system() == "Linux": + os.environ.pop("QT_QPA_PLATFORM_PLUGIN_PATH") + from nodes import image_iterator_nodes, image_nodes except Exception as e: logger.warning(e) @@ -100,6 +105,7 @@ async def run(request: Request): queue = request.app.ctx.queue try: + os.environ["killed"] = "False" if request.app.ctx.executor: logger.info("Resuming existing executor...") executor = request.app.ctx.executor @@ -148,31 +154,6 @@ async def run(request: Request): ) -# Gets extra information from node output data before sending to frontend -def get_extra_data(category, node, node_output): - if category == "Image": - if node == "Load Image": - img, name = node_output - - if img.ndim == 2: - h, w, c = img.shape[:2], 1 - else: - h, w, c = img.shape - - import base64 - - _, encoded_img = cv2.imencode(".png", (img * 255).astype("uint8")) - base64_img = base64.b64encode(encoded_img).decode("utf8") - - return { - "image": base64_img, - "height": h, - "width": w, - "channels": c, - } - return node_output - - @app.route("/run/individual", methods=["POST"]) async def run_individual(request: Request): """Runs a single node""" @@ -185,8 +166,9 @@ async def run_individual(request: Request): output = await app.loop.run_in_executor(None, run_func) # Cache the output of the node app.ctx.cache[full_data["id"]] = output + extra_data = node_instance.get_extra_data() del node_instance, run_func - return json(get_extra_data(full_data["category"], full_data["node"], output)) + return json(extra_data) @app.get("/sse") diff --git a/package-lock.json b/package-lock.json index d4eb007fb..bb4115935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "chainner", - "version": "0.5.1", + "version": "0.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "chainner", - "version": "0.5.1", + "version": "0.5.2", "license": "GPLv3", "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.17.3", "@chakra-ui/icons": "^1.0.15", "@chakra-ui/react": "^1.8.6", "@emotion/react": "^11.8.1", @@ -55,13 +56,15 @@ "concurrently": "^6.5.1", "cross-env": "^7.0.3", "css-loader": "^6.2.0", - "electron": "^18.0.1", + "electron": "^18.0.3", "electron-installer-common": "^0.10.3", "eslint": "^7.32.0", "eslint-config-airbnb": "^18.2.1", + "eslint-plugin-react": "^7.29.4", "file-loader": "^6.2.0", "image-webpack-loader": "^8.1.0", "node-loader": "^2.0.0", + "pre-commit": "^1.2.2", "react-refresh": "^0.11.0", "semver-regex": ">=3.1.3", "style-loader": "^3.2.1" @@ -90,32 +93,32 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", + "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", + "@babel/generator": "^7.17.9", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.9", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" }, "engines": { @@ -135,9 +138,9 @@ } }, "node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", "dependencies": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -151,7 +154,6 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, "dependencies": { "@babel/types": "^7.16.7" }, @@ -160,11 +162,11 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", "dependencies": { - "@babel/compat-data": "^7.16.4", + "@babel/compat-data": "^7.17.7", "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" @@ -196,24 +198,12 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -242,13 +232,13 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", @@ -268,11 +258,11 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -306,12 +296,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "dependencies": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0" }, "engines": { @@ -332,9 +322,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -375,7 +365,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", - "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -480,17 +469,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", "dependencies": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", + "@babel/generator": "^7.17.9", "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", + "@babel/parser": "^7.17.9", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" @@ -4259,7 +4248,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -4306,7 +4294,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -7163,9 +7150,9 @@ "dev": true }, "node_modules/electron": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-18.0.1.tgz", - "integrity": "sha512-8y3nxmK+v/tiuaR8yd4K83ApHxgomMIPAEl3J+2Jfv/D5G6M3KnvxNlNiNoTXI8uOegfmoqiDm5/2xlWFLzfLQ==", + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-18.0.3.tgz", + "integrity": "sha512-QRUZkGL8O/8CyDmTLSjBeRsZmGTPlPVeWnnpkdNqgHYYaOc/A881FKMiNzvQ9Cj0a+rUavDdwBUfUL82U3Ay7w==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -8710,11 +8697,10 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz", - "integrity": "sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", + "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", "dev": true, - "peer": true, "dependencies": { "array-includes": "^3.1.4", "array.prototype.flatmap": "^1.2.5", @@ -8756,7 +8742,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -8769,7 +8754,6 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", "dev": true, - "peer": true, "dependencies": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" @@ -8783,7 +8767,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -12479,12 +12462,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dependencies": { - "minimist": "^1.2.5" - }, + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "bin": { "json5": "lib/cli.js" }, @@ -12534,7 +12514,6 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", "dev": true, - "peer": true, "dependencies": { "array-includes": "^3.1.3", "object.assign": "^4.1.2" @@ -13992,7 +13971,6 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -14010,7 +13988,6 @@ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", "dev": true, - "peer": true, "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.19.1" @@ -14024,7 +14001,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -14245,6 +14221,15 @@ "node": ">=4" } }, + "node_modules/os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -14990,6 +14975,78 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + } + }, + "node_modules/pre-commit/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/pre-commit/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/pre-commit/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit/node_modules/which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/pre-commit/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -15128,8 +15185,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true, - "optional": true + "dev": true }, "node_modules/psl": { "version": "1.8.0", @@ -16490,6 +16546,17 @@ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "dev": true }, + "node_modules/spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -16690,7 +16757,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", "integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==", "dev": true, - "peer": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -18647,29 +18713,29 @@ } }, "@babel/compat-data": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.0.tgz", - "integrity": "sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng==" + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==" }, "@babel/core": { - "version": "7.17.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", - "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz", + "integrity": "sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw==", "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.17.2", - "@babel/parser": "^7.17.3", + "@babel/generator": "^7.17.9", + "@babel/helper-compilation-targets": "^7.17.7", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.9", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", + "json5": "^2.2.1", "semver": "^6.3.0" }, "dependencies": { @@ -18681,9 +18747,9 @@ } }, "@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz", + "integrity": "sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ==", "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -18694,17 +18760,16 @@ "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, "requires": { "@babel/types": "^7.16.7" } }, "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", "requires": { - "@babel/compat-data": "^7.16.4", + "@babel/compat-data": "^7.17.7", "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" @@ -18726,21 +18791,12 @@ } }, "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz", + "integrity": "sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==", "requires": { - "@babel/helper-get-function-arity": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-hoist-variables": { @@ -18760,13 +18816,13 @@ } }, "@babel/helper-module-transforms": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz", - "integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", @@ -18780,11 +18836,11 @@ "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==" }, "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.17.0" } }, "@babel/helper-split-export-declaration": { @@ -18806,12 +18862,12 @@ "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==" }, "@babel/helpers": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.2.tgz", - "integrity": "sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "requires": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.0", + "@babel/traverse": "^7.17.9", "@babel/types": "^7.17.0" } }, @@ -18826,9 +18882,9 @@ } }, "@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==" + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz", + "integrity": "sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==" }, "@babel/plugin-syntax-jsx": { "version": "7.16.7", @@ -18851,7 +18907,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz", "integrity": "sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -18923,17 +18978,17 @@ } }, "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz", + "integrity": "sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==", "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", + "@babel/generator": "^7.17.9", "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", + "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", + "@babel/parser": "^7.17.9", "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" @@ -21896,7 +21951,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -21928,7 +21982,6 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.5.tgz", "integrity": "sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -24159,9 +24212,9 @@ "dev": true }, "electron": { - "version": "18.0.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-18.0.1.tgz", - "integrity": "sha512-8y3nxmK+v/tiuaR8yd4K83ApHxgomMIPAEl3J+2Jfv/D5G6M3KnvxNlNiNoTXI8uOegfmoqiDm5/2xlWFLzfLQ==", + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-18.0.3.tgz", + "integrity": "sha512-QRUZkGL8O/8CyDmTLSjBeRsZmGTPlPVeWnnpkdNqgHYYaOc/A881FKMiNzvQ9Cj0a+rUavDdwBUfUL82U3Ay7w==", "dev": true, "requires": { "@electron/get": "^1.13.0", @@ -25428,11 +25481,10 @@ } }, "eslint-plugin-react": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz", - "integrity": "sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz", + "integrity": "sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==", "dev": true, - "peer": true, "requires": { "array-includes": "^3.1.4", "array.prototype.flatmap": "^1.2.5", @@ -25455,7 +25507,6 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, - "peer": true, "requires": { "esutils": "^2.0.2" } @@ -25465,7 +25516,6 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", "dev": true, - "peer": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" @@ -25475,8 +25525,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "peer": true + "dev": true } } }, @@ -28273,12 +28322,9 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" }, "jsonfile": { "version": "6.1.0", @@ -28314,7 +28360,6 @@ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", "dev": true, - "peer": true, "requires": { "array-includes": "^3.1.3", "object.assign": "^4.1.2" @@ -29499,7 +29544,6 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -29511,7 +29555,6 @@ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.0.tgz", "integrity": "sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==", "dev": true, - "peer": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.19.1" @@ -29522,7 +29565,6 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", @@ -29681,6 +29723,12 @@ "arch": "^2.1.0" } }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", + "dev": true + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -30229,6 +30277,70 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha1-287g7p3nI15X95xW186UZBpp7sY=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -30340,8 +30452,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true, - "optional": true + "dev": true }, "psl": { "version": "1.8.0", @@ -31412,6 +31523,16 @@ "integrity": "sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A=", "dev": true }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -31587,7 +31708,6 @@ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.6.tgz", "integrity": "sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==", "dev": true, - "peer": true, "requires": { "call-bind": "^1.0.2", "define-properties": "^1.1.3", diff --git a/package.json b/package.json index bb7cdebc9..d5c0ce92a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "chainner", "productName": "chaiNNer", - "version": "0.5.1", + "version": "0.5.2", "description": "A flowchart based image processing GUI", "main": ".webpack/main", "scripts": { @@ -13,8 +13,12 @@ "make-win-zip": "cross-env NODE_ENV=production electron-forge make --targets @electron-forge/maker-zip --platform win32", "make-mac-zip": "cross-env NODE_ENV=production electron-forge make --targets @electron-forge/maker-zip --platform darwin", "publish": "cross-env NODE_ENV=production electron-forge publish", - "lint": "eslint . --fix" + "lint": "eslint . --ext \".js,.jsx\" && black --check ./backend", + "lint-fix": "eslint . --fix --ext \".js,.jsx\" && black ./backend" }, + "pre-commit": [ + "lint" + ], "keywords": [], "author": { "name": "Joey Ballentine", @@ -122,18 +126,21 @@ "concurrently": "^6.5.1", "cross-env": "^7.0.3", "css-loader": "^6.2.0", - "electron": "^18.0.1", + "electron": "^18.0.3", "electron-installer-common": "^0.10.3", "eslint": "^7.32.0", "eslint-config-airbnb": "^18.2.1", + "eslint-plugin-react": "^7.29.4", "file-loader": "^6.2.0", "image-webpack-loader": "^8.1.0", "node-loader": "^2.0.0", + "pre-commit": "^1.2.2", "react-refresh": "^0.11.0", "semver-regex": ">=3.1.3", "style-loader": "^3.2.1" }, "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.17.3", "@chakra-ui/icons": "^1.0.15", "@chakra-ui/react": "^1.8.6", "@emotion/react": "^11.8.1", @@ -163,4 +170,4 @@ "use-http": "^1.0.26", "uuid": "^8.3.2" } -} \ No newline at end of file +} diff --git a/src/app.jsx b/src/app.jsx index b149cd018..0b94d9ef5 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -2,7 +2,7 @@ import { Box, Center, ChakraProvider, ColorModeScript, Spinner, } from '@chakra-ui/react'; import { ipcRenderer } from 'electron'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; // eslint-disable-next-line import/extensions import './global.css'; // eslint-disable-next-line import/extensions @@ -22,8 +22,14 @@ const App = () => { if (!port) { Component = () => ( - -
+ +
diff --git a/src/helpers/CustomEdge.jsx b/src/components/CustomEdge.jsx similarity index 53% rename from src/helpers/CustomEdge.jsx rename to src/components/CustomEdge.jsx index 40a72c65b..458a61a65 100644 --- a/src/helpers/CustomEdge.jsx +++ b/src/components/CustomEdge.jsx @@ -1,19 +1,17 @@ -/* eslint-disable import/extensions */ -/* eslint-disable react/prop-types */ import { DeleteIcon } from '@chakra-ui/icons'; import { Center, IconButton, useColorModeValue, } from '@chakra-ui/react'; -import React, { +import { memo, useCallback, useContext, useMemo, useState, } from 'react'; import { getBezierPath, getEdgeCenter, } from 'react-flow-renderer'; import { useDebouncedCallback } from 'use-debounce'; -import getNodeAccentColors from './getNodeAccentColors'; -import { GlobalContext } from './GlobalNodeState.jsx'; -import shadeColor from './shadeColor.js'; +import { GlobalContext } from '../helpers/contexts/GlobalNodeState.jsx'; +import getNodeAccentColors from '../helpers/getNodeAccentColors'; +import shadeColor from '../helpers/shadeColor.js'; const EdgeWrapper = memo(({ id, @@ -28,14 +26,14 @@ const EdgeWrapper = memo(({ }) => ( )); @@ -54,7 +52,9 @@ const CustomEdge = memo(({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition, }), [sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition]); - const { removeEdgeById, nodes, edges } = useContext(GlobalContext); + const { + removeEdgeById, nodes, edges, useHoveredNode, + } = useContext(GlobalContext); const edge = useMemo(() => edges.find((e) => e.id === id), []); const parentNode = useMemo(() => nodes.find((n) => edge.source === n.id), []); @@ -97,82 +97,86 @@ const CustomEdge = memo(({ setIsHovered(false); }, 7500); + const [, setHoveredNode] = useHoveredNode; + return ( - <> - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - onMouseOver={() => hoverTimeout()} - // onMouseOut={() => setIsHovered(false)} + { + if (parentNode) { + setHoveredNode(parentNode.parentNode); + } + }} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onMouseOver={() => hoverTimeout()} + > + + + - - - -
} + size="sm" + onClick={() => removeEdgeById(id)} > - } - onClick={() => removeEdgeById(id)} - isRound - size="sm" - borderColor={useColorModeValue('gray.100', 'gray.800')} - borderWidth={2} - borderRadius={100} - > - × - -
-
-
- + × + +
+ + ); }); diff --git a/src/components/CustomIcons.jsx b/src/components/CustomIcons.jsx index 7c7c8cef9..38501cca3 100644 --- a/src/components/CustomIcons.jsx +++ b/src/components/CustomIcons.jsx @@ -1,7 +1,6 @@ /* eslint-disable global-require */ import { createIcon } from '@chakra-ui/icons'; import { Icon } from '@chakra-ui/react'; -import React from 'react'; import * as bs from 'react-icons/bs'; import * as cg from 'react-icons/cg'; import * as im from 'react-icons/im'; @@ -18,35 +17,17 @@ export const NumPyIcon = createIcon({ path: ( <> ), }); -// export const PyTorchIcon = createIcon({ -// displayName: 'PyTorchIcon', -// viewBox: '0 0 20.97 24.99', -// // path can also be an array of elements, if you have multiple paths, lines, shapes, etc. -// path: ( -// <> -// -// -// -// ), -// }); - export const PyTorchIcon = createIcon({ displayName: 'PyTorchIcon', viewBox: '0 0 20.97 24.99', @@ -55,13 +36,13 @@ export const PyTorchIcon = createIcon({ <> ), @@ -74,16 +55,16 @@ export const OpenCVIcon = createIcon({ path: ( <> ), @@ -171,22 +152,82 @@ export const IconFactory = (icon, accentColor) => { } switch (icon) { case 'NumPy': - return ; + return ( + + ); case 'PyTorch': - return ; + return ( + + ); case 'Image': // return ; - return ; + return ( + + ); case 'Image (Utility)': - return ; + return ( + + ); case 'Image (Effect)': - return ; + return ( + + ); case 'ONNX': - return ; + return ( + + ); case 'NCNN': - return ; + return ( + + ); case 'Utility': - return ; + return ( + + ); default: // nothing } @@ -197,7 +238,18 @@ export const IconFactory = (icon, accentColor) => { return ; } const libraryIcon = library[icon]; - return ; + return ( + + ); }; // color={shadeColor(accentColor, 100)} diff --git a/src/components/DependencyManager.jsx b/src/components/DependencyManager.jsx index a2a449f0e..c785f0d23 100644 --- a/src/components/DependencyManager.jsx +++ b/src/components/DependencyManager.jsx @@ -1,5 +1,3 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable import/extensions */ import { DeleteIcon, DownloadIcon } from '@chakra-ui/icons'; import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, @@ -11,12 +9,12 @@ import { } from '@chakra-ui/react'; import { exec, spawn } from 'child_process'; import { ipcRenderer } from 'electron'; -import React, { +import { memo, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react'; import semver from 'semver'; +import { SettingsContext } from '../helpers/contexts/SettingsContext.jsx'; import getAvailableDeps from '../helpers/dependencies.js'; -import { GlobalContext } from '../helpers/GlobalNodeState.jsx'; import pipInstallWithProgress from '../helpers/pipInstallWithProgress.js'; const checkSemver = (v1, v2) => { @@ -31,7 +29,7 @@ const checkSemver = (v1, v2) => { const DependencyManager = ({ isOpen, onClose, onPipListUpdate = () => {} }) => { const { useIsSystemPython, - } = useContext(GlobalContext); + } = useContext(SettingsContext); const [isSystemPython] = useIsSystemPython; @@ -91,7 +89,7 @@ const DependencyManager = ({ isOpen, onClose, onPipListUpdate = () => {} }) => { ...deps, pythonVersion: pKeys.version, }); - exec(`${pKeys.python} -m pip list --disable-pip-version-check`, (error, stdout, stderr) => { + exec(`${pKeys.python} -m pip list --disable-pip-version-check`, (error, stdout) => { if (error) { setIsLoadingPipList(false); return; @@ -210,13 +208,13 @@ const DependencyManager = ({ isOpen, onClose, onPipListUpdate = () => {} }) => { return ( <> @@ -225,51 +223,81 @@ const DependencyManager = ({ isOpen, onClose, onPipListUpdate = () => {} }) => { - }> - }> - - + } + w="full" + > + } + w="full" + > + + {`GPU (${isNvidiaAvailable ? nvidiaGpuName : gpuInfo[0] ?? 'No GPU Available'})`} - - + + {`Python (${deps.pythonVersion}) [${isSystemPython ? 'System' : 'Integrated'}]`} {isLoadingPipList ? : availableDeps.map((dep) => ( - - + + {/* {`Installed version: ${dep.version ?? 'None'}`} */} - + {`${dep.name} (${pipList[dep.packageName] ? pipList[dep.packageName] : 'not installed'})`} {pipList[dep.packageName] ? ( @@ -278,32 +306,45 @@ const DependencyManager = ({ isOpen, onClose, onPipListUpdate = () => {} }) => { : ( )} {isRunningShell && installingPackage?.name === dep.name && ( -
- +
+
)} ))} - +

- + Console Output @@ -311,18 +352,13 @@ const DependencyManager = ({ isOpen, onClose, onPipListUpdate = () => {} }) => {