The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .envrc
├── .eslintrc.js
├── .gitattributes
├── .github
    ├── FUNDING.yml
    ├── ISSUE_TEMPLATE
    │   ├── bug_report.md
    │   ├── feature_request.md
    │   └── other.md
    └── workflows
    │   ├── build-site.yml
    │   ├── lint-backend.yml
    │   ├── lint-frontend.yml
    │   ├── make.yml
    │   ├── release-test.yml
    │   ├── release.yml
    │   ├── test-backend.yml
    │   └── test-frontend.yml
├── .gitignore
├── .prettierrc.json
├── .stylelintrc.json
├── .vscode
    ├── extensions.json
    ├── launch.json
    └── settings.json
├── LICENSE
├── README.md
├── backend
    ├── src
    │   ├── api
    │   │   ├── __init__.py
    │   │   ├── api.py
    │   │   ├── group.py
    │   │   ├── input.py
    │   │   ├── iter.py
    │   │   ├── lazy.py
    │   │   ├── node_check.py
    │   │   ├── node_context.py
    │   │   ├── node_data.py
    │   │   ├── output.py
    │   │   ├── settings.py
    │   │   └── types.py
    │   ├── chain
    │   │   ├── cache.py
    │   │   ├── chain.py
    │   │   ├── input.py
    │   │   ├── json.py
    │   │   └── optimize.py
    │   ├── custom_types.py
    │   ├── dependencies
    │   │   ├── __init__.py
    │   │   ├── install_server_deps.py
    │   │   ├── store.py
    │   │   └── whls
    │   │   │   ├── README.md
    │   │   │   ├── Sanic-Cors
    │   │   │       ├── LICENSE
    │   │   │       └── Sanic_Cors-2.2.0-py2.py3-none-any.whl
    │   │   │   ├── aiofiles
    │   │   │       ├── LICENSE
    │   │   │       └── aiofiles-23.1.0-py3-none-any.whl
    │   │   │   ├── chainner-pip
    │   │   │       └── chainner_pip-23.2.0-py3-none-any.whl
    │   │   │   ├── html5tagger
    │   │   │       ├── LICENSE
    │   │   │       └── html5tagger-1.3.0-py3-none-any.whl
    │   │   │   ├── pynvml
    │   │   │       ├── LICENSE
    │   │   │       └── pynvml-11.5.0-py3-none-any.whl
    │   │   │   ├── sanic-routing
    │   │   │       ├── LICENSE
    │   │   │       └── sanic_routing-22.8.0-py3-none-any.whl
    │   │   │   ├── sanic
    │   │   │       ├── LICENSE
    │   │   │       └── sanic-23.3.0-py3-none-any.whl
    │   │   │   ├── tracerite
    │   │   │       ├── LICENSE
    │   │   │       └── tracerite-1.1.0-py3-none-any.whl
    │   │   │   └── typing_extensions
    │   │   │       ├── LICENSE
    │   │   │       └── typing_extensions-4.6.3-py3-none-any.whl
    │   ├── events.py
    │   ├── fonts
    │   │   ├── Apache License.txt
    │   │   ├── Roboto-Light.ttf
    │   │   └── Roboto
    │   │   │   ├── LICENSE.txt
    │   │   │   ├── Roboto-Bold.ttf
    │   │   │   ├── Roboto-BoldItalic.ttf
    │   │   │   ├── Roboto-Italic.ttf
    │   │   │   └── Roboto-Regular.ttf
    │   ├── gpu.py
    │   ├── navi.py
    │   ├── nodes
    │   │   ├── __init__.py
    │   │   ├── condition.py
    │   │   ├── groups.py
    │   │   ├── impl
    │   │   │   ├── __init__.py
    │   │   │   ├── blend.py
    │   │   │   ├── caption.py
    │   │   │   ├── cas.py
    │   │   │   ├── color
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── color.py
    │   │   │   │   ├── convert.py
    │   │   │   │   ├── convert_data.py
    │   │   │   │   └── convert_model.py
    │   │   │   ├── color_transfer
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── linear_histogram.py
    │   │   │   │   ├── mean_std.py
    │   │   │   │   └── principal_color.py
    │   │   │   ├── dds
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── format.py
    │   │   │   │   └── texconv.py
    │   │   │   ├── dithering
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── constants.py
    │   │   │   │   └── palette.py
    │   │   │   ├── ffmpeg.py
    │   │   │   ├── gradients.py
    │   │   │   ├── image_formats.py
    │   │   │   ├── image_op.py
    │   │   │   ├── image_utils.py
    │   │   │   ├── ncnn
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── auto_split.py
    │   │   │   │   ├── model.py
    │   │   │   │   ├── optimizer.py
    │   │   │   │   ├── param_schema.json
    │   │   │   │   └── session.py
    │   │   │   ├── noise.py
    │   │   │   ├── noise_functions
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── blue.py
    │   │   │   │   ├── noise_generator.py
    │   │   │   │   ├── simplex.py
    │   │   │   │   └── value.py
    │   │   │   ├── normals
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── addition.py
    │   │   │   │   ├── edge_filter.py
    │   │   │   │   ├── height.py
    │   │   │   │   └── util.py
    │   │   │   ├── onnx
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── auto_split.py
    │   │   │   │   ├── load.py
    │   │   │   │   ├── model.py
    │   │   │   │   ├── np_tensor_utils.py
    │   │   │   │   ├── onnx_to_ncnn.py
    │   │   │   │   ├── session.py
    │   │   │   │   ├── tensorproto_utils.py
    │   │   │   │   ├── update_model_dims.py
    │   │   │   │   └── utils.py
    │   │   │   ├── pil_utils.py
    │   │   │   ├── pytorch
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── auto_split.py
    │   │   │   │   ├── convert_to_onnx_impl.py
    │   │   │   │   ├── pix_transform
    │   │   │   │   │   ├── LICENSE
    │   │   │   │   │   ├── auto_split.py
    │   │   │   │   │   ├── pix_transform.py
    │   │   │   │   │   └── pix_transform_net.py
    │   │   │   │   ├── rife
    │   │   │   │   │   ├── IFNet_HDv3_v4_14_align.py
    │   │   │   │   │   ├── LICENSE
    │   │   │   │   │   └── warplayer.py
    │   │   │   │   ├── utils.py
    │   │   │   │   └── xfeat
    │   │   │   │   │   ├── LICENSE
    │   │   │   │   │   ├── xfeat_align.py
    │   │   │   │   │   └── xfeat_arch.py
    │   │   │   ├── rembg
    │   │   │   │   ├── LICENSE.md
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── bg.py
    │   │   │   │   ├── session_base.py
    │   │   │   │   ├── session_cloth.py
    │   │   │   │   ├── session_factory.py
    │   │   │   │   └── session_simple.py
    │   │   │   ├── resize.py
    │   │   │   ├── rust_regex.py
    │   │   │   ├── tile.py
    │   │   │   ├── upscale
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── auto_split.py
    │   │   │   │   ├── auto_split_tiles.py
    │   │   │   │   ├── basic_upscale.py
    │   │   │   │   ├── convenient_upscale.py
    │   │   │   │   ├── exact_split.py
    │   │   │   │   ├── grayscale.py
    │   │   │   │   ├── passthrough.py
    │   │   │   │   ├── tile_blending.py
    │   │   │   │   └── tiler.py
    │   │   │   └── video.py
    │   │   ├── node_cache.py
    │   │   ├── properties
    │   │   │   ├── __init__.py
    │   │   │   ├── inputs
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── __system_inputs.py
    │   │   │   │   ├── file_inputs.py
    │   │   │   │   ├── generic_inputs.py
    │   │   │   │   ├── image_dropdown_inputs.py
    │   │   │   │   ├── label.py
    │   │   │   │   ├── ncnn_inputs.py
    │   │   │   │   ├── numeric_inputs.py
    │   │   │   │   ├── numpy_inputs.py
    │   │   │   │   ├── onnx_inputs.py
    │   │   │   │   └── pytorch_inputs.py
    │   │   │   └── outputs
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── file_outputs.py
    │   │   │   │   ├── generic_outputs.py
    │   │   │   │   ├── ncnn_outputs.py
    │   │   │   │   ├── numpy_outputs.py
    │   │   │   │   ├── onnx_outputs.py
    │   │   │   │   └── pytorch_outputs.py
    │   │   └── utils
    │   │   │   ├── __init__.py
    │   │   │   ├── checked_cast.py
    │   │   │   ├── format.py
    │   │   │   ├── replacement.py
    │   │   │   ├── seed.py
    │   │   │   ├── unpickler.py
    │   │   │   └── utils.py
    │   ├── packages
    │   │   ├── chaiNNer_external
    │   │   │   ├── __init__.py
    │   │   │   ├── external_stable_diffusion
    │   │   │   │   ├── __init__.py
    │   │   │   │   └── automatic1111
    │   │   │   │   │   ├── clip_interrogate.py
    │   │   │   │   │   ├── image_to_image.py
    │   │   │   │   │   ├── inpaint.py
    │   │   │   │   │   ├── outpaint.py
    │   │   │   │   │   ├── text_to_image.py
    │   │   │   │   │   └── upscale.py
    │   │   │   ├── features.py
    │   │   │   ├── util.py
    │   │   │   └── web_ui.py
    │   │   ├── chaiNNer_ncnn
    │   │   │   ├── __init__.py
    │   │   │   ├── ncnn
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── batch_processing
    │   │   │   │   │   └── load_models.py
    │   │   │   │   ├── io
    │   │   │   │   │   ├── load_model.py
    │   │   │   │   │   └── save_model.py
    │   │   │   │   ├── processing
    │   │   │   │   │   └── upscale_image.py
    │   │   │   │   └── utility
    │   │   │   │   │   ├── get_model_scale.py
    │   │   │   │   │   └── interpolate_models.py
    │   │   │   └── settings.py
    │   │   ├── chaiNNer_onnx
    │   │   │   ├── __init__.py
    │   │   │   ├── onnx
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── batch_processing
    │   │   │   │   │   └── load_models.py
    │   │   │   │   ├── io
    │   │   │   │   │   ├── load_model.py
    │   │   │   │   │   └── save_model.py
    │   │   │   │   ├── processing
    │   │   │   │   │   ├── remove_background.py
    │   │   │   │   │   └── upscale_image.py
    │   │   │   │   └── utility
    │   │   │   │   │   ├── convert_to_ncnn.py
    │   │   │   │   │   ├── get_model_info.py
    │   │   │   │   │   ├── interpolate_models.py
    │   │   │   │   │   └── optimize_model.py
    │   │   │   └── settings.py
    │   │   ├── chaiNNer_pytorch
    │   │   │   ├── __init__.py
    │   │   │   ├── pytorch
    │   │   │   │   ├── __init__.py
    │   │   │   │   ├── io
    │   │   │   │   │   ├── load_model.py
    │   │   │   │   │   └── save_model.py
    │   │   │   │   ├── iteration
    │   │   │   │   │   └── load_models.py
    │   │   │   │   ├── processing
    │   │   │   │   │   ├── align_image_to_reference.py
    │   │   │   │   │   ├── guided_upscale.py
    │   │   │   │   │   ├── inpaint.py
    │   │   │   │   │   ├── upscale_image.py
    │   │   │   │   │   └── wavelet_color_fix.py
    │   │   │   │   ├── restoration
    │   │   │   │   │   └── upscale_face.py
    │   │   │   │   └── utility
    │   │   │   │   │   ├── convert_to_ncnn.py
    │   │   │   │   │   ├── convert_to_onnx.py
    │   │   │   │   │   ├── get_model_info.py
    │   │   │   │   │   └── interpolate_models.py
    │   │   │   └── settings.py
    │   │   └── chaiNNer_standard
    │   │   │   ├── __init__.py
    │   │   │   ├── image
    │   │   │       ├── __init__.py
    │   │   │       ├── batch_processing
    │   │   │       │   ├── load_images.py
    │   │   │       │   ├── merge_spritesheet.py
    │   │   │       │   └── split_spritesheet.py
    │   │   │       ├── create_images
    │   │   │       │   ├── create_checkerboard.py
    │   │   │       │   ├── create_color.py
    │   │   │       │   ├── create_colorwheel.py
    │   │   │       │   ├── create_gradient.py
    │   │   │       │   ├── create_noise.py
    │   │   │       │   └── text_as_image.py
    │   │   │       ├── io
    │   │   │       │   ├── load_image.py
    │   │   │       │   ├── save_image.py
    │   │   │       │   ├── view_image.py
    │   │   │       │   └── view_image_external.py
    │   │   │       └── video_frames
    │   │   │       │   ├── load_video.py
    │   │   │       │   └── save_video.py
    │   │   │   ├── image_adjustment
    │   │   │       ├── __init__.py
    │   │   │       ├── adjustments
    │   │   │       │   ├── brightness_and_contrast.py
    │   │   │       │   ├── clamp.py
    │   │   │       │   ├── color_levels.py
    │   │   │       │   ├── hue_and_saturation.py
    │   │   │       │   ├── invert_color.py
    │   │   │       │   ├── opacity.py
    │   │   │       │   └── stretch_contrast.py
    │   │   │       ├── arithmetic
    │   │   │       │   ├── add.py
    │   │   │       │   ├── divide.py
    │   │   │       │   ├── multiply.py
    │   │   │       │   └── premultiplied_alpha.py
    │   │   │       ├── gamma
    │   │   │       │   ├── gamma.py
    │   │   │       │   └── log_to_linear.py
    │   │   │       └── threshold
    │   │   │       │   ├── generate_threshold.py
    │   │   │       │   ├── threshold.py
    │   │   │       │   └── threshold_adaptive.py
    │   │   │   ├── image_channel
    │   │   │       ├── __init__.py
    │   │   │       ├── all
    │   │   │       │   ├── combine_rgba.py
    │   │   │       │   ├── merge_channels.py
    │   │   │       │   └── separate_rgba.py
    │   │   │       ├── misc
    │   │   │       │   ├── alpha_matting.py
    │   │   │       │   ├── chroma_key.py
    │   │   │       │   └── fill_alpha.py
    │   │   │       └── transparency
    │   │   │       │   ├── merge_transparency.py
    │   │   │       │   └── split_transparency.py
    │   │   │   ├── image_dimension
    │   │   │       ├── __init__.py
    │   │   │       ├── border
    │   │   │       │   └── pad.py
    │   │   │       ├── crop
    │   │   │       │   ├── crop.py
    │   │   │       │   ├── crop_border.py
    │   │   │       │   └── crop_to_content.py
    │   │   │       ├── resize
    │   │   │       │   ├── resize.py
    │   │   │       │   ├── resize_pixel_art.py
    │   │   │       │   └── resize_to_side.py
    │   │   │       └── utility
    │   │   │       │   ├── get_bounding_box.py
    │   │   │       │   └── get_dimensions.py
    │   │   │   ├── image_filter
    │   │   │       ├── __init__.py
    │   │   │       ├── blur
    │   │   │       │   ├── box_blur.py
    │   │   │       │   ├── gaussian_blur.py
    │   │   │       │   ├── lens_blur.py
    │   │   │       │   ├── median_blur.py
    │   │   │       │   └── surface_blur.py
    │   │   │       ├── correction
    │   │   │       │   ├── average_color_fix.py
    │   │   │       │   └── color_transfer.py
    │   │   │       ├── miscellaneous
    │   │   │       │   ├── canny_edge_detection.py
    │   │   │       │   ├── convolve.py
    │   │   │       │   ├── dilate.py
    │   │   │       │   ├── distance_transform.py
    │   │   │       │   ├── edge_detection.py
    │   │   │       │   ├── erode.py
    │   │   │       │   ├── high_pass.py
    │   │   │       │   └── pixelate.py
    │   │   │       ├── noise
    │   │   │       │   ├── add_noise.py
    │   │   │       │   └── denoise.py
    │   │   │       ├── quantize
    │   │   │       │   ├── dither.py
    │   │   │       │   ├── dither_palette.py
    │   │   │       │   └── quantize_to_reference.py
    │   │   │       └── sharpen
    │   │   │       │   ├── high_boost_filter.py
    │   │   │       │   └── unsharp_mask.py
    │   │   │   ├── image_utility
    │   │   │       ├── __init__.py
    │   │   │       ├── compositing
    │   │   │       │   ├── add_caption.py
    │   │   │       │   ├── blend_images.py
    │   │   │       │   ├── stack_images.py
    │   │   │       │   └── z_stack_images.py
    │   │   │       ├── miscellaneous
    │   │   │       │   ├── apply_palette.py
    │   │   │       │   ├── change_color_model.py
    │   │   │       │   ├── generate_hash.py
    │   │   │       │   ├── image_metrics.py
    │   │   │       │   ├── image_statistics.py
    │   │   │       │   ├── inpaint.py
    │   │   │       │   ├── palette_from_image.py
    │   │   │       │   └── pick_color.py
    │   │   │       └── modification
    │   │   │       │   ├── flip.py
    │   │   │       │   ├── rotate.py
    │   │   │       │   └── shift.py
    │   │   │   ├── material_textures
    │   │   │       ├── __init__.py
    │   │   │       ├── conversion
    │   │   │       │   ├── metal_to_specular.py
    │   │   │       │   └── specular_to_metal.py
    │   │   │       └── normal_map
    │   │   │       │   ├── add_normals.py
    │   │   │       │   ├── balance_normals.py
    │   │   │       │   ├── convert_normals.py
    │   │   │       │   ├── normal_map_generator.py
    │   │   │       │   ├── normalize_normals.py
    │   │   │       │   └── scale_normals.py
    │   │   │   └── utility
    │   │   │       ├── __init__.py
    │   │   │       ├── clipboard
    │   │   │           └── copy_to_clipboard.py
    │   │   │       ├── color
    │   │   │           ├── color.py
    │   │   │           ├── color_from.py
    │   │   │           └── separate_color.py
    │   │   │       ├── directory
    │   │   │           ├── directory_go_into.py
    │   │   │           ├── directory_go_up.py
    │   │   │           └── directory_to_text.py
    │   │   │       ├── math
    │   │   │           ├── accumulate.py
    │   │   │           ├── compare.py
    │   │   │           ├── logic_operation.py
    │   │   │           ├── math.py
    │   │   │           └── round.py
    │   │   │       ├── random
    │   │   │           ├── derive_seed.py
    │   │   │           └── random_number.py
    │   │   │       ├── text
    │   │   │           ├── note.py
    │   │   │           ├── regex_find.py
    │   │   │           ├── regex_replace.py
    │   │   │           ├── text_append.py
    │   │   │           ├── text_length.py
    │   │   │           ├── text_padding.py
    │   │   │           ├── text_pattern.py
    │   │   │           ├── text_replace.py
    │   │   │           └── text_slice.py
    │   │   │       └── value
    │   │   │           ├── conditional.py
    │   │   │           ├── directory.py
    │   │   │           ├── execution_number.py
    │   │   │           ├── number.py
    │   │   │           ├── parse_number.py
    │   │   │           ├── pass_through.py
    │   │   │           ├── percent.py
    │   │   │           ├── range.py
    │   │   │           ├── resolutions.py
    │   │   │           ├── switch.py
    │   │   │           └── text.py
    │   ├── process.py
    │   ├── progress_controller.py
    │   ├── response.py
    │   ├── run.py
    │   ├── server.py
    │   ├── server_config.py
    │   ├── server_host.py
    │   ├── server_process_helper.py
    │   ├── system.py
    │   ├── texconv
    │   │   ├── LICENSE
    │   │   └── texconv.exe
    │   └── util.py
    └── tests
    │   ├── test_dummy.py
    │   └── test_util.py
├── docs
    ├── CONTRIBUTING.md
    ├── FAQ.md
    ├── assets
    │   ├── banner.png
    │   ├── input-override.png
    │   ├── screenshot.png
    │   └── simple_screenshot.png
    ├── cli.md
    ├── data-representation.md
    ├── navi.md
    ├── nodes.md
    └── troubleshooting.md
├── forge.config.js
├── index.html
├── package-lock.json
├── package.json
├── patches
    └── @electron-forge+plugin-vite+7.4.0.patch
├── pyproject.toml
├── pyrightconfig.json
├── requirements.txt
├── src
    ├── common
    │   ├── 2d.ts
    │   ├── Backend.ts
    │   ├── CategoryMap.ts
    │   ├── IdSet.ts
    │   ├── PassthroughMap.ts
    │   ├── SchemaInputsMap.ts
    │   ├── SchemaMap.ts
    │   ├── Validity.ts
    │   ├── color-json-util.ts
    │   ├── common-types.ts
    │   ├── formatExecutionErrorMessage.ts
    │   ├── group-inputs.ts
    │   ├── i18n.ts
    │   ├── input-override-common.ts
    │   ├── links.ts
    │   ├── locales
    │   │   └── en
    │   │   │   └── translation.json
    │   ├── log.ts
    │   ├── migrations-legacy.js
    │   ├── migrations.ts
    │   ├── nodes
    │   │   ├── EdgeState.ts
    │   │   ├── TypeState.ts
    │   │   ├── checkFeatures.ts
    │   │   ├── checkNodeValidity.ts
    │   │   ├── condition.ts
    │   │   ├── connectedInputs.ts
    │   │   ├── disabled.ts
    │   │   ├── groupStacks.ts
    │   │   ├── inputCondition.ts
    │   │   ├── keyInfo.ts
    │   │   ├── lineage.ts
    │   │   ├── optimize.ts
    │   │   ├── parseFunctionDefinitions.ts
    │   │   ├── sideEffect.ts
    │   │   ├── sort.ts
    │   │   └── toBackendJson.ts
    │   ├── rust-regex.ts
    │   ├── safeIpc.ts
    │   ├── settings
    │   │   ├── migration.ts
    │   │   └── settings.ts
    │   ├── types
    │   │   ├── assign.ts
    │   │   ├── chainner-builtin.ts
    │   │   ├── chainner-scope.ts
    │   │   ├── explain.ts
    │   │   ├── function.ts
    │   │   ├── json.ts
    │   │   ├── mismatch.ts
    │   │   ├── pretty.ts
    │   │   └── util.ts
    │   ├── ui
    │   │   ├── error.ts
    │   │   ├── interrupt.ts
    │   │   └── progress.ts
    │   ├── util.ts
    │   └── version.ts
    ├── custom.d.ts
    ├── globals.d.ts
    ├── i18next.d.ts
    ├── main
    │   ├── SaveFile.ts
    │   ├── arguments.ts
    │   ├── backend
    │   │   ├── process.ts
    │   │   └── setup.ts
    │   ├── childProc.ts
    │   ├── cli
    │   │   ├── create.ts
    │   │   ├── exit.ts
    │   │   └── run.ts
    │   ├── env.ts
    │   ├── fileWatcher.ts
    │   ├── gui
    │   │   ├── create.ts
    │   │   ├── main-window.ts
    │   │   └── menu.ts
    │   ├── i18n.ts
    │   ├── input-override.ts
    │   ├── main.ts
    │   ├── platform.ts
    │   ├── preload.ts
    │   ├── python
    │   │   ├── checkPythonPaths.ts
    │   │   ├── integratedPython.ts
    │   │   └── version.ts
    │   ├── safeIpc.ts
    │   ├── setting-storage.ts
    │   ├── squirrel.ts
    │   ├── systemInfo.ts
    │   └── util.ts
    ├── public
    │   ├── Info.plist
    │   ├── dmg-background.png
    │   ├── fonts
    │   │   ├── Noto Emoji
    │   │   │   ├── NotoEmoji-VariableFont_wght.ttf
    │   │   │   └── OFL.txt
    │   │   ├── Open Sans
    │   │   │   ├── OFL.txt
    │   │   │   ├── OpenSans-Italic-VariableFont_wdth,wght.ttf
    │   │   │   └── OpenSans-VariableFont_wdth,wght.ttf
    │   │   └── Roboto Mono
    │   │   │   ├── LICENSE.txt
    │   │   │   └── RobotoMono-VariableFont_wght.ttf
    │   ├── icons
    │   │   ├── cross_platform
    │   │   │   ├── icon.icns
    │   │   │   ├── icon.ico
    │   │   │   └── icon.png
    │   │   ├── mac
    │   │   │   ├── file_chn.icns
    │   │   │   └── icon.icns
    │   │   ├── png
    │   │   │   ├── 1024x1024.png
    │   │   │   ├── 128x128.png
    │   │   │   ├── 16x16.png
    │   │   │   ├── 24x24.png
    │   │   │   ├── 256x256.png
    │   │   │   ├── 32x32.png
    │   │   │   ├── 48x48.png
    │   │   │   ├── 512x512.png
    │   │   │   └── 64x64.png
    │   │   └── win
    │   │   │   ├── icon.ico
    │   │   │   └── installing_loop.gif
    │   └── splash_imgs
    │   │   ├── background.png
    │   │   └── front.png
    └── renderer
    │   ├── app.tsx
    │   ├── colors.scss
    │   ├── components
    │       ├── CustomEdge
    │       │   ├── CustomEdge.scss
    │       │   └── CustomEdge.tsx
    │       ├── CustomIcons.tsx
    │       ├── DependencyManagerButton.tsx
    │       ├── Handle.tsx
    │       ├── Header
    │       │   ├── AppInfo.tsx
    │       │   ├── ExecutionButtons.tsx
    │       │   ├── Header.tsx
    │       │   └── KoFiButton.tsx
    │       ├── HistoryProvider.tsx
    │       ├── IfVisible.tsx
    │       ├── Markdown.tsx
    │       ├── NodeDocumentation
    │       │   ├── ConditionExplanation.tsx
    │       │   ├── DropDownOptions.tsx
    │       │   ├── HighlightContainer.tsx
    │       │   ├── NodeDocs.tsx
    │       │   ├── NodeDocumentationModal.tsx
    │       │   ├── NodeExample.tsx
    │       │   ├── NodesList.tsx
    │       │   └── SchemaLink.tsx
    │       ├── NodeSelectorPanel
    │       │   ├── FavoritesAccordionItem.tsx
    │       │   ├── NodeRepresentative.tsx
    │       │   ├── NodeSelectorPanel.tsx
    │       │   ├── RegularAccordionItem.tsx
    │       │   ├── SubcategoryHeading.tsx
    │       │   └── TextBox.tsx
    │       ├── PaneNodeSearchMenu.tsx
    │       ├── ReactFlowBox.tsx
    │       ├── SearchBar.tsx
    │       ├── SettingsModal.tsx
    │       ├── SystemStats.tsx
    │       ├── TypeTag.tsx
    │       ├── chaiNNerLogo.tsx
    │       ├── groups
    │       │   ├── ConditionalGroup.tsx
    │       │   ├── FromToDropdownsGroup.tsx
    │       │   ├── Group.tsx
    │       │   ├── IconSetGroup.tsx
    │       │   ├── LinkedInputsGroup.tsx
    │       │   ├── MenuIconRowGroup.tsx
    │       │   ├── NcnnFileInputsGroup.tsx
    │       │   ├── OptionalInputsGroup.tsx
    │       │   ├── RequiredGroup.tsx
    │       │   ├── SeedGroup.tsx
    │       │   ├── props.ts
    │       │   └── util.ts
    │       ├── inputs
    │       │   ├── ColorInput.tsx
    │       │   ├── DirectoryInput.tsx
    │       │   ├── DropDownInput.tsx
    │       │   ├── FileInput.tsx
    │       │   ├── GenericInput.tsx
    │       │   ├── InputContainer.tsx
    │       │   ├── NumberInput.tsx
    │       │   ├── SchemaInput.tsx
    │       │   ├── SliderInput.tsx
    │       │   ├── StaticValueInput.tsx
    │       │   ├── TextInput.tsx
    │       │   ├── elements
    │       │   │   ├── AdvanceNumberInput.tsx
    │       │   │   ├── AdvancedNumberInput.scss
    │       │   │   ├── AnchorSelector.tsx
    │       │   │   ├── Checkbox.scss
    │       │   │   ├── Checkbox.tsx
    │       │   │   ├── ColorBoxButton.tsx
    │       │   │   ├── ColorCompare.tsx
    │       │   │   ├── ColorKindSelector.tsx
    │       │   │   ├── ColorPicker.tsx
    │       │   │   ├── ColorSlider.tsx
    │       │   │   ├── Dropdown.tsx
    │       │   │   ├── IconList.tsx
    │       │   │   ├── RgbHexInput.tsx
    │       │   │   ├── StyledSlider.tsx
    │       │   │   └── TabList.tsx
    │       │   └── props.ts
    │       ├── node
    │       │   ├── BreakPoint.tsx
    │       │   ├── CollapsedHandles.tsx
    │       │   ├── Node.tsx
    │       │   ├── NodeBody.tsx
    │       │   ├── NodeFooter
    │       │   │   ├── DisableSwitch.tsx
    │       │   │   ├── NodeFooter.tsx
    │       │   │   ├── Timer.tsx
    │       │   │   └── ValidityIndicator.tsx
    │       │   ├── NodeHeader.tsx
    │       │   ├── NodeInputs.tsx
    │       │   ├── NodeOutputs.tsx
    │       │   └── special
    │       │   │   └── NoteNode.tsx
    │       ├── outputs
    │       │   ├── GenericOutput.tsx
    │       │   ├── LargeImageOutput.tsx
    │       │   ├── OutputContainer.tsx
    │       │   ├── TaggedOutput.tsx
    │       │   ├── elements
    │       │   │   └── ModelDataTags.tsx
    │       │   └── props.ts
    │       └── settings
    │       │   ├── SettingContainer.tsx
    │       │   ├── SettingItem.tsx
    │       │   ├── components.tsx
    │       │   └── props.ts
    │   ├── contexts
    │       ├── AlertBoxContext.tsx
    │       ├── BackendContext.tsx
    │       ├── CollapsedNodeContext.tsx
    │       ├── ContextMenuContext.tsx
    │       ├── DependencyContext.tsx
    │       ├── ExecutionContext.tsx
    │       ├── FakeExampleContext.tsx
    │       ├── GlobalNodeState.tsx
    │       ├── HotKeyContext.tsx
    │       ├── InputContext.tsx
    │       ├── NodeDocumentationContext.tsx
    │       └── SettingsContext.tsx
    │   ├── env.ts
    │   ├── global.scss
    │   ├── helpers
    │       ├── accentColors.ts
    │       ├── canConnect.ts
    │       ├── chainProgress.ts
    │       ├── color.ts
    │       ├── colorTools.ts
    │       ├── copyAndPaste.ts
    │       ├── dataTransfer.ts
    │       ├── github.ts
    │       ├── graphUtils.ts
    │       ├── naviHelpers.ts
    │       ├── nodeScreenshot.ts
    │       ├── nodeSearchFuncs.ts
    │       ├── nodeState.ts
    │       ├── reactFlowUtil.ts
    │       ├── sliderScale.ts
    │       └── types.ts
    │   ├── hooks
    │       ├── useAsyncEffect.ts
    │       ├── useAutomaticFeatures.ts
    │       ├── useBackendEventSource.ts
    │       ├── useBatchedCallback.ts
    │       ├── useChangeCounter.ts
    │       ├── useColorModels.ts
    │       ├── useContextMenu.ts
    │       ├── useDevicePixelRatio.ts
    │       ├── useDisabled.ts
    │       ├── useEdgeMenu.tsx
    │       ├── useEventBacklog.ts
    │       ├── useHotkeys.ts
    │       ├── useInputHashes.ts
    │       ├── useInputRefactor.tsx
    │       ├── useInterval.ts
    │       ├── useIpcRendererListener.ts
    │       ├── useIsCollapsedNode.ts
    │       ├── useLastDirectory.ts
    │       ├── useLastWindowSize.ts
    │       ├── useMemo.ts
    │       ├── useNodeFavorites.ts
    │       ├── useNodeMenu.scss
    │       ├── useNodeMenu.tsx
    │       ├── useNodesMenu.tsx
    │       ├── useOpenRecent.ts
    │       ├── useOutputDataStore.ts
    │       ├── usePaneNodeSearchMenu.tsx
    │       ├── usePassthrough.ts
    │       ├── usePrevious.ts
    │       ├── useRunNode.ts
    │       ├── useSessionStorage.ts
    │       ├── useSettings.ts
    │       ├── useSourceTypeColor.ts
    │       ├── useStored.ts
    │       ├── useThemeColor.ts
    │       ├── useTypeColor.ts
    │       ├── useTypeMap.ts
    │       ├── useValidDropDownValue.ts
    │       ├── useValidity.ts
    │       └── useWatchFiles.ts
    │   ├── i18n.ts
    │   ├── index.tsx
    │   ├── main.tsx
    │   ├── renderer.ts
    │   ├── safeIpc.ts
    │   └── theme.ts
├── tests
    ├── common
    │   ├── SaveFile.test.ts
    │   ├── __snapshots__
    │   │   ├── SaveFile.test.ts.snap
    │   │   └── settings.test.ts.snap
    │   ├── chainner-scope.test.ts
    │   ├── settings.test.ts
    │   └── util.test.ts
    └── data
    │   ├── DiffusePBR.chn
    │   ├── add noise with seed edge.chn
    │   ├── big ol test.chn
    │   ├── blend-images.chn
    │   ├── box-median-blur.chn
    │   ├── canny-edge-detection.chn
    │   ├── color-transfer.chn
    │   ├── combine-rgba.chn
    │   ├── convert-onnx-update.chn
    │   ├── convert-to-ncnn.chn
    │   ├── copy-to-clipboard.chn
    │   ├── create-border-migration.chn
    │   ├── create-color-old.chn
    │   ├── create-edges.chn
    │   ├── crop-content.chn
    │   ├── crop.chn
    │   ├── empty-string-input-test.chn
    │   ├── fast-nlmeans.chn
    │   ├── gamma.chn
    │   ├── image-adjustments.chn
    │   ├── image-channels.chn
    │   ├── image-dim.chn
    │   ├── image-filters.chn
    │   ├── image-input-output.chn
    │   ├── image-iterator.chn
    │   ├── image-metrics.chn
    │   ├── image-utilities.chn
    │   ├── metal-specular.chn
    │   ├── model-scale.chn
    │   ├── ncnn.chn
    │   ├── normal-map-gen-invert.chn
    │   ├── normal-map-generator.chn
    │   ├── onnx-interpolate.chn
    │   ├── opacity.chn
    │   ├── pass-through.chn
    │   ├── pytorch-scunet.chn
    │   ├── pytorch.chn
    │   ├── resize-to-side.chn
    │   ├── rnd.chn
    │   ├── save video input.chn
    │   ├── save-image-webp-lossless.chn
    │   ├── text-as-image.chn
    │   ├── text-pattern.chn
    │   ├── unified-resize.chn
    │   ├── utilities.chn
    │   └── video-frame-iterator.chn
├── tsconfig.json
└── vite
    ├── base.config.ts
    ├── forge-types.ts
    ├── main.config.ts
    ├── preload.config.ts
    └── renderer.config.ts


/.envrc:
--------------------------------------------------------------------------------
1 | NODEVERSION=18
2 | nvmrc=~/.nvm/nvm.sh
3 | if [ -e $nvmrc ]; then
4 |   source $nvmrc
5 |   nvm use $NODEVERSION
6 | fi
7 | 
8 | PATH_add node_modules/.bin
9 | 


--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=LF
2 | 


--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
 1 | # These are supported funding model platforms
 2 | 
 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 4 | patreon: # Replace with a single Patreon username
 5 | open_collective: # Replace with a single Open Collective username
 6 | ko_fi: jballentine
 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
 9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve.
 4 | title: ''
 5 | labels: 'bug'
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | <!--
11 | Before you make open an issue, please search for your problem using the search bar here: https://github.com/chaiNNer-org/chaiNNer/issues
12 | 
13 | Many problems are reported to us multiple times, so please try to find your problem before opening a new issue.
14 | -->
15 | 
16 | **Information:**
17 | 
18 | - Chainner version: [e.g. 0.11.0]
19 | - OS: [e.g. Windows 10, macOS 10.13]
20 | 
21 | **Description**
22 | A clear and concise description of what the bug is and how to reproduce it.
23 | If applicable, add screenshots to help explain your problem.
24 | 
25 | **Logs**
26 | Logs help us to find the cause of many problems. To provide your logs, open Chainner and click _Help_ > _Open logs folder_ in the menu. Create a zip file with `main.log` and `renderer.log`, and [attach the zip file](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/attaching-files) to this issue.
27 | 
28 | Privacy information: Log files contain file paths of Chainner's application files and the files you processed with Chainner. These file paths may contain the name of your account (e.g. Users/michael/...). If your account is your real name, and you are not comfortable sharing, then do not upload your logs or replace all identifying text with something like "fake name".
29 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
 1 | ---
 2 | name: Feature request
 3 | about: Suggest a new feature or change to existing features.
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Motivation**
11 | Please explain the problem you're having/why you propose this feature. Ex. I'm always frustrated when [...]
12 | 
13 | **Description**
14 | A clear and concise description of what you want to happen.
15 | 
16 | **Alternatives**
17 | A clear and concise description of any alternative solutions or features you've considered, if applicable.
18 | 


--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/other.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Other
3 | about: A report or questions that doesn't fit in the other categories.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | 
8 | ---
9 | 


--------------------------------------------------------------------------------
/.github/workflows/build-site.yml:
--------------------------------------------------------------------------------
 1 | name: Build chaiNNer.app
 2 | 
 3 | on:
 4 |     release:
 5 |         types:
 6 |             - published
 7 |             - edited
 8 | 
 9 |     workflow_dispatch:
10 | 
11 | jobs:
12 |     build-site:
13 |         runs-on: ubuntu-latest
14 |         steps:
15 |             - name: Trigger chaiNNer.app build + deploy
16 |               uses: fjogeleit/http-request-action@v1
17 |               with:
18 |                   url: 'https://api.github.com/repos/chaiNNer-org/chaiNNer-org.github.io/dispatches'
19 |                   method: 'POST'
20 |                   customHeaders: '{"Accept": "application/vnd.github+json"}'
21 |                   bearerToken: ${{ secrets.GH_TEST_TOKEN }}
22 |                   data: '{"event_type": "webhook"}'
23 | 


--------------------------------------------------------------------------------
/.github/workflows/lint-frontend.yml:
--------------------------------------------------------------------------------
 1 | # This is a basic workflow to help you get started with Actions
 2 | 
 3 | name: Lint
 4 | 
 5 | # Controls when the workflow will run
 6 | on:
 7 |     pull_request:
 8 |         branches: ['*']
 9 |         types:
10 |             - opened
11 |             - synchronize
12 |             - closed
13 |         paths-ignore:
14 |             - 'backend/**'
15 |             - 'requirements.txt'
16 |             - '.pylintrc'
17 |             - 'README.md'
18 |     push:
19 |         branches: [main]
20 | 
21 |     # Allows you to run this workflow manually from the Actions tab
22 |     workflow_dispatch:
23 | 
24 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
25 | jobs:
26 |     frontend-lint:
27 |         runs-on: ubuntu-latest
28 |         steps:
29 |             - uses: actions/checkout@v3
30 |             - uses: actions/setup-node@v3
31 |               with:
32 |                   node-version: 16
33 |                   cache: 'npm'
34 |             - run: npm ci
35 |             - name: eslint
36 |               run: npm run lint:js-ci
37 |             - name: typescript
38 |               run: npx tsc
39 | 


--------------------------------------------------------------------------------
/.github/workflows/test-frontend.yml:
--------------------------------------------------------------------------------
 1 | name: Tests
 2 | 
 3 | # Controls when the workflow will run
 4 | on:
 5 |     pull_request:
 6 |         branches: ['*']
 7 |         types:
 8 |             - opened
 9 |             - synchronize
10 |             - closed
11 |         paths-ignore:
12 |             - 'backend/**'
13 |             - 'requirements.txt'
14 |             - '.pylintrc'
15 |             - 'README.md'
16 |     push:
17 |         branches: [main]
18 | 
19 |     # Allows you to run this workflow manually from the Actions tab
20 |     workflow_dispatch:
21 | 
22 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
23 | jobs:
24 |     frontend-tests:
25 |         runs-on: ubuntu-latest
26 |         strategy:
27 |             matrix:
28 |                 node-version: [16, 18]
29 |         steps:
30 |             - uses: actions/checkout@v3
31 |             - uses: actions/setup-node@v3
32 |               with:
33 |                   node-version: ${{ matrix.node-version }}
34 |                   cache: 'npm'
35 |             - run: npm ci
36 |             - run: npm run test:js
37 | 


--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "printWidth": 100,
 3 |     "singleQuote": true,
 4 |     "singleAttributePerLine": true,
 5 |     "tabWidth": 4,
 6 |     "overrides": [
 7 |         {
 8 |             "files": ["package.json", "package-lock.json"],
 9 |             "options": {
10 |                 "tabWidth": 2
11 |             }
12 |         }
13 |     ]
14 | }
15 | 


--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "extends": [
 3 |         "stylelint-config-standard-scss",
 4 |         "stylelint-prettier/recommended",
 5 |         "stylelint-config-prettier-scss"
 6 |     ],
 7 |     "rules": {
 8 |         "custom-property-empty-line-before": null,
 9 |         "no-descending-specificity": null,
10 |         "selector-class-pattern": "^[a-z]+(?:-[a-z]+)*(?:__[a-z]+(?:-[a-z]+)*)?
quot;
11 |     }
12 | }
13 | 


--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"recommendations": [
 3 | 		"ms-python.vscode-pylance",
 4 | 		"streetsidesoftware.code-spell-checker",
 5 | 		"dbaeumer.vscode-eslint",
 6 | 		"charliermarsh.ruff",
 7 | 		"esbenp.prettier-vscode"
 8 | 	]
 9 | }
10 | 


--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	// Use IntelliSense to learn about possible attributes.
 3 | 	// Hover to view descriptions of existing attributes.
 4 | 	// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 5 | 	"version": "0.2.0",
 6 | 	"configurations": [
 7 | 		{
 8 | 			"name": "Attach Debugger to chaiNNer",
 9 | 			"type": "python",
10 | 			"request": "attach",
11 | 			"connect": {
12 | 				"host": "localhost",
13 | 				"port": 5678
14 | 			},
15 | 			"justMyCode": true
16 | 		}
17 | 	]
18 | }
19 | 


--------------------------------------------------------------------------------
/backend/src/api/__init__.py:
--------------------------------------------------------------------------------
 1 | from .api import *
 2 | from .group import *
 3 | from .input import *
 4 | from .iter import *
 5 | from .lazy import *
 6 | from .node_context import *
 7 | from .node_data import *
 8 | from .output import *
 9 | from .settings import *
10 | from .types import *
11 | 


--------------------------------------------------------------------------------
/backend/src/api/group.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import Any, Generic, NewType, TypeVar, Union
 4 | 
 5 | from .input import BaseInput
 6 | from .types import InputId
 7 | 
 8 | T = TypeVar("T")
 9 | 
10 | 
11 | GroupId = NewType("GroupId", int)
12 | 
13 | 
14 | class GroupInfo:
15 |     def __init__(
16 |         self,
17 |         group_id: GroupId,
18 |         kind: str,
19 |         options: dict[str, Any] | None = None,
20 |     ) -> None:
21 |         self.id: GroupId = group_id
22 |         self.kind: str = kind
23 |         self.options: dict[str, Any] = {} if options is None else options
24 | 
25 | 
26 | class Group(Generic[T]):
27 |     def __init__(self, info: GroupInfo, items: list[T]) -> None:
28 |         self.info: GroupInfo = info
29 |         self.items: list[T] = items
30 | 
31 |     def to_dict(self):
32 |         return {
33 |             "id": self.info.id,
34 |             "kind": self.info.kind,
35 |             "options": self.info.options,
36 |             "items": [i.to_dict() if isinstance(i, Group) else i for i in self.items],
37 |         }
38 | 
39 | 
40 | NestedGroup = Group[Union[BaseInput, "NestedGroup"]]
41 | NestedIdGroup = Group[Union[InputId, "NestedIdGroup"]]
42 | 
43 | 
44 | # pylint: disable-next=redefined-builtin
45 | def group(kind: str, options: dict[str, Any] | None = None, id: int = -1):
46 |     info = GroupInfo(GroupId(id), kind, options)
47 | 
48 |     def ret(*items: BaseInput | NestedGroup) -> NestedGroup:
49 |         return Group(info, list(items))
50 | 
51 |     return ret
52 | 


--------------------------------------------------------------------------------
/backend/src/api/types.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import Any, Callable, Literal, NewType
 4 | 
 5 | NodeId = NewType("NodeId", str)
 6 | InputId = NewType("InputId", int)
 7 | OutputId = NewType("OutputId", int)
 8 | IterInputId = NewType("IterInputId", int)
 9 | IterOutputId = NewType("IterOutputId", int)
10 | FeatureId = NewType("FeatureId", str)
11 | 
12 | 
13 | RunFn = Callable[..., Any]
14 | 
15 | NodeKind = Literal["regularNode", "generator", "collector"]
16 | 


--------------------------------------------------------------------------------
/backend/src/custom_types.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 | 
3 | from typing import Awaitable, Callable, Union
4 | 
5 | UpdateProgressFn = Callable[[str, float, Union[float, None]], Awaitable[None]]
6 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/__init__.py


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/README.md:
--------------------------------------------------------------------------------
 1 | # Bundled Wheels
 2 | 
 3 | This is where we can store wheel files that are to be bundled with chaiNNer, in order to avoid needing to download them.
 4 | 
 5 | ## Requirements
 6 | 
 7 | Bundled wheels must be
 8 | 
 9 | 1. Reasonably small (a few MB max)
10 | 2. py3-none-any (compatible with any python version and device)
11 | 3. License compatible (allows bundling)
12 | 
13 | ## Goals
14 | 
15 | - Speed up initial start time by downloading the minimal number of wheel files from the internet
16 | - Not increase chaiNNer's bundle size too much
17 | 
18 | ## Structure
19 | 
20 | The `whls` folder shall contain individual folders, named according to the package name we use to install via pip. Inside the folder must be the .whl file as well as the project's license.
21 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/Sanic-Cors/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2016-present Sanic Community
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/Sanic-Cors/Sanic_Cors-2.2.0-py2.py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/Sanic-Cors/Sanic_Cors-2.2.0-py2.py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/aiofiles/aiofiles-23.1.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/aiofiles/aiofiles-23.1.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/chainner-pip/chainner_pip-23.2.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/chainner-pip/chainner_pip-23.2.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/html5tagger/LICENSE:
--------------------------------------------------------------------------------
 1 | This is free and unencumbered software released into the public domain.
 2 | 
 3 | Anyone is free to copy, modify, publish, use, compile, sell, or
 4 | distribute this software, either in source code form or as a compiled
 5 | binary, for any purpose, commercial or non-commercial, and by any
 6 | means.
 7 | 
 8 | In jurisdictions that recognize copyright laws, the author or authors
 9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 | 
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/html5tagger/html5tagger-1.3.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/html5tagger/html5tagger-1.3.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/pynvml/pynvml-11.5.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/pynvml/pynvml-11.5.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/sanic-routing/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2016-present Sanic Community
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/sanic-routing/sanic_routing-22.8.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/sanic-routing/sanic_routing-22.8.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/sanic/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2016-present Sanic Community
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/sanic/sanic-23.3.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/sanic/sanic-23.3.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/tracerite/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2016-present Sanic Community
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/tracerite/tracerite-1.1.0-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/tracerite/tracerite-1.1.0-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/dependencies/whls/typing_extensions/typing_extensions-4.6.3-py3-none-any.whl:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/dependencies/whls/typing_extensions/typing_extensions-4.6.3-py3-none-any.whl


--------------------------------------------------------------------------------
/backend/src/fonts/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/fonts/Roboto-Light.ttf


--------------------------------------------------------------------------------
/backend/src/fonts/Roboto/Roboto-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/fonts/Roboto/Roboto-Bold.ttf


--------------------------------------------------------------------------------
/backend/src/fonts/Roboto/Roboto-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/fonts/Roboto/Roboto-BoldItalic.ttf


--------------------------------------------------------------------------------
/backend/src/fonts/Roboto/Roboto-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/fonts/Roboto/Roboto-Italic.ttf


--------------------------------------------------------------------------------
/backend/src/fonts/Roboto/Roboto-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/fonts/Roboto/Roboto-Regular.ttf


--------------------------------------------------------------------------------
/backend/src/nodes/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/color/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/color/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/color_transfer/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/color_transfer/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/dds/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/dds/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/dithering/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/dithering/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/ncnn/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/ncnn/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/noise_functions/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/noise_functions/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/noise_functions/noise_generator.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | 
3 | import numpy as np
4 | 
5 | 
6 | class NoiseGenerator(ABC):
7 |     @abstractmethod
8 |     def evaluate(self, points: np.ndarray) -> np.ndarray: ...
9 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/normals/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/normals/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/normals/height.py:
--------------------------------------------------------------------------------
 1 | from enum import Enum
 2 | 
 3 | import numpy as np
 4 | 
 5 | from ...utils.utils import get_h_w_c
 6 | 
 7 | 
 8 | class HeightSource(Enum):
 9 |     AVERAGE_RGB = 0
10 |     MAX_RGB = 1
11 |     # 1 - ((1-r) * (1-g) * (1-b))
12 |     SCREEN_RGB = 2
13 |     RED = 3
14 |     GREEN = 4
15 |     BLUE = 5
16 |     ALPHA = 6
17 | 
18 | 
19 | def get_height_map(img: np.ndarray, source: HeightSource) -> np.ndarray:
20 |     """
21 |     Converts the given color/grayscale image to a height map.
22 |     """
23 |     h, w, c = get_h_w_c(img)
24 | 
25 |     assert c in (1, 3, 4), "Only grayscale, RGB, and RGBA images are supported"
26 | 
27 |     if source == HeightSource.ALPHA:
28 |         if c < 4:
29 |             return np.ones((h, w), dtype=np.float32)
30 |         return img[:, :, 3]
31 | 
32 |     if c == 1:
33 |         if source == HeightSource.SCREEN_RGB:
34 |             x = 1 - img
35 |             return 1 - x * x * x
36 |         return img
37 | 
38 |     r = img[:, :, 2]
39 |     g = img[:, :, 1]
40 |     b = img[:, :, 0]
41 | 
42 |     if source == HeightSource.RED:
43 |         return r
44 |     elif source == HeightSource.GREEN:
45 |         return g
46 |     elif source == HeightSource.BLUE:
47 |         return b
48 |     elif source == HeightSource.MAX_RGB:
49 |         return np.maximum(np.maximum(r, g), b)
50 |     elif source == HeightSource.AVERAGE_RGB:
51 |         return (r + g + b) / 3
52 |     elif source == HeightSource.SCREEN_RGB:
53 |         return 1 - ((1 - r) * (1 - g) * (1 - b))
54 |     else:
55 |         raise AssertionError(f"Invalid height source {source}.")
56 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/onnx/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/onnx/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/pytorch/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/pytorch/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/pytorch/pix_transform/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2019 Riccardo de Lutio
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/pytorch/rife/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright  (c)  Megvii  Inc.
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/pytorch/rife/warplayer.py:
--------------------------------------------------------------------------------
 1 | # type: ignore
 2 | import torch
 3 | 
 4 | backwarp_tenGrid = {}  # noqa: N816
 5 | 
 6 | 
 7 | def warp(tenInput, tenFlow, device):  # noqa: ANN001, N803
 8 |     k = (str(tenFlow.device), str(tenFlow.size()))
 9 |     if k not in backwarp_tenGrid:
10 |         tenHorizontal = (  # noqa: N806
11 |             torch.linspace(-1.0, 1.0, tenFlow.shape[3], device=device)
12 |             .view(1, 1, 1, tenFlow.shape[3])
13 |             .expand(tenFlow.shape[0], -1, tenFlow.shape[2], -1)
14 |         )
15 |         tenVertical = (  # noqa: N806
16 |             torch.linspace(-1.0, 1.0, tenFlow.shape[2], device=device)
17 |             .view(1, 1, tenFlow.shape[2], 1)
18 |             .expand(tenFlow.shape[0], -1, -1, tenFlow.shape[3])
19 |         )
20 |         backwarp_tenGrid[k] = torch.cat([tenHorizontal, tenVertical], 1).to(device)
21 | 
22 |     tenFlow = torch.cat(  # noqa: N806
23 |         [
24 |             tenFlow[:, 0:1, :, :] / ((tenInput.shape[3] - 1.0) / 2.0),
25 |             tenFlow[:, 1:2, :, :] / ((tenInput.shape[2] - 1.0) / 2.0),
26 |         ],
27 |         1,
28 |     )
29 | 
30 |     g = (backwarp_tenGrid[k] + tenFlow).permute(0, 2, 3, 1)
31 |     tenOutput = torch.nn.functional.grid_sample(
32 |         input=tenInput,
33 |         grid=g,
34 |         mode="bicubic",
35 |         padding_mode="border",
36 |         align_corners=True,
37 |     )
38 |     return tenOutput
39 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/rembg/LICENSE.md:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2020 Daniel Gatis
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/rembg/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Rembg code is modified from and copyright of Daniel Gatis,
3 | and can be found here: https://github.com/danielgatis/rembg
4 | """
5 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/rembg/session_base.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from abc import ABC, abstractmethod
 4 | 
 5 | import numpy as np
 6 | import onnxruntime as ort
 7 | 
 8 | from nodes.impl.resize import ResizeFilter, resize
 9 | 
10 | 
11 | class BaseSession(ABC):
12 |     def __init__(
13 |         self,
14 |         inner_session: ort.InferenceSession,
15 |         mean: tuple[float, float, float],
16 |         std: tuple[float, float, float],
17 |         size: tuple[int, int],
18 |     ):
19 |         self.inner_session = inner_session
20 |         self.mean = mean
21 |         self.std = std
22 |         self.size = size
23 | 
24 |     def normalize(self, img: np.ndarray) -> dict[str, np.ndarray]:
25 |         img = resize(img, self.size, ResizeFilter.LANCZOS)
26 | 
27 |         tmp_img = np.zeros((img.shape[0], img.shape[1], 3))
28 |         tmp_img[:, :, 0] = (img[:, :, 0] - self.mean[0]) / self.std[0]
29 |         tmp_img[:, :, 1] = (img[:, :, 1] - self.mean[1]) / self.std[1]
30 |         tmp_img[:, :, 2] = (img[:, :, 2] - self.mean[2]) / self.std[2]
31 | 
32 |         tmp_img = tmp_img.transpose((2, 0, 1))
33 | 
34 |         model_input_name = self.inner_session.get_inputs()[0].name
35 | 
36 |         return {model_input_name: np.expand_dims(tmp_img, 0).astype(np.float32)}
37 | 
38 |     @abstractmethod
39 |     def predict(self, img: np.ndarray) -> list[np.ndarray]:
40 |         pass
41 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/rembg/session_factory.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import onnxruntime as ort
 4 | 
 5 | from ..onnx.session import get_input_shape
 6 | from .session_base import BaseSession
 7 | from .session_cloth import ClothSession
 8 | from .session_simple import SimpleSession
 9 | 
10 | 
11 | def new_session(session: ort.InferenceSession) -> BaseSession:
12 |     session_class: type[BaseSession]
13 | 
14 |     input_width = get_input_shape(session)[2]
15 | 
16 |     # Using size to determine session type and norm parameters is fragile,
17 |     # but at the moment I don't know a better way to detect architecture due
18 |     # to the lack of consistency in naming and outputs across arches and repos.
19 |     # It works right now because of the limited number of models supported,
20 |     # but if that expands, it may become necessary to find an alternative.
21 |     mean = (0.485, 0.456, 0.406)
22 |     std = (0.229, 0.224, 0.225)
23 |     size = (input_width, input_width) if input_width is not None else (320, 320)
24 |     if input_width == 768:  # U2NET cloth model
25 |         session_class = ClothSession
26 |         mean = (0.5, 0.5, 0.5)
27 |         std = (0.5, 0.5, 0.5)
28 |     else:
29 |         session_class = SimpleSession
30 |         if input_width == 1024:  # ISNET
31 |             mean = (0.5, 0.5, 0.5)
32 |             std = (1, 1, 1)
33 |         elif input_width == 512:  # Models trained using anime-segmentation repo
34 |             mean = (0, 0, 0)
35 |             std = (1, 1, 1)
36 | 
37 |     return session_class(session, mean, std, size)
38 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/rembg/session_simple.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.impl.image_utils import normalize
 6 | from nodes.impl.resize import ResizeFilter, resize
 7 | from nodes.utils.utils import get_h_w_c
 8 | 
 9 | from .session_base import BaseSession
10 | 
11 | 
12 | class SimpleSession(BaseSession):
13 |     def predict(self, img: np.ndarray) -> list[np.ndarray]:
14 |         h, w, _ = get_h_w_c(img)
15 |         ort_outs = self.inner_session.run(None, self.normalize(img))
16 | 
17 |         pred = ort_outs[0][:, 0, :, :]
18 | 
19 |         ma = np.max(pred)
20 |         mi = np.min(pred)
21 | 
22 |         pred = (pred - mi) / (ma - mi)
23 |         mask = normalize(np.squeeze(pred))
24 |         mask = np.squeeze(resize(mask, (w, h), ResizeFilter.LANCZOS))
25 | 
26 |         return [mask]
27 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/rust_regex.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import Protocol
 4 | 
 5 | from chainner_ext import MatchGroup, RegexMatch, RustRegex
 6 | 
 7 | 
 8 | class Range(Protocol):
 9 |     @property
10 |     def start(self) -> int: ...
11 |     @property
12 |     def end(self) -> int: ...
13 | 
14 | 
15 | def get_range_text(text: str, range: Range) -> str:
16 |     return text[range.start : range.end]
17 | 
18 | 
19 | def match_to_replacements_dict(
20 |     regex: RustRegex, match: RegexMatch, text: str
21 | ) -> dict[str, str]:
22 |     def get_group_text(group: MatchGroup | None) -> str:
23 |         if group is None:
24 |             return ""
25 |         return get_range_text(text, group)
26 | 
27 |     replacements: dict[str, str] = {}
28 |     for i in range(regex.groups + 1):
29 |         replacements[str(i)] = get_group_text(match.get(i))
30 |     for name, i in regex.groupindex.items():
31 |         replacements[name] = get_group_text(match.get(i))
32 | 
33 |     return replacements
34 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/tile.py:
--------------------------------------------------------------------------------
 1 | import math
 2 | from enum import Enum
 3 | 
 4 | import cv2
 5 | import numpy as np
 6 | 
 7 | from ..utils.utils import get_h_w_c
 8 | 
 9 | 
10 | class TileMode(Enum):
11 |     TILE = 0
12 |     MIRROR = 1
13 | 
14 | 
15 | def tile_image(img: np.ndarray, width: int, height: int, mode: TileMode) -> np.ndarray:
16 |     if mode == TileMode.TILE:
17 |         # do nothing
18 |         pass
19 |     elif mode == TileMode.MIRROR:
20 |         # flip the image to create a mirrored tile
21 |         flip_x: np.ndarray = cv2.flip(img, 0)
22 |         flip_y: np.ndarray = cv2.flip(img, 1)
23 |         flip_xy: np.ndarray = cv2.flip(img, -1)
24 | 
25 |         img = cv2.vconcat(
26 |             [
27 |                 cv2.hconcat([img, flip_y]),  # type: ignore
28 |                 cv2.hconcat([flip_x, flip_xy]),  # type: ignore
29 |             ]
30 |         )
31 |     else:
32 |         raise AssertionError(f"Invalid tile mode {mode}")
33 | 
34 |     h, w, _ = get_h_w_c(img)
35 |     tile_w = math.ceil(width / w)
36 |     tile_h = math.ceil(height / h)
37 |     img = np.tile(img, (tile_h, tile_w) if img.ndim == 2 else (tile_h, tile_w, 1))
38 | 
39 |     # crop to make sure the dimensions are correct
40 |     return img[:height, :width]
41 | 


--------------------------------------------------------------------------------
/backend/src/nodes/impl/upscale/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/impl/upscale/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/impl/upscale/passthrough.py:
--------------------------------------------------------------------------------
 1 | import numpy as np
 2 | 
 3 | from ...utils.utils import get_h_w_c
 4 | from ..image_op import ImageOp
 5 | 
 6 | 
 7 | def passthrough_single_color(img: np.ndarray, scale: int, op: ImageOp) -> np.ndarray:
 8 |     """
 9 |     If the given image is a single-color image, it will be scaled and returned as is instead of being processed by the given operation.
10 |     Obviously, this optimization is only correct if `op` doesn't change the color of single-color images.
11 | 
12 |     To make this a transparent optimization, it is important that `scale` is correct.
13 |     `scale` must be the same factor by which `op` changes the dimension of the image.
14 |     """
15 | 
16 |     h, w, c = get_h_w_c(img)
17 | 
18 |     if c == 1:
19 |         unique_list = np.unique(img)
20 |         if len(unique_list) == 1:
21 |             return np.full((h * scale, w * scale), unique_list[0], np.float32)
22 |     else:
23 |         unique_values = []
24 |         is_unique = True
25 |         for channel in range(c):
26 |             unique_list = np.unique(img[:, :, channel])
27 |             if len(unique_list) == 1:
28 |                 unique_values.append(unique_list[0])
29 |             else:
30 |                 is_unique = False
31 |                 break
32 | 
33 |         if is_unique:
34 |             channels = [
35 |                 np.full((h * scale, w * scale), unique_values[channel], np.float32)
36 |                 for channel in range(c)
37 |             ]
38 |             return np.dstack(channels)
39 | 
40 |     return op(img)
41 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/properties/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/properties/inputs/__init__.py:
--------------------------------------------------------------------------------
 1 | from .file_inputs import *
 2 | from .generic_inputs import *
 3 | from .image_dropdown_inputs import *
 4 | from .numeric_inputs import *
 5 | from .numpy_inputs import *
 6 | 
 7 | try:
 8 |     from .ncnn_inputs import *
 9 | except Exception:
10 |     pass
11 | try:
12 |     from .onnx_inputs import *
13 | except Exception:
14 |     pass
15 | try:
16 |     from .pytorch_inputs import *
17 | except Exception:
18 |     pass
19 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/inputs/__system_inputs.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import math
 4 | from typing import Literal
 5 | 
 6 | from api import BaseInput
 7 | from navi import ExpressionJson
 8 | 
 9 | 
10 | class StaticValueInput(BaseInput):
11 |     def __init__(
12 |         self,
13 |         label: str,
14 |         py_type: type = str,
15 |         navi_type: ExpressionJson = "string",
16 |         value: Literal["execution_number"] = "execution_number",
17 |     ):
18 |         super().__init__(navi_type, label, kind="static", has_handle=False)
19 | 
20 |         self.associated_type = py_type
21 |         self.value = value
22 | 
23 |     def to_dict(self):
24 |         return {
25 |             **super().to_dict(),
26 |             "value": self.value,
27 |         }
28 | 
29 |     def enforce(self, value: object):
30 |         return_value = value
31 |         if not isinstance(value, self.associated_type):
32 |             return_value = self.associated_type(value)
33 | 
34 |         if isinstance(value, (float, int)) and math.isnan(value):
35 |             raise ValueError("NaN is not a valid number")
36 | 
37 |         return return_value
38 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/inputs/label.py:
--------------------------------------------------------------------------------
1 | from typing import Literal
2 | 
3 | LabelStyle = Literal["default", "hidden", "inline"]
4 | 
5 | 
6 | def get_default_label_style(label: str) -> LabelStyle:
7 |     return "inline" if len(label) <= 8 else "default"
8 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/inputs/ncnn_inputs.py:
--------------------------------------------------------------------------------
 1 | from api import BaseInput
 2 | 
 3 | from ...impl.ncnn.model import NcnnModelWrapper
 4 | 
 5 | 
 6 | class NcnnModelInput(BaseInput):
 7 |     """Input for NcnnModel"""
 8 | 
 9 |     def __init__(self, label: str = "Model"):
10 |         super().__init__("NcnnNetwork", label)
11 |         self.associated_type = NcnnModelWrapper
12 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/outputs/__init__.py:
--------------------------------------------------------------------------------
 1 | from .file_outputs import *
 2 | from .generic_outputs import *
 3 | from .numpy_outputs import *
 4 | 
 5 | try:
 6 |     from .ncnn_outputs import *
 7 | except Exception:
 8 |     pass
 9 | try:
10 |     from .onnx_outputs import *
11 | except Exception:
12 |     pass
13 | try:
14 |     from .pytorch_outputs import *
15 | except Exception:
16 |     pass
17 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/outputs/file_outputs.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from pathlib import Path
 4 | 
 5 | import navi
 6 | from api import BaseOutput
 7 | 
 8 | 
 9 | class DirectoryOutput(BaseOutput[Path]):
10 |     """Output for saving to a directory"""
11 | 
12 |     def __init__(
13 |         self,
14 |         label: str = "Directory",
15 |         of_input: int | None = None,
16 |         output_type: str = "Directory",
17 |     ):
18 |         directory_type = (
19 |             "Directory"
20 |             if of_input is None
21 |             else f"splitFilePath(Input{of_input}.path).dir"
22 |         )
23 |         directory_type = navi.intersect_with_error(directory_type, output_type)
24 |         super().__init__(directory_type, label, associated_type=Path)
25 | 
26 |     def get_broadcast_type(self, value: Path):
27 |         return navi.named("Directory", {"path": navi.literal(str(value))})
28 | 
29 |     def enforce(self, value: object) -> Path:
30 |         assert isinstance(value, Path)
31 |         return value
32 | 


--------------------------------------------------------------------------------
/backend/src/nodes/properties/outputs/ncnn_outputs.py:
--------------------------------------------------------------------------------
 1 | import navi
 2 | from api import BaseOutput, OutputKind
 3 | 
 4 | from ...impl.ncnn.model import NcnnModelWrapper
 5 | from ...utils.format import format_channel_numbers
 6 | 
 7 | 
 8 | class NcnnModelOutput(BaseOutput):
 9 |     def __init__(
10 |         self,
11 |         model_type: navi.ExpressionJson = "NcnnNetwork",
12 |         label: str = "Model",
13 |         kind: OutputKind = "generic",
14 |     ):
15 |         super().__init__(model_type, label, kind=kind, associated_type=NcnnModelWrapper)
16 | 
17 |     def get_broadcast_data(self, value: NcnnModelWrapper):
18 |         return {
19 |             "tags": [
20 |                 format_channel_numbers(value.in_nc, value.out_nc),
21 |                 f"{value.nf}nf",
22 |                 value.fp,
23 |             ]
24 |         }
25 | 
26 |     def get_broadcast_type(self, value: NcnnModelWrapper):
27 |         return navi.named(
28 |             "NcnnNetwork",
29 |             {
30 |                 "scale": value.scale,
31 |                 "inputChannels": value.in_nc,
32 |                 "outputChannels": value.out_nc,
33 |                 "nf": value.nf,
34 |                 "fp": navi.literal(value.fp),
35 |             },
36 |         )
37 | 


--------------------------------------------------------------------------------
/backend/src/nodes/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/nodes/utils/__init__.py


--------------------------------------------------------------------------------
/backend/src/nodes/utils/checked_cast.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import TypeVar
 4 | 
 5 | T = TypeVar("T")
 6 | 
 7 | 
 8 | def checked_cast(t: type[T], value: object) -> T:
 9 |     assert isinstance(value, t), f"Value is {type(value)}, must be type {t}"
10 |     return value
11 | 


--------------------------------------------------------------------------------
/backend/src/nodes/utils/seed.py:
--------------------------------------------------------------------------------
 1 | from dataclasses import dataclass
 2 | from random import Random
 3 | 
 4 | _U32_MAX = 4294967296
 5 | 
 6 | 
 7 | @dataclass(frozen=True)
 8 | class Seed:
 9 |     value: int
10 |     """
11 |     The value of the seed. This value may be signed and generally have any range.
12 |     """
13 | 
14 |     @staticmethod
15 |     def from_bytes(b: bytes):
16 |         return Seed(Random(b).randint(0, _U32_MAX - 1))
17 | 
18 |     def to_range(self, a: int, b: int) -> int:
19 |         """
20 |         Returns the value of the seed within the given range [a,b] both ends inclusive.
21 | 
22 |         If the current seed is not within the given range, a value within the range will be derived from the current seed.
23 |         """
24 |         if a <= self.value <= b:
25 |             return self.value
26 |         return Random(self.value).randint(a, b)
27 | 
28 |     def to_u32(self) -> int:
29 |         """
30 |         Returns the value of the seed as a 32bit unsigned integer.
31 |         """
32 |         return self.to_range(0, _U32_MAX - 1)
33 | 
34 |     def cache_key_func(self):
35 |         return self.value
36 | 


--------------------------------------------------------------------------------
/backend/src/nodes/utils/unpickler.py:
--------------------------------------------------------------------------------
 1 | # Safe unpickler to prevent arbitrary code execution
 2 | import pickle
 3 | from types import SimpleNamespace
 4 | 
 5 | safe_list = {
 6 |     ("collections", "OrderedDict"),
 7 |     ("typing", "OrderedDict"),
 8 |     ("torch._utils", "_rebuild_tensor_v2"),
 9 |     ("torch", "BFloat16Storage"),
10 |     ("torch", "FloatStorage"),
11 |     ("torch", "HalfStorage"),
12 |     ("torch", "IntStorage"),
13 |     ("torch", "LongStorage"),
14 |     ("torch", "DoubleStorage"),
15 | }
16 | 
17 | 
18 | class RestrictedUnpickler(pickle.Unpickler):
19 |     def find_class(self, module: str, name: str):
20 |         # Only allow required classes to load state dict
21 |         if (module, name) not in safe_list:
22 |             raise pickle.UnpicklingError(f"Global '{module}.{name}' is forbidden")
23 |         return super().find_class(module, name)
24 | 
25 | 
26 | RestrictedUnpickle = SimpleNamespace(
27 |     Unpickler=RestrictedUnpickler,
28 |     __name__="pickle",
29 |     load=lambda *args, **kwargs: RestrictedUnpickler(*args, **kwargs).load(),
30 | )
31 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_external/__init__.py:
--------------------------------------------------------------------------------
 1 | import sys
 2 | 
 3 | from sanic.log import logger
 4 | 
 5 | from api import add_package
 6 | 
 7 | package = add_package(
 8 |     __file__,
 9 |     id="chaiNNer_external",
10 |     name="External",
11 |     description="Interact with an external Stable Diffusion API",
12 | )
13 | 
14 | external_stable_diffusion_category = package.add_category(
15 |     name="Stable Diffusion (External)",
16 |     description="Interact with an external Stable Diffusion API",
17 |     icon="FaPaintBrush",
18 |     color="#9331CC",
19 | )
20 | 
21 | _FEATURE_DESCRIPTION = f"""
22 | ChaiNNer can connect to [AUTOMATIC1111's Stable Diffusion Web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) to run Stable Diffusion nodes.
23 | 
24 | If you want to use the External Stable Diffusion nodes, run the Automatic1111 web UI with the `--api` flag, like so:
25 | 
26 | ```bash
27 | ./webui.{"bat" if sys.platform == "win32" else "sh"} --api
28 | ```
29 | 
30 | To manually set where chaiNNer looks for the API, use the `STABLE_DIFFUSION_PROTOCOL`, `STABLE_DIFFUSION_HOST`, and `STABLE_DIFFUSION_PORT` environment variables. By default, `127.0.0.1` will be the host. If not specified, chaiNNer will try to auto-detect the protocol and port.
31 | """
32 | 
33 | 
34 | web_ui_feature_descriptor = package.add_feature(
35 |     id="webui",
36 |     name="AUTOMATIC1111/stable-diffusion-webui",
37 |     description=_FEATURE_DESCRIPTION,
38 | )
39 | 
40 | logger.debug(f"Loaded package {package.name}")
41 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_external/external_stable_diffusion/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import external_stable_diffusion_category
2 | 
3 | auto1111_group = external_stable_diffusion_category.add_node_group("Automatic1111")
4 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_external/external_stable_diffusion/automatic1111/clip_interrogate.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.node_cache import cached
 6 | from nodes.properties.inputs import ImageInput
 7 | from nodes.properties.outputs import TextOutput
 8 | 
 9 | from ...features import web_ui
10 | from ...util import encode_base64_image
11 | from ...web_ui import STABLE_DIFFUSION_INTERROGATE_PATH, get_api
12 | from .. import auto1111_group
13 | 
14 | 
15 | @auto1111_group.register(
16 |     schema_id="chainner:external_stable_diffusion:interrograte",
17 |     name="CLIP Interrogate",
18 |     description="Use Automatic1111 to get a description of an image",
19 |     icon="MdTextFields",
20 |     inputs=[
21 |         ImageInput(),
22 |     ],
23 |     outputs=[
24 |         TextOutput("Text"),
25 |     ],
26 |     decorators=[cached],
27 |     features=web_ui,
28 | )
29 | def clip_interrogate_node(image: np.ndarray) -> str:
30 |     request_data = {
31 |         "image": encode_base64_image(image),
32 |     }
33 |     response = get_api().post(
34 |         path=STABLE_DIFFUSION_INTERROGATE_PATH, json_data=request_data
35 |     )
36 |     return response["caption"]
37 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_external/features.py:
--------------------------------------------------------------------------------
 1 | from api import FeatureState
 2 | from nodes.utils.format import join_english
 3 | 
 4 | from . import web_ui_feature_descriptor
 5 | from .web_ui import ApiConfig, get_verified_api
 6 | 
 7 | 
 8 | async def check_connection() -> FeatureState:
 9 |     config = None
10 |     try:
11 |         config = ApiConfig.from_env()
12 |         api = await get_verified_api()
13 |         if api is not None:
14 |             return FeatureState.enabled(f"Connected to {api.base_url}")
15 |         else:
16 |             url = config.host
17 |             if len(config.protocol) == 1:
18 |                 url = f"{config.protocol[0]}://{url}"
19 |             if len(config.port) == 1:
20 |                 url += f":{config.port[0]}"
21 |             else:
22 |                 ports = join_english(config.port)
23 |                 url += f" on ports {ports}"
24 | 
25 |             return FeatureState.disabled(
26 |                 f"No stable diffusion API found. Could not connect to {url}."
27 |             )
28 |     except Exception as e:
29 |         return FeatureState.disabled(f"Could not connect to stable diffusion API: {e}")
30 | 
31 | 
32 | web_ui = web_ui_feature_descriptor.add_behavior(check=check_connection)
33 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_external/util.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import base64
 4 | import io
 5 | 
 6 | import cv2
 7 | import numpy as np
 8 | from PIL import Image
 9 | 
10 | from nodes.impl.image_utils import normalize, to_uint8
11 | from nodes.utils.utils import get_h_w_c
12 | 
13 | 
14 | def nearest_valid_size(width: int, height: int):
15 |     return (width // 8) * 8, (height // 8) * 8
16 | 
17 | 
18 | def decode_base64_image(image_bytes: bytes | str) -> np.ndarray:
19 |     image = Image.open(io.BytesIO(base64.b64decode(image_bytes)))
20 |     image_nparray = np.array(image)
21 |     _, _, c = get_h_w_c(image_nparray)
22 |     if c == 3:
23 |         image_nparray = cv2.cvtColor(image_nparray, cv2.COLOR_RGB2BGR)
24 |     elif c == 4:
25 |         image_nparray = cv2.cvtColor(image_nparray, cv2.COLOR_RGBA2BGRA)
26 |     return normalize(image_nparray)
27 | 
28 | 
29 | def encode_base64_image(image_nparray: np.ndarray) -> str:
30 |     image_nparray = to_uint8(image_nparray)
31 |     _, _, c = get_h_w_c(image_nparray)
32 |     if c == 1:
33 |         # PIL supports grayscale images just fine, so we don't need to do any conversion
34 |         pass
35 |     elif c == 3:
36 |         image_nparray = cv2.cvtColor(image_nparray, cv2.COLOR_BGR2RGB)
37 |     elif c == 4:
38 |         image_nparray = cv2.cvtColor(image_nparray, cv2.COLOR_BGRA2RGBA)
39 |     else:
40 |         raise RuntimeError
41 |     with io.BytesIO() as buffer:
42 |         with Image.fromarray(image_nparray) as image:
43 |             image.save(buffer, format="PNG")
44 |         return base64.b64encode(buffer.getvalue()).decode("utf-8")
45 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_ncnn/ncnn/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import ncnn_category
2 | 
3 | io_group = ncnn_category.add_node_group("Input & Output")
4 | processing_group = ncnn_category.add_node_group("Processing")
5 | utility_group = ncnn_category.add_node_group("Utility")
6 | batch_processing_group = ncnn_category.add_node_group("Batch Processing")
7 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_ncnn/ncnn/io/save_model.py:
--------------------------------------------------------------------------------
 1 | from pathlib import Path
 2 | 
 3 | from sanic.log import logger
 4 | 
 5 | from nodes.impl.ncnn.model import NcnnModelWrapper
 6 | from nodes.properties.inputs import DirectoryInput, NcnnModelInput, RelativePathInput
 7 | 
 8 | from .. import io_group
 9 | 
10 | 
11 | @io_group.register(
12 |     schema_id="chainner:ncnn:save_model",
13 |     name="Save Model",
14 |     description="Save an NCNN model to specified directory. It can also be saved in fp16 mode for smaller file size and faster processing.",
15 |     icon="MdSave",
16 |     inputs=[
17 |         NcnnModelInput(),
18 |         DirectoryInput(must_exist=False),
19 |         RelativePathInput("Param/Bin Name"),
20 |     ],
21 |     outputs=[],
22 |     side_effects=True,
23 | )
24 | def save_model_node(model: NcnnModelWrapper, directory: Path, name: str) -> None:
25 |     full_bin_path = (directory / f"{name}.bin").resolve()
26 |     full_param_path = (directory / f"{name}.param").resolve()
27 | 
28 |     logger.debug(f"Writing NCNN model to paths: {full_bin_path} {full_param_path}")
29 | 
30 |     full_bin_path.parent.mkdir(parents=True, exist_ok=True)
31 |     model.model.write_bin(full_bin_path)
32 |     model.model.write_param(full_param_path)
33 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_ncnn/ncnn/utility/get_model_scale.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.impl.ncnn.model import NcnnModelWrapper
 4 | from nodes.properties.inputs import NcnnModelInput
 5 | from nodes.properties.outputs import NumberOutput
 6 | 
 7 | from .. import utility_group
 8 | 
 9 | 
10 | @utility_group.register(
11 |     schema_id="chainner:ncnn:model_dim",
12 |     name="Get Model Scale",
13 |     description="""Returns the scale of an NCNN model.""",
14 |     icon="BsRulers",
15 |     inputs=[NcnnModelInput()],
16 |     outputs=[NumberOutput("Scale", output_type="Input0.scale")],
17 | )
18 | def get_model_scale_node(model: NcnnModelWrapper) -> int:
19 |     return model.scale
20 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_onnx/onnx/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import onnx_category
2 | 
3 | io_group = onnx_category.add_node_group("Input & Output")
4 | processing_group = onnx_category.add_node_group("Processing")
5 | utility_group = onnx_category.add_node_group("Utility")
6 | batch_processing_group = onnx_category.add_node_group("Batch Processing")
7 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_onnx/onnx/io/save_model.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from pathlib import Path
 4 | 
 5 | from sanic.log import logger
 6 | 
 7 | from nodes.impl.onnx.model import OnnxModel
 8 | from nodes.properties.inputs import DirectoryInput, OnnxModelInput, RelativePathInput
 9 | 
10 | from .. import io_group
11 | 
12 | 
13 | @io_group.register(
14 |     schema_id="chainner:onnx:save_model",
15 |     name="Save Model",
16 |     description="""Save ONNX model to file (.onnx).""",
17 |     icon="MdSave",
18 |     inputs=[
19 |         OnnxModelInput(),
20 |         DirectoryInput(must_exist=False),
21 |         RelativePathInput("Model Name"),
22 |     ],
23 |     outputs=[],
24 |     side_effects=True,
25 | )
26 | def save_model_node(model: OnnxModel, directory: Path, model_name: str) -> None:
27 |     full_path = (directory / f"{model_name}.onnx").resolve()
28 |     logger.debug(f"Writing file to path: {full_path}")
29 |     full_path.parent.mkdir(parents=True, exist_ok=True)
30 |     with open(full_path, "wb") as f:
31 |         f.write(model.bytes)
32 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_onnx/onnx/utility/get_model_info.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.impl.onnx.model import OnnxModel
 4 | from nodes.properties.inputs import OnnxModelInput
 5 | from nodes.properties.outputs import NumberOutput, TextOutput
 6 | 
 7 | from .. import utility_group
 8 | 
 9 | 
10 | @utility_group.register(
11 |     schema_id="chainner:onnx:model_info",
12 |     name="Get Model Info",
13 |     description="""Returns the scale and purpose of a ONNX model.""",
14 |     icon="ImInfo",
15 |     inputs=[OnnxModelInput("ONNX Model")],
16 |     outputs=[
17 |         NumberOutput(
18 |             "Scale",
19 |             output_type="""
20 |                 if Input0.scaleWidth == Input0.scaleHeight {
21 |                     Input0.scaleHeight
22 |                 } else {
23 |                     0
24 |                 }
25 |                 """,
26 |         ),
27 |         TextOutput("Purpose", output_type="Input0.subType"),
28 |     ],
29 | )
30 | def get_model_info_node(model: OnnxModel) -> tuple[int, str]:
31 |     scale_width = model.info.scale_width
32 |     scale_height = model.info.scale_height
33 |     return (
34 |         scale_width if (scale_width is not None and scale_width == scale_height) else 0,
35 |         model.sub_type,
36 |     )
37 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_onnx/onnx/utility/optimize_model.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import onnx
 4 | 
 5 | from nodes.impl.onnx.load import load_onnx_model
 6 | from nodes.impl.onnx.model import OnnxModel
 7 | from nodes.impl.onnx.utils import safely_optimize_onnx_model
 8 | from nodes.properties.inputs import OnnxModelInput
 9 | from nodes.properties.outputs import OnnxModelOutput
10 | 
11 | from .. import utility_group
12 | 
13 | 
14 | @utility_group.register(
15 |     schema_id="chainner:onnx:optimize_model",
16 |     name="Optimize Model",
17 |     description="Optimize the give model. Optimizations may or may not improve performance.",
18 |     icon="MdSpeed",
19 |     inputs=[
20 |         OnnxModelInput(),
21 |     ],
22 |     outputs=[
23 |         OnnxModelOutput(),
24 |     ],
25 | )
26 | def optimize_model_node(model: OnnxModel) -> OnnxModel:
27 |     model_proto = onnx.load_from_string(model.bytes)
28 |     model_proto = safely_optimize_onnx_model(model_proto)
29 |     return load_onnx_model(model_proto)
30 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_pytorch/pytorch/__init__.py:
--------------------------------------------------------------------------------
 1 | from .. import pytorch_category
 2 | 
 3 | io_group = pytorch_category.add_node_group("Input & Output")
 4 | processing_group = pytorch_category.add_node_group("Processing")
 5 | restoration_group = pytorch_category.add_node_group("Restoration")
 6 | batch_processing_group = pytorch_category.add_node_group("Batch Processing")
 7 | utility_group = pytorch_category.add_node_group("Utility")
 8 | 
 9 | processing_group.order = [
10 |     "chainner:pytorch:upscale_image",
11 |     "chainner:pytorch:inpaint",
12 | ]
13 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_pytorch/pytorch/utility/get_model_info.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from spandrel import ModelDescriptor
 4 | 
 5 | from nodes.properties.inputs import ModelInput
 6 | from nodes.properties.outputs import NumberOutput, TextOutput
 7 | 
 8 | from .. import utility_group
 9 | 
10 | 
11 | @utility_group.register(
12 |     schema_id="chainner:pytorch:model_dim",
13 |     name="Get Model Info",
14 |     description="""Returns the purpose, architecture and scale of a PyTorch model.""",
15 |     icon="ImInfo",
16 |     inputs=[ModelInput()],
17 |     outputs=[
18 |         NumberOutput("Scale", output_type="Input0.scale"),
19 |         TextOutput("Architecture", output_type="Input0.arch"),
20 |         TextOutput("Purpose", output_type="Input0.subType"),
21 |     ],
22 | )
23 | def get_model_info_node(model: ModelDescriptor) -> tuple[int, str, str]:
24 |     return (model.scale, model.architecture.name, model.purpose)
25 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image/__init__.py:
--------------------------------------------------------------------------------
 1 | from .. import image_category
 2 | 
 3 | io_group = image_category.add_node_group("Input & Output")
 4 | batch_processing_group = image_category.add_node_group("Batch Processing")
 5 | video_frames_group = image_category.add_node_group("Video Frames")
 6 | create_images_group = image_category.add_node_group("Create Images")
 7 | 
 8 | batch_processing_group.order = [
 9 |     "chainner:image:load_images",
10 |     "chainner:image:load_image_pairs",
11 |     "chainner:image:split_spritesheet",
12 |     "chainner:image:merge_spritesheet",
13 | ]
14 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image/create_images/create_color.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | import navi
 6 | from nodes.impl.color.color import Color
 7 | from nodes.properties.inputs import ColorInput, NumberInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import create_images_group
11 | 
12 | 
13 | @create_images_group.register(
14 |     schema_id="chainner:image:create_color",
15 |     name="Create Color",
16 |     description="Create an image of specified dimensions filled with the given color.",
17 |     icon="MdFormatColorFill",
18 |     inputs=[
19 |         ColorInput(),
20 |         NumberInput("Width", min=1, unit="px", default=1),
21 |         NumberInput("Height", min=1, unit="px", default=1),
22 |     ],
23 |     outputs=[
24 |         ImageOutput(
25 |             image_type=navi.Image(
26 |                 width="Input1",
27 |                 height="Input2",
28 |                 channels="Input0.channels",
29 |             ),
30 |             assume_normalized=True,
31 |         )
32 |     ],
33 | )
34 | def create_color_node(color: Color, width: int, height: int) -> np.ndarray:
35 |     return color.to_image(width, height)
36 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image/io/view_image.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput
 6 | from nodes.properties.outputs import LargeImageOutput
 7 | 
 8 | from .. import io_group
 9 | 
10 | 
11 | @io_group.register(
12 |     schema_id="chainner:image:view",
13 |     name="View Image",
14 |     description="See an inline preview of the image in the editor.",
15 |     icon="BsEyeFill",
16 |     inputs=[ImageInput()],
17 |     outputs=[
18 |         LargeImageOutput(
19 |             "Preview",
20 |             image_type="Input0",
21 |             has_handle=False,
22 |             assume_normalized=True,
23 |         ),
24 |     ],
25 |     side_effects=True,
26 | )
27 | def view_image_node(img: np.ndarray):
28 |     return img
29 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import image_adjustments_category
2 | 
3 | adjustments_group = image_adjustments_category.add_node_group("Adjustments")
4 | threshold_group = image_adjustments_category.add_node_group("Threshold")
5 | gamma_group = image_adjustments_category.add_node_group("Gamma")
6 | arithmetic_group = image_adjustments_category.add_node_group("Arithmetic")
7 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/adjustments/clamp.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput, SliderInput
 6 | from nodes.properties.outputs import ImageOutput
 7 | 
 8 | from .. import adjustments_group
 9 | 
10 | 
11 | @adjustments_group.register(
12 |     schema_id="chainner:image:clamp",
13 |     name="Clamp",
14 |     description="Clamps the values of an image.",
15 |     icon="ImContrast",
16 |     inputs=[
17 |         ImageInput(),
18 |         SliderInput(
19 |             "Minimum",
20 |             min=0.0,
21 |             max=1.0,
22 |             default=0.0,
23 |             precision=4,
24 |             step=0.001,
25 |             scale="log",
26 |         ),
27 |         SliderInput(
28 |             "Maximum",
29 |             min=0.0,
30 |             max=1.0,
31 |             default=1.0,
32 |             precision=4,
33 |             step=0.001,
34 |             scale="log",
35 |         ),
36 |     ],
37 |     outputs=[
38 |         ImageOutput(shape_as=0, assume_normalized=True),
39 |     ],
40 | )
41 | def clamp_node(img: np.ndarray, minimum: float, maximum: float) -> np.ndarray:
42 |     if minimum <= 0 and maximum >= 1:
43 |         return img
44 |     return np.clip(img, minimum, maximum)
45 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/adjustments/invert_color.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput
 6 | from nodes.properties.outputs import ImageOutput
 7 | from nodes.utils.utils import get_h_w_c
 8 | 
 9 | from .. import adjustments_group
10 | 
11 | 
12 | @adjustments_group.register(
13 |     schema_id="chainner:image:invert",
14 |     name="Invert Color",
15 |     description="Inverts all colors in an image.",
16 |     icon="MdInvertColors",
17 |     inputs=[ImageInput()],
18 |     outputs=[ImageOutput(shape_as=0, assume_normalized=True)],
19 | )
20 | def invert_color_node(img: np.ndarray) -> np.ndarray:
21 |     c = get_h_w_c(img)[2]
22 | 
23 |     # invert the first 3 channels
24 |     if c <= 3:
25 |         return 1 - img
26 | 
27 |     img = img.copy()
28 |     img[:, :, :3] = 1 - img[:, :, :3]
29 |     return img
30 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/adjustments/opacity.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from api import KeyInfo
 6 | from nodes.impl.pil_utils import convert_to_bgra
 7 | from nodes.properties.inputs import ImageInput, SliderInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | from nodes.utils.utils import get_h_w_c
10 | 
11 | from .. import adjustments_group
12 | 
13 | 
14 | @adjustments_group.register(
15 |     schema_id="chainner:image:opacity",
16 |     name="Opacity",
17 |     description="Adjusts the opacity of an image. The higher the opacity value, the more opaque the image is.",
18 |     icon="MdOutlineOpacity",
19 |     inputs=[
20 |         ImageInput(),
21 |         SliderInput("Opacity", max=100, default=100, precision=1, step=1, unit="%"),
22 |     ],
23 |     outputs=[
24 |         ImageOutput(size_as=0, channels=4, assume_normalized=True),
25 |     ],
26 |     key_info=KeyInfo.number(1),
27 | )
28 | def opacity_node(img: np.ndarray, opacity: float) -> np.ndarray:
29 |     # Convert inputs
30 |     c = get_h_w_c(img)[2]
31 |     if opacity == 100 and c == 4:
32 |         return img
33 |     imgout = convert_to_bgra(img, c)
34 |     opacity /= 100
35 | 
36 |     imgout[:, :, 3] *= opacity
37 | 
38 |     return imgout
39 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/arithmetic/add.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput, SliderInput
 6 | from nodes.properties.outputs import ImageOutput
 7 | 
 8 | from .. import arithmetic_group
 9 | 
10 | 
11 | @arithmetic_group.register(
12 |     schema_id="chainner:image:add",
13 |     description="Add values to an image.",
14 |     name="Add",
15 |     icon="ImBrightnessContrast",
16 |     inputs=[
17 |         ImageInput(),
18 |         SliderInput(
19 |             "Add",
20 |             min=-100,
21 |             max=100,
22 |             default=0,
23 |             precision=1,
24 |             step=1,
25 |         ),
26 |     ],
27 |     outputs=[ImageOutput(shape_as=0)],
28 | )
29 | def add_node(img: np.ndarray, add: float) -> np.ndarray:
30 |     if add == 0:
31 |         return img
32 | 
33 |     img = img + (add / 100)
34 | 
35 |     return img
36 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/arithmetic/divide.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput, SliderInput
 6 | from nodes.properties.outputs import ImageOutput
 7 | 
 8 | from .. import arithmetic_group
 9 | 
10 | 
11 | @arithmetic_group.register(
12 |     schema_id="chainner:image:divide",
13 |     description="Divide all channels in an image by a value.",
14 |     name="Divide",
15 |     icon="ImBrightnessContrast",
16 |     inputs=[
17 |         ImageInput(),
18 |         SliderInput(
19 |             "Divide",
20 |             min=0.0001,
21 |             max=4.0,
22 |             default=1.0,
23 |             precision=4,
24 |             step=0.0001,
25 |             scale="log",
26 |         ),
27 |     ],
28 |     outputs=[ImageOutput(shape_as=0)],
29 | )
30 | def divide_node(img: np.ndarray, divide: float) -> np.ndarray:
31 |     if divide == 1.0:
32 |         return img
33 | 
34 |     img = img * (1.0 / divide)
35 | 
36 |     return img
37 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/arithmetic/multiply.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput, SliderInput
 6 | from nodes.properties.outputs import ImageOutput
 7 | 
 8 | from .. import arithmetic_group
 9 | 
10 | 
11 | @arithmetic_group.register(
12 |     schema_id="chainner:image:multiply",
13 |     description="Multiply all channels in an image by a value.",
14 |     name="Multiply",
15 |     icon="ImBrightnessContrast",
16 |     inputs=[
17 |         ImageInput(),
18 |         SliderInput(
19 |             "Multiply",
20 |             min=0.0,
21 |             max=4.0,
22 |             default=1.0,
23 |             precision=4,
24 |             step=0.0001,
25 |             scale="log",
26 |         ),
27 |     ],
28 |     outputs=[ImageOutput(shape_as=0)],
29 | )
30 | def multiply_node(img: np.ndarray, mult: float) -> np.ndarray:
31 |     if mult == 1.0:
32 |         return img
33 | 
34 |     img = img * mult
35 | 
36 |     return img
37 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_adjustment/gamma/gamma.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | from chainner_ext import fast_gamma
 5 | 
 6 | from nodes.properties.inputs import BoolInput, ImageInput, SliderInput
 7 | from nodes.properties.outputs import ImageOutput
 8 | 
 9 | from .. import gamma_group
10 | 
11 | 
12 | @gamma_group.register(
13 |     schema_id="chainner:image:gamma",
14 |     name="Gamma",
15 |     description="Adjusts the gamma of an image.",
16 |     icon="ImBrightnessContrast",
17 |     inputs=[
18 |         ImageInput(),
19 |         SliderInput(
20 |             "Gamma",
21 |             min=0.01,
22 |             max=100,
23 |             default=1,
24 |             precision=4,
25 |             step=0.1,
26 |             scale="log",
27 |         ),
28 |         BoolInput("Invert Gamma", default=False),
29 |     ],
30 |     outputs=[ImageOutput(shape_as=0, assume_normalized=True)],
31 | )
32 | def gamma_node(img: np.ndarray, gamma: float, invert_gamma: bool) -> np.ndarray:
33 |     if gamma == 1:
34 |         # noop
35 |         return img
36 | 
37 |     if invert_gamma:
38 |         gamma = 1 / gamma
39 | 
40 |     return fast_gamma(img, gamma)
41 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_channel/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import image_channel_category
2 | 
3 | all_group = image_channel_category.add_node_group("All")
4 | transparency_group = image_channel_category.add_node_group("Transparency")
5 | miscellaneous_group = image_channel_category.add_node_group("Miscellaneous")
6 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_channel/transparency/split_transparency.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.impl.image_utils import as_target_channels
 6 | from nodes.properties.inputs import ImageInput
 7 | from nodes.properties.outputs import ImageOutput
 8 | from nodes.utils.utils import get_h_w_c
 9 | 
10 | from .. import transparency_group
11 | 
12 | 
13 | @transparency_group.register(
14 |     schema_id="chainner:image:split_transparency",
15 |     name="Split Transparency",
16 |     description="Split image channels into RGB and Alpha (transparency) channels.",
17 |     icon="MdCallSplit",
18 |     inputs=[ImageInput(channels=[1, 3, 4])],
19 |     outputs=[
20 |         ImageOutput("RGB", size_as=0, channels=3, assume_normalized=True),
21 |         ImageOutput("Alpha", size_as=0, channels=1, assume_normalized=True),
22 |     ],
23 | )
24 | def split_transparency_node(img: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
25 |     c = get_h_w_c(img)[2]
26 |     if c == 3:
27 |         # Performance optimization:
28 |         # Subsequent operations will be faster since the underlying array will
29 |         # be contiguous in memory. The speed up can anything from nothing to
30 |         # 5x faster depending on the operation.
31 |         return img, np.ones(img.shape[:2], dtype=img.dtype)
32 | 
33 |     img = as_target_channels(img, 4)
34 | 
35 |     rgb = img[:, :, :3]
36 |     alpha = img[:, :, 3]
37 | 
38 |     return rgb, alpha
39 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_dimension/__init__.py:
--------------------------------------------------------------------------------
 1 | from .. import image_dimensions_category
 2 | 
 3 | padding_group = image_dimensions_category.add_node_group("Padding")
 4 | crop_group = image_dimensions_category.add_node_group("Crop")
 5 | resize_group = image_dimensions_category.add_node_group("Resize")
 6 | utility_group = image_dimensions_category.add_node_group("Utility")
 7 | 
 8 | resize_group.order = [
 9 |     "chainner:image:resize",
10 |     "chainner:image:resize_to_side",
11 | ]
12 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_dimension/utility/get_dimensions.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.properties.inputs import ImageInput
 6 | from nodes.properties.outputs import NumberOutput
 7 | from nodes.utils.utils import get_h_w_c
 8 | 
 9 | from .. import utility_group
10 | 
11 | 
12 | @utility_group.register(
13 |     schema_id="chainner:image:get_dims",
14 |     name="Get Dimensions",
15 |     description=("Get the Height, Width, and number of Channels from an image."),
16 |     icon="BsRulers",
17 |     inputs=[
18 |         ImageInput(),
19 |     ],
20 |     outputs=[
21 |         NumberOutput("Width", output_type="Input0.width"),
22 |         NumberOutput("Height", output_type="Input0.height"),
23 |         NumberOutput("Channels", output_type="Input0.channels"),
24 |     ],
25 | )
26 | def get_dimensions_node(
27 |     img: np.ndarray,
28 | ) -> tuple[int, int, int]:
29 |     h, w, c = get_h_w_c(img)
30 |     return w, h, c
31 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_filter/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import image_filter_category
2 | 
3 | blur_group = image_filter_category.add_node_group("Blur")
4 | sharpen_group = image_filter_category.add_node_group("Sharpen")
5 | noise_group = image_filter_category.add_node_group("Noise")
6 | quantize_group = image_filter_category.add_node_group("Quantize")
7 | correction_group = image_filter_category.add_node_group("Correction")
8 | miscellaneous_group = image_filter_category.add_node_group("Miscellaneous")
9 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_filter/blur/gaussian_blur.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.groups import linked_inputs_group
 6 | from nodes.impl.image_utils import fast_gaussian_blur
 7 | from nodes.properties.inputs import ImageInput, SliderInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import blur_group
11 | 
12 | 
13 | @blur_group.register(
14 |     schema_id="chainner:image:gaussian_blur",
15 |     name="Gaussian Blur",
16 |     description="Apply Gaussian blur to an image.",
17 |     icon="MdBlurOn",
18 |     inputs=[
19 |         ImageInput(),
20 |         linked_inputs_group(
21 |             SliderInput(
22 |                 "Radius X",
23 |                 min=0,
24 |                 max=1000,
25 |                 default=1,
26 |                 precision=1,
27 |                 step=1,
28 |                 slider_step=0.1,
29 |                 scale="log",
30 |             ),
31 |             SliderInput(
32 |                 "Radius Y",
33 |                 min=0,
34 |                 max=1000,
35 |                 default=1,
36 |                 precision=1,
37 |                 step=1,
38 |                 slider_step=0.1,
39 |                 scale="log",
40 |             ),
41 |         ),
42 |     ],
43 |     outputs=[ImageOutput(shape_as=0)],
44 | )
45 | def gaussian_blur_node(
46 |     img: np.ndarray,
47 |     sigma_x: float,
48 |     sigma_y: float,
49 | ) -> np.ndarray:
50 |     if sigma_x == 0 and sigma_y == 0:
51 |         return img
52 | 
53 |     return fast_gaussian_blur(img, sigma_x, sigma_y)
54 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_filter/blur/median_blur.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import cv2
 4 | import numpy as np
 5 | 
 6 | from nodes.impl.image_utils import to_uint8
 7 | from nodes.properties.inputs import ImageInput, SliderInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import blur_group
11 | 
12 | 
13 | @blur_group.register(
14 |     schema_id="chainner:image:median_blur",
15 |     name="Median Blur",
16 |     description="Apply median blur to an image.",
17 |     icon="MdBlurOn",
18 |     inputs=[
19 |         ImageInput(),
20 |         SliderInput("Radius", min=0, max=1000, default=1, scale="log"),
21 |     ],
22 |     outputs=[ImageOutput(shape_as=0)],
23 |     limited_to_8bpc=True,
24 | )
25 | def median_blur_node(
26 |     img: np.ndarray,
27 |     radius: int,
28 | ) -> np.ndarray:
29 |     if radius == 0:
30 |         return img
31 |     else:
32 |         size = 2 * radius + 1
33 |         if size <= 5:
34 |             return cv2.medianBlur(img, size)
35 |         else:  # cv2 requires uint8 for kernel size > 5
36 |             return cv2.medianBlur(to_uint8(img, normalized=True), size)
37 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_filter/miscellaneous/canny_edge_detection.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import cv2
 4 | import numpy as np
 5 | 
 6 | from nodes.impl.image_utils import to_uint8
 7 | from nodes.properties.inputs import ImageInput, NumberInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import miscellaneous_group
11 | 
12 | 
13 | @miscellaneous_group.register(
14 |     schema_id="chainner:image:canny_edge_detection",
15 |     name="Canny Edge Detection",
16 |     description=(
17 |         "Detect the edges of the input image and output as black and white image."
18 |     ),
19 |     icon="MdAutoFixHigh",
20 |     inputs=[
21 |         ImageInput(),
22 |         NumberInput("Lower Threshold", min=0, default=100),
23 |         NumberInput("Upper Threshold", min=0, default=300),
24 |     ],
25 |     outputs=[ImageOutput(size_as=0, channels=1)],
26 | )
27 | def canny_edge_detection_node(
28 |     img: np.ndarray,
29 |     t_lower: int,
30 |     t_upper: int,
31 | ) -> np.ndarray:
32 |     return cv2.Canny(to_uint8(img, normalized=True), t_lower, t_upper)
33 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_filter/miscellaneous/dilate.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from enum import Enum
 4 | 
 5 | import cv2
 6 | import numpy as np
 7 | 
 8 | from nodes.properties.inputs import EnumInput, ImageInput, SliderInput
 9 | from nodes.properties.outputs import ImageOutput
10 | 
11 | from .. import miscellaneous_group
12 | 
13 | 
14 | class MorphShape(Enum):
15 |     RECTANGLE = cv2.MORPH_RECT
16 |     ELLIPSE = cv2.MORPH_ELLIPSE
17 |     CROSS = cv2.MORPH_CROSS
18 | 
19 | 
20 | @miscellaneous_group.register(
21 |     schema_id="chainner:image:dilate",
22 |     name="Dilate",
23 |     description="Dilate an image",
24 |     icon="MdOutlineAutoFixHigh",
25 |     inputs=[
26 |         ImageInput(),
27 |         EnumInput(
28 |             MorphShape,
29 |             option_labels={
30 |                 MorphShape.RECTANGLE: "Square",
31 |                 MorphShape.ELLIPSE: "Circle",
32 |                 MorphShape.CROSS: "Cross",
33 |             },
34 |         ),
35 |         SliderInput("Radius", min=0, max=1000, default=1, scale="log"),
36 |         SliderInput("Iterations", min=0, max=1000, default=1, scale="log"),
37 |     ],
38 |     outputs=[ImageOutput(shape_as=0)],
39 | )
40 | def dilate_node(
41 |     img: np.ndarray,
42 |     morph_shape: MorphShape,
43 |     radius: int,
44 |     iterations: int,
45 | ) -> np.ndarray:
46 |     if radius == 0 or iterations == 0:
47 |         return img
48 | 
49 |     size = 2 * radius + 1
50 |     element = cv2.getStructuringElement(morph_shape.value, (size, size))
51 | 
52 |     return cv2.dilate(img, element, iterations=iterations)
53 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_filter/miscellaneous/erode.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from enum import Enum
 4 | 
 5 | import cv2
 6 | import numpy as np
 7 | 
 8 | from nodes.properties.inputs import EnumInput, ImageInput, SliderInput
 9 | from nodes.properties.outputs import ImageOutput
10 | 
11 | from .. import miscellaneous_group
12 | 
13 | 
14 | class MorphShape(Enum):
15 |     RECTANGLE = cv2.MORPH_RECT
16 |     ELLIPSE = cv2.MORPH_ELLIPSE
17 |     CROSS = cv2.MORPH_CROSS
18 | 
19 | 
20 | @miscellaneous_group.register(
21 |     schema_id="chainner:image:erode",
22 |     name="Erode",
23 |     description="Erode an image",
24 |     icon="MdOutlineAutoFixHigh",
25 |     inputs=[
26 |         ImageInput(),
27 |         EnumInput(
28 |             MorphShape,
29 |             option_labels={
30 |                 MorphShape.RECTANGLE: "Square",
31 |                 MorphShape.ELLIPSE: "Circle",
32 |                 MorphShape.CROSS: "Cross",
33 |             },
34 |         ),
35 |         SliderInput("Radius", min=0, max=1000, default=1, scale="log"),
36 |         SliderInput("Iterations", min=0, max=1000, default=1, scale="log"),
37 |     ],
38 |     outputs=[ImageOutput(shape_as=0)],
39 | )
40 | def erode_node(
41 |     img: np.ndarray,
42 |     morph_shape: MorphShape,
43 |     radius: int,
44 |     iterations: int,
45 | ) -> np.ndarray:
46 |     if radius == 0 or iterations == 0:
47 |         return img
48 | 
49 |     size = 2 * radius + 1
50 |     element = cv2.getStructuringElement(morph_shape.value, (size, size))
51 | 
52 |     return cv2.erode(img, element, iterations=iterations)
53 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_utility/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import image_utility_category
2 | 
3 | modification_group = image_utility_category.add_node_group("Modification")
4 | compositing_group = image_utility_category.add_node_group("Compositing")
5 | miscellaneous_group = image_utility_category.add_node_group("Miscellaneous")
6 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_utility/compositing/add_caption.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.impl.caption import CaptionPosition, add_caption
 6 | from nodes.properties.inputs import EnumInput, ImageInput, NumberInput, TextInput
 7 | from nodes.properties.outputs import ImageOutput
 8 | 
 9 | from .. import compositing_group
10 | 
11 | 
12 | @compositing_group.register(
13 |     schema_id="chainner:image:caption",
14 |     name="Add Caption",
15 |     description="Add a caption to the top or bottom of an image.",
16 |     icon="MdVideoLabel",
17 |     inputs=[
18 |         ImageInput(),
19 |         TextInput("Caption"),
20 |         NumberInput("Size", min=20, default=42, unit="px"),
21 |         EnumInput(
22 |             CaptionPosition,
23 |             "Position",
24 |             default=CaptionPosition.BOTTOM,
25 |             label_style="inline",
26 |         ),
27 |     ],
28 |     outputs=[
29 |         ImageOutput(
30 |             image_type="""
31 |                 // this value is defined by `add_caption`
32 |                 let captionHeight = Input2;
33 |                 Image {
34 |                     width: Input0.width,
35 |                     height: Input0.height + captionHeight,
36 |                     channels: Input0.channels,
37 |                 }
38 |                 """,
39 |             assume_normalized=True,
40 |         )
41 |     ],
42 | )
43 | def add_caption_node(
44 |     img: np.ndarray, caption: str, size: int, position: CaptionPosition
45 | ) -> np.ndarray:
46 |     return add_caption(img, caption, size, position)
47 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_utility/miscellaneous/generate_hash.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import base64
 4 | import hashlib
 5 | 
 6 | import numpy as np
 7 | 
 8 | from nodes.impl.image_utils import to_uint8
 9 | from nodes.properties.inputs import ImageInput, SliderInput
10 | from nodes.properties.outputs import TextOutput
11 | 
12 | from .. import miscellaneous_group
13 | 
14 | 
15 | @miscellaneous_group.register(
16 |     schema_id="chainner:image:generate_hash",
17 |     name="Generate Hash",
18 |     description="Generate a hash from an image using the BLAKE2b hashing algorithm.",
19 |     icon="MdCalculate",
20 |     inputs=[
21 |         ImageInput(),
22 |         SliderInput("Digest Size (in bytes)", min=1, max=64, default=8).with_docs(
23 |             "The digest size determines the length of the hash that is returned."
24 |         ),
25 |     ],
26 |     outputs=[
27 |         TextOutput("Hex"),
28 |         TextOutput("Base64"),
29 |     ],
30 | )
31 | def generate_hash_node(img: np.ndarray, size: int) -> tuple[str, str]:
32 |     img = np.ascontiguousarray(to_uint8(img))
33 |     h = hashlib.blake2b(img, digest_size=size)  # type: ignore
34 |     return h.hexdigest(), base64.urlsafe_b64encode(h.digest()).decode("utf-8")
35 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/image_utility/modification/flip.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from api import KeyInfo
 6 | from nodes.impl.image_utils import FlipAxis
 7 | from nodes.properties.inputs import EnumInput, ImageInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import modification_group
11 | 
12 | 
13 | @modification_group.register(
14 |     schema_id="chainner:image:flip",
15 |     name="Flip",
16 |     description="Flip an image.",
17 |     icon="MdFlip",
18 |     inputs=[
19 |         ImageInput("Image"),
20 |         EnumInput(FlipAxis),
21 |     ],
22 |     outputs=[ImageOutput(shape_as=0, assume_normalized=True)],
23 |     key_info=KeyInfo.enum(1),
24 | )
25 | def flip_node(img: np.ndarray, axis: FlipAxis) -> np.ndarray:
26 |     return axis.flip(img)
27 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/material_textures/__init__.py:
--------------------------------------------------------------------------------
1 | from .. import material_textures_category
2 | 
3 | normal_map_group = material_textures_category.add_node_group("Normal Map")
4 | conversion_group = material_textures_category.add_node_group("Conversion")
5 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/material_textures/normal_map/balance_normals.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | import navi
 6 | from nodes.impl.normals.util import gr_to_xyz, normalize_normals, xyz_to_bgr
 7 | from nodes.properties.inputs import ImageInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import normal_map_group
11 | 
12 | 
13 | @normal_map_group.register(
14 |     schema_id="chainner:image:balance_normals",
15 |     name="Balance Normals",
16 |     description=[
17 |         "This ensures that the average of all normals is pointing straight up. The input normal map is normalized before this operation is applied. The output normal map is guaranteed to be normalized.",
18 |     ],
19 |     icon="MdExpand",
20 |     inputs=[
21 |         ImageInput("Normal Map", channels=[3, 4]),
22 |     ],
23 |     outputs=[
24 |         ImageOutput("Normal Map", image_type=navi.Image(size_as="Input0"), channels=3),
25 |     ],
26 | )
27 | def balance_normals_node(n: np.ndarray) -> np.ndarray:
28 |     x, y, _ = gr_to_xyz(n)
29 | 
30 |     x -= np.mean(x)
31 |     y -= np.mean(y)
32 | 
33 |     return xyz_to_bgr(normalize_normals(x, y))
34 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/material_textures/normal_map/scale_normals.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | 
 5 | from nodes.impl.normals.addition import AdditionMethod, strengthen_normals
 6 | from nodes.impl.normals.util import xyz_to_bgr
 7 | from nodes.properties.inputs import EnumInput, ImageInput, SliderInput
 8 | from nodes.properties.outputs import ImageOutput
 9 | 
10 | from .. import normal_map_group
11 | 
12 | 
13 | @normal_map_group.register(
14 |     schema_id="chainner:image:strengthen_normals",
15 |     name="Scale Normals",
16 |     description=[
17 |         "Strengths or weakens the normals in the given normal map. Only the R and G channels of the input image will be used. The output normal map is guaranteed to be normalized.",
18 |         "Conceptually, this node is equivalent to `chainner:image:add_normals` with the strength of the second normal map set to 0.",
19 |     ],
20 |     icon="MdExpand",
21 |     inputs=[
22 |         ImageInput("Normal Map", channels=[3, 4]),
23 |         SliderInput("Strength", max=400, default=100),
24 |         EnumInput(
25 |             AdditionMethod,
26 |             label="Method",
27 |             default=AdditionMethod.PARTIAL_DERIVATIVES,
28 |         ),
29 |     ],
30 |     outputs=[
31 |         ImageOutput("Normal Map", size_as=0, channels=3),
32 |     ],
33 | )
34 | def scale_normals_node(
35 |     n: np.ndarray, strength: int, method: AdditionMethod
36 | ) -> np.ndarray:
37 |     return xyz_to_bgr(strengthen_normals(method, n, strength / 100))
38 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/__init__.py:
--------------------------------------------------------------------------------
 1 | from .. import utility_category
 2 | 
 3 | clipboard_group = utility_category.add_node_group("Clipboard")
 4 | value_group = utility_category.add_node_group("Value")
 5 | math_group = utility_category.add_node_group("Math")
 6 | text_group = utility_category.add_node_group("Text")
 7 | color_group = utility_category.add_node_group("Color")
 8 | random_group = utility_category.add_node_group("Random")
 9 | directory_group = utility_category.add_node_group("Directory")
10 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/clipboard/copy_to_clipboard.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | import numpy as np
 4 | from chainner_ext import Clipboard
 5 | 
 6 | from nodes.properties.inputs import ClipboardInput
 7 | 
 8 | from .. import clipboard_group
 9 | 
10 | 
11 | @clipboard_group.register(
12 |     schema_id="chainner:utility:copy_to_clipboard",
13 |     name="Copy To Clipboard",
14 |     description="Copies the input to the clipboard.",
15 |     icon="BsClipboard",
16 |     inputs=[
17 |         ClipboardInput(),
18 |     ],
19 |     outputs=[],
20 |     side_effects=True,
21 |     limited_to_8bpc="The image will be copied to clipboard with 8 bits/channel.",
22 | )
23 | def copy_to_clipboard_node(value: str | np.ndarray) -> None:
24 |     if isinstance(value, np.ndarray):
25 |         Clipboard.create_instance().write_image(value, "BGR")
26 |     else:
27 |         Clipboard.create_instance().write_text(value)
28 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/color/color.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from api import KeyInfo
 4 | from nodes.impl.color.color import Color
 5 | from nodes.properties.inputs import ColorInput
 6 | from nodes.properties.outputs import ColorOutput
 7 | 
 8 | from .. import color_group
 9 | 
10 | 
11 | @color_group.register(
12 |     schema_id="chainner:utility:color",
13 |     name="Color",
14 |     description="Outputs the given color.",
15 |     icon="MdColorLens",
16 |     inputs=[
17 |         ColorInput().make_fused(),
18 |     ],
19 |     outputs=[
20 |         ColorOutput(color_type="Input0").suggest(),
21 |     ],
22 |     key_info=KeyInfo.type(
23 |         """
24 |         let channels = Input0.channels;
25 |         match channels {
26 |             1 => "Gray",
27 |             3 => "RGB",
28 |             4 => "RGBA",
29 |             _ => never
30 |         }
31 |         """
32 |     ),
33 | )
34 | def color_node(color: Color) -> Color:
35 |     return color
36 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/directory/directory_go_up.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from pathlib import Path
 4 | 
 5 | from nodes.properties.inputs import DirectoryInput, NumberInput
 6 | from nodes.properties.outputs import DirectoryOutput
 7 | 
 8 | from .. import directory_group
 9 | 
10 | 
11 | @directory_group.register(
12 |     schema_id="chainner:utility:back_directory",
13 |     name="Directory Go Up",
14 |     description="Traverse up from a directory the specified number of times.",
15 |     icon="BsFolder",
16 |     inputs=[
17 |         DirectoryInput(must_exist=False, label_style="hidden"),
18 |         NumberInput("Times", min=0, default=1, label_style="inline").with_docs(
19 |             "How many times to go up.",
20 |             "- 0 will return the given directory as is.",
21 |             "- 1 will return the parent directory.",
22 |             "- 2 will return the grandparent directory.",
23 |             "- etc.",
24 |             hint=True,
25 |         ),
26 |     ],
27 |     outputs=[
28 |         DirectoryOutput(
29 |             output_type="Directory { path: getParentDirectory(Input0.path, Input1) }",
30 |         ),
31 |     ],
32 | )
33 | def directory_go_up_node(directory: Path, amt: int) -> Path:
34 |     result = directory
35 |     for _ in range(amt):
36 |         result = result.parent
37 |     return result
38 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/directory/directory_to_text.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from pathlib import Path
 4 | 
 5 | from nodes.properties.inputs import DirectoryInput
 6 | from nodes.properties.outputs import TextOutput
 7 | 
 8 | from .. import directory_group
 9 | 
10 | 
11 | @directory_group.register(
12 |     schema_id="chainner:utility:directory_to_text",
13 |     name="Directory to Text",
14 |     description="Converts a directory path into text.",
15 |     icon="BsFolder",
16 |     inputs=[
17 |         DirectoryInput(must_exist=False, label_style="hidden"),
18 |     ],
19 |     outputs=[
20 |         TextOutput(
21 |             "Dir Text",
22 |             output_type="Input0.path",
23 |         ),
24 |     ],
25 | )
26 | def directory_to_text_node(directory: Path) -> str:
27 |     return str(directory)
28 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/random/random_number.py:
--------------------------------------------------------------------------------
 1 | from random import Random
 2 | 
 3 | from nodes.groups import seed_group
 4 | from nodes.properties.inputs import NumberInput, SeedInput
 5 | from nodes.properties.outputs import NumberOutput
 6 | from nodes.utils.seed import Seed
 7 | 
 8 | from .. import random_group
 9 | 
10 | 
11 | @random_group.register(
12 |     schema_id="chainner:utility:random_number",
13 |     name="Random Number",
14 |     description="Generate a random integer.",
15 |     icon="MdCalculate",
16 |     inputs=[
17 |         NumberInput("Min", min=None, max=None, default=0),
18 |         NumberInput("Max", min=None, max=None, default=100),
19 |         seed_group(SeedInput()),
20 |     ],
21 |     outputs=[
22 |         NumberOutput("Result", output_type="int & max(.., Input0) & min(.., Input1)")
23 |     ],
24 | )
25 | def random_number_node(min_val: int, max_val: int, seed: Seed) -> int:
26 |     return Random(seed.value).randint(min_val, max_val)
27 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/text/note.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.properties.inputs import BoolInput, TextInput
 4 | 
 5 | from .. import text_group
 6 | 
 7 | 
 8 | # This node is a bit special as it has special handling by the frontend. Changes made here will not necessarily be reflected in the frontend.
 9 | @text_group.register(
10 |     schema_id="chainner:utility:note",
11 |     name="Note",
12 |     description="Make a sticky note for whatever notes or comments you want to leave in the chain. Supports markdown syntax",
13 |     icon="MdOutlineStickyNote2",
14 |     inputs=[
15 |         TextInput(
16 |             "Note Text",
17 |             multiline=True,
18 |             has_handle=False,
19 |             label_style="hidden",
20 |         ).make_optional(),
21 |         BoolInput("Display Markdown", default=False),
22 |     ],
23 |     outputs=[],
24 | )
25 | def note_node(_text: str | None, display_markdown: bool) -> None:
26 |     return
27 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/text/text_length.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.properties.inputs import TextInput
 4 | from nodes.properties.outputs import NumberOutput
 5 | 
 6 | from .. import text_group
 7 | 
 8 | 
 9 | @text_group.register(
10 |     schema_id="chainner:utility:text_length",
11 |     name="Text Length",
12 |     description="Returns the number characters in a string of text.",
13 |     icon="MdTextFields",
14 |     inputs=[
15 |         TextInput("Text"),
16 |     ],
17 |     outputs=[
18 |         NumberOutput("Length", output_type="string::len(Input0)"),
19 |     ],
20 | )
21 | def text_length_node(text: str) -> int:
22 |     return len(text)
23 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/conditional.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from api import Lazy
 4 | from nodes.properties.inputs import AnyInput, BoolInput
 5 | from nodes.properties.outputs import BaseOutput
 6 | 
 7 | from .. import value_group
 8 | 
 9 | 
10 | @value_group.register(
11 |     schema_id="chainner:utility:conditional",
12 |     name="Conditional",
13 |     description="Allows you to pass in multiple inputs and then change which one passes through to the output.",
14 |     icon="BsShuffle",
15 |     inputs=[
16 |         BoolInput("Condition", default=True, has_handle=True).with_id(0),
17 |         AnyInput(label="If True").with_id(1).make_lazy(),
18 |         AnyInput(label="If False").with_id(2).make_lazy(),
19 |     ],
20 |     outputs=[
21 |         BaseOutput(
22 |             output_type="""
23 |             if Input0 { Input1 } else { Input2 }
24 |             """,
25 |             label="Value",
26 |         ).as_passthrough_of(1),
27 |     ],
28 |     see_also=["chainner:utility:switch"],
29 | )
30 | def conditional_node(
31 |     cond: bool, if_true: Lazy[object], if_false: Lazy[object]
32 | ) -> object:
33 |     return if_true.value if cond else if_false.value
34 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/directory.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from pathlib import Path
 4 | 
 5 | from nodes.properties.inputs import DirectoryInput
 6 | from nodes.properties.outputs import DirectoryOutput
 7 | 
 8 | from .. import value_group
 9 | 
10 | 
11 | @value_group.register(
12 |     schema_id="chainner:utility:directory",
13 |     name="Directory",
14 |     description="Outputs the given directory.",
15 |     icon="BsFolder",
16 |     inputs=[
17 |         DirectoryInput(must_exist=False, label_style="hidden").make_fused(),
18 |     ],
19 |     outputs=[
20 |         DirectoryOutput(output_type="Input0").suggest(),
21 |     ],
22 | )
23 | def directory_node(directory: Path) -> Path:
24 |     return directory
25 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/execution_number.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.properties.inputs.__system_inputs import StaticValueInput
 4 | from nodes.properties.outputs import NumberOutput
 5 | 
 6 | from .. import value_group
 7 | 
 8 | 
 9 | @value_group.register(
10 |     schema_id="chainner:utility:execution_number",
11 |     name="Execution Number",
12 |     description="Get the current execution number of this session. Increments by 1 every time you press the run button.",
13 |     icon="MdNumbers",
14 |     inputs=[
15 |         StaticValueInput(
16 |             label="Value",
17 |             value="execution_number",
18 |             navi_type="int(1..)",
19 |             py_type=int,
20 |         ).make_fused(),
21 |     ],
22 |     outputs=[
23 |         NumberOutput("Execution Number", output_type="Input0"),
24 |     ],
25 | )
26 | def execution_number_node(number: int) -> int:
27 |     return number
28 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/number.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from api import KeyInfo
 4 | from nodes.properties.inputs import NumberInput
 5 | from nodes.properties.outputs import NumberOutput
 6 | 
 7 | from .. import value_group
 8 | 
 9 | 
10 | @value_group.register(
11 |     schema_id="chainner:utility:number",
12 |     name="Number",
13 |     description="Outputs the given number.",
14 |     icon="MdCalculate",
15 |     inputs=[
16 |         NumberInput(
17 |             "Number",
18 |             min=None,
19 |             max=None,
20 |             precision="unlimited",
21 |             step=1,
22 |             label_style="hidden",
23 |         ).make_fused(),
24 |     ],
25 |     outputs=[
26 |         NumberOutput("Number", output_type="Input0").suggest(),
27 |     ],
28 |     key_info=KeyInfo.number(0),
29 | )
30 | def number_node(number: float) -> float:
31 |     return number
32 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/parse_number.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.properties.inputs import NumberInput, TextInput
 4 | from nodes.properties.outputs import NumberOutput
 5 | 
 6 | from .. import value_group
 7 | 
 8 | 
 9 | @value_group.register(
10 |     schema_id="chainner:utility:parse_number",
11 |     name="Parse Number",
12 |     description="Parses text to base-10.",
13 |     icon="MdCalculate",
14 |     inputs=[
15 |         TextInput("Text", label_style="inline"),
16 |         NumberInput("Base", default=10, min=2, max=36),
17 |     ],
18 |     outputs=[
19 |         NumberOutput(
20 |             "Value",
21 |             output_type="int & number::parseInt(Input0, Input1)",
22 |         ).with_never_reason("The given text cannot be parsed into a number."),
23 |     ],
24 | )
25 | def parse_number_node(text: str, base: int) -> int:
26 |     return int(text, base)
27 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/percent.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from api import KeyInfo
 4 | from nodes.properties.inputs import SliderInput
 5 | from nodes.properties.outputs import NumberOutput
 6 | 
 7 | from .. import value_group
 8 | 
 9 | 
10 | @value_group.register(
11 |     schema_id="chainner:utility:percent",
12 |     name="Percent",
13 |     description="Outputs the given percent.",
14 |     icon="MdCalculate",
15 |     inputs=[
16 |         SliderInput("Percent", min=0, max=100, default=50, unit="%").make_fused(),
17 |     ],
18 |     outputs=[
19 |         NumberOutput("Percent", output_type="Input0"),
20 |     ],
21 |     key_info=KeyInfo.number(0),
22 | )
23 | def percent_node(number: int) -> int:
24 |     return number
25 | 


--------------------------------------------------------------------------------
/backend/src/packages/chaiNNer_standard/utility/value/text.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from nodes.properties.inputs import TextInput
 4 | from nodes.properties.outputs import TextOutput
 5 | 
 6 | from .. import value_group
 7 | 
 8 | 
 9 | @value_group.register(
10 |     schema_id="chainner:utility:text",
11 |     name="Text",
12 |     description="Outputs the given text.",
13 |     icon="MdTextFields",
14 |     inputs=[
15 |         TextInput(
16 |             "Text", min_length=0, label_style="hidden", allow_empty_string=True
17 |         ).make_fused(),
18 |     ],
19 |     outputs=[
20 |         TextOutput("Text", output_type="Input0").suggest(),
21 |     ],
22 | )
23 | def text_node(text: str) -> str:
24 |     return text
25 | 


--------------------------------------------------------------------------------
/backend/src/response.py:
--------------------------------------------------------------------------------
 1 | from __future__ import annotations
 2 | 
 3 | from typing import Literal, TypedDict
 4 | 
 5 | from events import ExecutionErrorSource
 6 | from process import NodeExecutionError
 7 | 
 8 | 
 9 | class SuccessResponse(TypedDict):
10 |     type: Literal["success"]
11 | 
12 | 
13 | class ErrorResponse(TypedDict):
14 |     type: Literal["error"]
15 |     message: str
16 |     exception: str
17 |     source: ExecutionErrorSource | None
18 | 
19 | 
20 | class NoExecutorResponse(TypedDict):
21 |     type: Literal["no-executor"]
22 | 
23 | 
24 | class AlreadyRunningResponse(TypedDict):
25 |     type: Literal["already-running"]
26 |     message: str
27 | 
28 | 
29 | def success_response() -> SuccessResponse:
30 |     return {"type": "success"}
31 | 
32 | 
33 | def error_response(
34 |     message: str,
35 |     exception: str | Exception,
36 |     source: ExecutionErrorSource | None = None,
37 | ) -> ErrorResponse:
38 |     if source is None and isinstance(exception, NodeExecutionError):
39 |         source = {
40 |             "nodeId": exception.node_id,
41 |             "schemaId": exception.node_data.schema_id,
42 |             "inputs": exception.inputs,
43 |         }
44 |     return {
45 |         "type": "error",
46 |         "message": message,
47 |         "exception": str(exception),
48 |         "source": source,
49 |     }
50 | 
51 | 
52 | def no_executor_response() -> NoExecutorResponse:
53 |     return {"type": "no-executor"}
54 | 
55 | 
56 | def already_running_response(message: str) -> AlreadyRunningResponse:
57 |     return {"type": "already-running", "message": message}
58 | 


--------------------------------------------------------------------------------
/backend/src/run.py:
--------------------------------------------------------------------------------
1 | import importlib
2 | 
3 | # Install server dependencies. Can't start the server without them, but we don't want to install the other deps yet.
4 | importlib.import_module("dependencies.install_server_deps")
5 | 
6 | # Start the host server
7 | server_host = importlib.import_module("server_host")
8 | server_host.main()
9 | 


--------------------------------------------------------------------------------
/backend/src/system.py:
--------------------------------------------------------------------------------
1 | import platform
2 | import sys
3 | 
4 | is_mac = sys.platform == "darwin"
5 | is_arm_mac = is_mac and platform.machine() == "arm64"
6 | is_windows = sys.platform == "win32"
7 | is_linux = sys.platform == "linux"
8 | 


--------------------------------------------------------------------------------
/backend/src/texconv/LICENSE:
--------------------------------------------------------------------------------
 1 |                                The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2011-2022 Microsoft Corp
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this
 6 | software and associated documentation files (the "Software"), to deal in the Software
 7 | without restriction, including without limitation the rights to use, copy, modify,
 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 9 | permit persons to whom the Software is furnished to do so, subject to the following
10 | conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all copies
13 | or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
17 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
18 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | 


--------------------------------------------------------------------------------
/backend/src/texconv/texconv.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/backend/src/texconv/texconv.exe


--------------------------------------------------------------------------------
/backend/tests/test_dummy.py:
--------------------------------------------------------------------------------
1 | from navi import Image
2 | 
3 | 
4 | def test_dummy():
5 |     Image(1, 1, 3)
6 | 


--------------------------------------------------------------------------------
/docs/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/docs/assets/banner.png


--------------------------------------------------------------------------------
/docs/assets/input-override.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/docs/assets/input-override.png


--------------------------------------------------------------------------------
/docs/assets/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/docs/assets/screenshot.png


--------------------------------------------------------------------------------
/docs/assets/simple_screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/docs/assets/simple_screenshot.png


--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | <html>
 3 |   <head>
 4 |     <meta charset="UTF-8" />
 5 |     <title>chaiNNer</title>
 6 | 
 7 |   </head>
 8 |   <body>
 9 |     <div id="root">
10 | 
11 |     </div>
12 |     <script>
13 |       if (global === undefined) {
14 |         var global = window;
15 |       }
16 |     </script>
17 |     <script type="module" src="./src/renderer/renderer.ts"></script>
18 |   </body>
19 | </html>
20 | 


--------------------------------------------------------------------------------
/patches/@electron-forge+plugin-vite+7.4.0.patch:
--------------------------------------------------------------------------------
 1 | diff --git a/node_modules/@electron-forge/plugin-vite/dist/util/package.js b/node_modules/@electron-forge/plugin-vite/dist/util/package.js
 2 | index be2a9f9..cf61a90 100644
 3 | --- a/node_modules/@electron-forge/plugin-vite/dist/util/package.js
 4 | +++ b/node_modules/@electron-forge/plugin-vite/dist/util/package.js
 5 | @@ -34,7 +34,10 @@ async function lookupNodeModulesPaths(root, paths = []) {
 6 |  }
 7 |  exports.lookupNodeModulesPaths = lookupNodeModulesPaths;
 8 |  async function resolveDependencies(root) {
 9 | -    const rootDependencies = Object.keys((await fs_extra_1.default.readJson(node_path_1.default.join(root, 'package.json'))).dependencies || {});
10 | +    // FIXME: spm patch to allow tree shaking with vite and forge
11 | +    //
12 | +    // const rootDependencies = Object.keys((await fs_extra_1.default.readJson(node_path_1.default.join(root, 'package.json'))).dependencies || {});
13 | +    const rootDependencies = [];
14 |      const resolve = async (prePath, dependencies, collected = new Map()) => await Promise.all(dependencies.map(async (name) => {
15 |          let curPath = prePath, depPath = null, packageJson = null;
16 |          while (!packageJson && !isRootPath(curPath)) {
17 | 


--------------------------------------------------------------------------------
/pyrightconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"include": [
 3 | 		"backend"
 4 | 	],
 5 | 	"extraPaths": [
 6 | 		"backend/src"
 7 | 	],
 8 | 	"exclude": [
 9 | 		"**/__pycache__"
10 | 	],
11 | 	"ignore": [],
12 | 	"typeCheckingMode": "basic",
13 | 	"useLibraryCodeForTypes": true,
14 | 	"strictListInference": true,
15 | 	"strictDictionaryInference": true,
16 | 	"strictSetInference": true,
17 | 	"reportDuplicateImport": "warning",
18 | 	"reportImportCycles": "error",
19 | 	"reportIncompatibleVariableOverride": "error",
20 | 	"reportIncompatibleMethodOverride": "error",
21 | 	"reportOverlappingOverload": "error",
22 | 	"reportPrivateImportUsage": "error",
23 | 	"reportUninitializedInstanceVariable": "error",
24 | 	"reportUnnecessaryCast": "error",
25 | 	"reportUnnecessaryComparison": "error",
26 | 	"reportUnnecessaryContains": "error",
27 | 	"reportUnnecessaryIsInstance": "error",
28 | 	// "reportUnnecessaryTypeIgnoreComment": "warning",
29 | 	"reportUnusedClass": "warning",
30 | 	"reportUnusedFunction": "warning",
31 | 	"reportUnusedImport": "warning",
32 | 	"reportUnusedVariable": "warning",
33 | }
34 | 


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | ruff==0.4.0
2 | debugpy
3 | pyright==1.1.338
4 | pytest
5 | appdirs==1.4.4
6 | viztracer
7 | 


--------------------------------------------------------------------------------
/src/common/CategoryMap.ts:
--------------------------------------------------------------------------------
 1 | import { Category, CategoryId, NodeGroup, NodeGroupId } from './common-types';
 2 | 
 3 | export class CategoryMap {
 4 |     /**
 5 |      * An ordered list of all categories supported by the backend.
 6 |      *
 7 |      * Some categories might be empty.
 8 |      */
 9 |     readonly categories: readonly Category[];
10 | 
11 |     private readonly lookup: ReadonlyMap<CategoryId, Category>;
12 | 
13 |     private readonly lookupGroup: ReadonlyMap<NodeGroupId, NodeGroup>;
14 | 
15 |     static readonly EMPTY: CategoryMap = new CategoryMap([]);
16 | 
17 |     constructor(categories: readonly Category[]) {
18 |         // defensive copy
19 |         this.categories = [...categories];
20 |         this.lookup = new Map(categories.map((c) => [c.id, c] as const));
21 |         this.lookupGroup = new Map(
22 |             categories.flatMap((c) => c.groups).map((g) => [g.id, g] as const)
23 |         );
24 |     }
25 | 
26 |     get(id: CategoryId): Category | undefined {
27 |         return this.lookup.get(id);
28 |     }
29 | 
30 |     getGroup(id: NodeGroupId): NodeGroup | undefined {
31 |         return this.lookupGroup.get(id);
32 |     }
33 | }
34 | 


--------------------------------------------------------------------------------
/src/common/Validity.ts:
--------------------------------------------------------------------------------
 1 | export type Validity =
 2 |     | { readonly isValid: true }
 3 |     | { readonly isValid: false; readonly reason: string };
 4 | 
 5 | export const VALID: Validity = { isValid: true };
 6 | 
 7 | export const invalid = (reason: string): Validity => ({ isValid: false, reason });
 8 | 
 9 | export const bothValid = (a: Validity, b: Validity): Validity => {
10 |     if (!a.isValid) return a;
11 |     if (!b.isValid) return b;
12 |     return VALID;
13 | };
14 | 


--------------------------------------------------------------------------------
/src/common/i18n.ts:
--------------------------------------------------------------------------------
 1 | import { InitOptions } from 'i18next';
 2 | import en from './locales/en/translation.json';
 3 | 
 4 | const resources = {
 5 |     en,
 6 | };
 7 | 
 8 | export const DEFAULT_OPTIONS: InitOptions = {
 9 |     resources,
10 |     lng: 'en',
11 | 
12 |     interpolation: {
13 |         escapeValue: false,
14 |     },
15 | 
16 |     returnNull: false,
17 | };
18 | 


--------------------------------------------------------------------------------
/src/common/input-override-common.ts:
--------------------------------------------------------------------------------
 1 | import { InputId } from './common-types';
 2 | 
 3 | export type InputOverrideId = string & { readonly __input_override_id?: never };
 4 | 
 5 | export const createInputOverrideId = (nodeId: string, inputId: InputId): InputOverrideId => {
 6 |     if (nodeId.length !== 36)
 7 |         throw new Error('Expected node id to be a 36 character hexadecimal UUID.');
 8 |     return `#${nodeId}:${inputId}`;
 9 | };
10 | 


--------------------------------------------------------------------------------
/src/common/links.ts:
--------------------------------------------------------------------------------
1 | export const links = {
2 |     kofi: 'https://ko-fi.com/T6T46KTTW',
3 | };
4 | 


--------------------------------------------------------------------------------
/src/common/nodes/checkFeatures.ts:
--------------------------------------------------------------------------------
 1 | import { Feature, FeatureId, FeatureState } from '../common-types';
 2 | import { joinEnglish } from '../util';
 3 | import { VALID, Validity, invalid } from '../Validity';
 4 | 
 5 | export const checkFeatures = (
 6 |     features: readonly FeatureId[],
 7 |     featureMap: ReadonlyMap<FeatureId, Feature>,
 8 |     featureStates: ReadonlyMap<FeatureId, FeatureState>
 9 | ): Validity => {
10 |     const nonEnabledFeatures = features.filter((f) => !featureStates.get(f)?.enabled);
11 |     if (nonEnabledFeatures.length === 0) return VALID;
12 | 
13 |     const getName = (f: FeatureId) => featureMap.get(f)?.name ?? f;
14 | 
15 |     let prefix;
16 |     if (nonEnabledFeatures.length === 1) {
17 |         prefix = `The feature ${getName(nonEnabledFeatures[0])} is`;
18 |     } else {
19 |         prefix = `The features ${joinEnglish(nonEnabledFeatures.map(getName))} are`;
20 |     }
21 | 
22 |     return invalid(
23 |         `${prefix} required to run this node. See the dependency manager for more details.`
24 |     );
25 | };
26 | 


--------------------------------------------------------------------------------
/src/common/nodes/inputCondition.ts:
--------------------------------------------------------------------------------
 1 | import { Condition, Group, InputId, NodeSchema } from '../common-types';
 2 | import { lazyKeyed } from '../util';
 3 | 
 4 | const analyseInputs = lazyKeyed((schema: NodeSchema): ReadonlyMap<InputId, Condition> => {
 5 |     const byInput = new Map<InputId, Condition>();
 6 | 
 7 |     const conditionStack: Condition[] = [];
 8 |     const recurse = (inputs: readonly (InputId | Group)[]): void => {
 9 |         for (const i of inputs) {
10 |             if (typeof i === 'object') {
11 |                 if (i.kind === 'conditional') {
12 |                     conditionStack.push(i.options.condition);
13 |                     recurse(i.items);
14 |                     conditionStack.pop();
15 |                 }
16 |             } else {
17 |                 byInput.set(i, {
18 |                     kind: 'and',
19 |                     items: [...conditionStack],
20 |                 });
21 |             }
22 |         }
23 |     };
24 | 
25 |     recurse(schema.groupLayout);
26 | 
27 |     return byInput;
28 | });
29 | 
30 | export const getInputCondition = (schema: NodeSchema, inputId: InputId): Condition | undefined => {
31 |     return analyseInputs(schema).get(inputId);
32 | };
33 | 


--------------------------------------------------------------------------------
/src/common/nodes/sort.ts:
--------------------------------------------------------------------------------
 1 | import { CategoryMap } from '../CategoryMap';
 2 | import { NodeGroup, NodeSchema } from '../common-types';
 3 | import { groupBy } from '../util';
 4 | 
 5 | const sortGroupNodes = (nodes: readonly NodeSchema[], group: NodeGroup): NodeSchema[] => {
 6 |     const ordered: NodeSchema[] = [];
 7 |     const unordered: NodeSchema[] = [];
 8 | 
 9 |     for (const n of nodes) {
10 |         if (group.order.includes(n.schemaId)) {
11 |             ordered.push(n);
12 |         } else {
13 |             unordered.push(n);
14 |         }
15 |     }
16 | 
17 |     ordered.sort((a, b) => group.order.indexOf(a.schemaId) - group.order.indexOf(b.schemaId));
18 | 
19 |     unordered.sort((a, b) => {
20 |         return a.name.localeCompare(b.name);
21 |     });
22 | 
23 |     return [...ordered, ...unordered];
24 | };
25 | 
26 | export const sortNodes = (nodes: readonly NodeSchema[], categories: CategoryMap): NodeSchema[] => {
27 |     const byGroup = groupBy(nodes, 'nodeGroup');
28 | 
29 |     return categories.categories
30 |         .flatMap((c) => c.groups)
31 |         .flatMap((g) => sortGroupNodes(byGroup.get(g.id) ?? [], g));
32 | };
33 | 


--------------------------------------------------------------------------------
/src/common/rust-regex.ts:
--------------------------------------------------------------------------------
 1 | import wasmUrl from 'rregex/lib/rregex.wasm?url';
 2 | import init, { RRegex as _rr } from 'rregex/lib/web';
 3 | import { log } from './log';
 4 | 
 5 | // This is not good, but I can't think of a better way.
 6 | // We are racing loading the wasm module and using it.
 7 | init(wasmUrl).catch(log.error);
 8 | 
 9 | export class RRegex extends _rr {}
10 | 


--------------------------------------------------------------------------------
/src/common/settings/settings.ts:
--------------------------------------------------------------------------------
 1 | import { PackageSettings, SchemaId, WindowSize } from '../common-types';
 2 | 
 3 | export interface ChainnerSettings {
 4 |     useSystemPython: boolean;
 5 |     systemPythonLocation: string;
 6 | 
 7 |     // renderer
 8 |     theme: string;
 9 |     checkForUpdatesOnStartup: boolean;
10 |     startupTemplate: string;
11 |     animateChain: boolean;
12 |     snapToGrid: boolean;
13 |     snapToGridAmount: number;
14 |     viewportExportPadding: number;
15 |     showMinimap: boolean;
16 | 
17 |     experimentalFeatures: boolean;
18 |     hardwareAcceleration: boolean;
19 |     allowMultipleInstances: boolean;
20 | 
21 |     lastWindowSize: WindowSize;
22 | 
23 |     favoriteNodes: readonly SchemaId[];
24 | 
25 |     packageSettings: PackageSettings;
26 | 
27 |     storage: Readonly<Record<string, unknown>>;
28 | }
29 | 
30 | export const defaultSettings: Readonly<ChainnerSettings> = {
31 |     useSystemPython: false,
32 |     systemPythonLocation: '',
33 | 
34 |     // renderer
35 |     theme: 'default-dark',
36 |     checkForUpdatesOnStartup: true,
37 |     startupTemplate: '',
38 |     animateChain: true,
39 |     snapToGrid: false,
40 |     snapToGridAmount: 16,
41 |     viewportExportPadding: 20,
42 |     showMinimap: false,
43 | 
44 |     experimentalFeatures: false,
45 |     hardwareAcceleration: false,
46 |     allowMultipleInstances: false,
47 | 
48 |     lastWindowSize: {
49 |         maximized: false,
50 |         width: 1280,
51 |         height: 720,
52 |     },
53 | 
54 |     favoriteNodes: [],
55 | 
56 |     packageSettings: {},
57 | 
58 |     storage: {},
59 | };
60 | 


--------------------------------------------------------------------------------
/src/common/ui/error.ts:
--------------------------------------------------------------------------------
 1 | import { InterruptRequest } from './interrupt';
 2 | 
 3 | export class CriticalError extends Error {
 4 |     interrupt: InterruptRequest;
 5 | 
 6 |     constructor(interrupt: Omit<InterruptRequest, 'type'>) {
 7 |         super(interrupt.message);
 8 |         this.interrupt = { type: 'critical error', ...interrupt };
 9 |     }
10 | }
11 | 


--------------------------------------------------------------------------------
/src/common/ui/interrupt.ts:
--------------------------------------------------------------------------------
 1 | interface ActionBase<T extends string> {
 2 |     readonly type: T;
 3 | }
 4 | 
 5 | interface OpenUrlAction extends ActionBase<'open-url'> {
 6 |     url: string;
 7 | }
 8 | interface RunAction extends ActionBase<'run'> {
 9 |     action: () => void;
10 | }
11 | 
12 | export type Action = OpenUrlAction | RunAction;
13 | 
14 | export interface InteractionOption {
15 |     title: string;
16 |     action: Action;
17 | }
18 | 
19 | export type InterruptType = 'critical error' | 'warning';
20 | 
21 | export interface InterruptRequest {
22 |     type: InterruptType;
23 |     title?: string;
24 |     message: string;
25 |     options?: InteractionOption[];
26 | }
27 | 


--------------------------------------------------------------------------------
/src/common/version.ts:
--------------------------------------------------------------------------------
 1 | import semver from 'semver';
 2 | import { Version } from './common-types';
 3 | 
 4 | export const parse = (v: string): Version => {
 5 |     const version = semver.coerce(v);
 6 |     if (!version) {
 7 |         throw new SyntaxError(`Invalid version '${v}'`);
 8 |     }
 9 |     return version.version as Version;
10 | };
11 | 
12 | export const versionLt = (lhs: Version, rhs: Version): boolean => {
13 |     try {
14 |         return semver.lt(parse(lhs), parse(rhs));
15 |     } catch {
16 |         return false;
17 |     }
18 | };
19 | 
20 | export const versionLte = (lhs: Version, rhs: Version): boolean => {
21 |     try {
22 |         return semver.lte(parse(lhs), parse(rhs));
23 |     } catch {
24 |         return false;
25 |     }
26 | };
27 | 
28 | export const versionGt = (lhs: Version, rhs: Version): boolean => {
29 |     try {
30 |         return semver.gt(parse(lhs), parse(rhs));
31 |     } catch {
32 |         return false;
33 |     }
34 | };
35 | 
36 | export const versionGte = (lhs: Version, rhs: Version): boolean => {
37 |     try {
38 |         return semver.gte(parse(lhs), parse(rhs));
39 |     } catch {
40 |         return false;
41 |     }
42 | };
43 | 


--------------------------------------------------------------------------------
/src/custom.d.ts:
--------------------------------------------------------------------------------
 1 | /* eslint-disable import/no-default-export */
 2 | 
 3 | declare module '*.svg' {
 4 |     const content: string;
 5 |     export default content;
 6 | }
 7 | declare module '*.png' {
 8 |     const content: string;
 9 |     export default content;
10 | }
11 | declare module '*.gif' {
12 |     const content: string;
13 |     export default content;
14 | }
15 | declare module '*.jpg' {
16 |     const content: string;
17 |     export default content;
18 | }
19 | declare module '*.jpeg' {
20 |     const content: string;
21 |     export default content;
22 | }
23 | 
24 | declare module 'rregex/lib/rregex.wasm?url' {
25 |     const content: string;
26 |     export default content;
27 | }
28 | 
29 | declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
30 | declare const MAIN_WINDOW_VITE_NAME: string;
31 | 


--------------------------------------------------------------------------------
/src/globals.d.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line no-var
2 | declare var startupFile: string | null;
3 | 
4 | interface Window {
5 |     unsafeIpcRenderer: import('electron').IpcRenderer;
6 | }
7 | 


--------------------------------------------------------------------------------
/src/i18next.d.ts:
--------------------------------------------------------------------------------
 1 | // https://www.i18next.com/overview/typescript#create-a-declaration-file
 2 | // https://www.i18next.com/overview/typescript#custom-type-options
 3 | 
 4 | import 'i18next';
 5 | 
 6 | declare module 'i18next' {
 7 |     // Extend CustomTypeOptions
 8 |     interface CustomTypeOptions {
 9 |         returnNull: false;
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/main/childProc.ts:
--------------------------------------------------------------------------------
 1 | import { spawn } from 'child_process';
 2 | 
 3 | export const promisifiedSpawn = async (command: string, args: string[]): Promise<string> => {
 4 |     return new Promise((resolve, reject) => {
 5 |         const proc = spawn(command, args);
 6 |         let stdout = '';
 7 |         let stderr = '';
 8 |         proc.stdout.on('data', (data) => {
 9 |             stdout += data;
10 |         });
11 |         proc.stderr.on('data', (data) => {
12 |             stderr += data;
13 |         });
14 |         proc.on('error', (err) => {
15 |             reject(err);
16 |         });
17 |         proc.on('close', (code) => {
18 |             if (code !== 0) {
19 |                 reject(new Error(stderr));
20 |             } else {
21 |                 resolve(stdout);
22 |             }
23 |         });
24 |     });
25 | };
26 | 


--------------------------------------------------------------------------------
/src/main/cli/create.ts:
--------------------------------------------------------------------------------
 1 | import electronLog from 'electron-log';
 2 | import { app } from 'electron/main';
 3 | import { log } from '../../common/log';
 4 | import { Exit } from './exit';
 5 | 
 6 | const fatalErrorInMain = (error: unknown) => {
 7 |     log.error('Error in Main process');
 8 |     log.error(error);
 9 |     app.exit(1);
10 | };
11 | 
12 | const setupErrorHandling = () => {
13 |     electronLog.catchErrors({
14 |         showDialog: false,
15 |         onError: fatalErrorInMain,
16 |     });
17 | 
18 |     process.on('uncaughtException', fatalErrorInMain);
19 | };
20 | 
21 | export const createCli = (command: () => Promise<void>) => {
22 |     setupErrorHandling();
23 | 
24 |     // we don't need hardware acceleration at all
25 |     app.disableHardwareAcceleration();
26 | 
27 |     command().then(
28 |         () => {
29 |             app.exit(0);
30 |         },
31 |         (error) => {
32 |             if (error instanceof Exit) {
33 |                 app.exit(error.exitCode);
34 |             } else {
35 |                 log.error(error);
36 |                 app.exit(1);
37 |             }
38 |         }
39 |     );
40 | };
41 | 


--------------------------------------------------------------------------------
/src/main/cli/exit.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * An error that signals that the CLI app should exit in error with a given non-zero exit code.
 3 |  */
 4 | export class Exit extends Error {
 5 |     exitCode: number;
 6 | 
 7 |     constructor(exitCode = 1) {
 8 |         super(`Exit with code ${exitCode}`);
 9 |         this.exitCode = exitCode;
10 |     }
11 | }
12 | 


--------------------------------------------------------------------------------
/src/main/env.ts:
--------------------------------------------------------------------------------
 1 | import os from 'os';
 2 | 
 3 | export const isMac = process.platform === 'darwin';
 4 | const cpuModel = os.cpus()[0]?.model || null;
 5 | export const isArmMac: boolean = isMac && !!cpuModel && /Apple M\d/i.test(cpuModel);
 6 | 
 7 | const env = { ...process.env };
 8 | delete env.PYTHONHOME;
 9 | export const sanitizedEnv = env;
10 | 


--------------------------------------------------------------------------------
/src/main/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import backend from 'i18next-http-backend';
3 | import { DEFAULT_OPTIONS } from '../common/i18n';
4 | import { log } from '../common/log';
5 | 
6 | i18n.use(backend)
7 |     .init({ ...DEFAULT_OPTIONS, saveMissing: true })
8 |     .catch(log.error);
9 | 


--------------------------------------------------------------------------------
/src/main/platform.ts:
--------------------------------------------------------------------------------
 1 | import { app } from 'electron/main';
 2 | import { existsSync } from 'fs';
 3 | import os from 'os';
 4 | import path from 'path';
 5 | import { lazy } from '../common/util';
 6 | 
 7 | export type SupportedPlatform = 'linux' | 'darwin' | 'win32';
 8 | 
 9 | export const getPlatform = (): SupportedPlatform => {
10 |     const platform = os.platform();
11 |     switch (platform) {
12 |         case 'win32':
13 |         case 'linux':
14 |         case 'darwin':
15 |             return platform;
16 |         default:
17 |             throw new Error(
18 |                 `Unsupported platform: ${platform}. Please report this to us and we may add support.`
19 |             );
20 |     }
21 | };
22 | 
23 | export const currentExecutableDir = path.dirname(app.getPath('exe'));
24 | 
25 | export const getIsPortableSync = lazy((): boolean => {
26 |     const isPortable = existsSync(path.join(currentExecutableDir, 'portable'));
27 |     return isPortable;
28 | });
29 | 
30 | export const getRootDir = lazy((): string => {
31 |     const isPortable = getIsPortableSync();
32 |     const rootDir = isPortable ? currentExecutableDir : app.getPath('userData');
33 |     return rootDir;
34 | });
35 | 
36 | export const getLogsFolder = lazy((): string => {
37 |     return path.join(getRootDir(), 'logs');
38 | });
39 | 
40 | export const getBackendStorageFolder = lazy((): string => {
41 |     return path.join(getRootDir(), 'backend-storage');
42 | });
43 | 
44 | export const installDir = getIsPortableSync()
45 |     ? path.dirname(app.getPath('exe'))
46 |     : path.join(path.dirname(app.getPath('exe')), '..');
47 | 


--------------------------------------------------------------------------------
/src/main/python/checkPythonPaths.ts:
--------------------------------------------------------------------------------
 1 | import { PythonInfo } from '../../common/common-types';
 2 | import { log } from '../../common/log';
 3 | import { getPythonVersion, isSupportedPythonVersion } from './version';
 4 | 
 5 | export const checkPythonPaths = async (pythonsToCheck: string[]): Promise<PythonInfo> => {
 6 |     for (const py of pythonsToCheck) {
 7 |         // eslint-disable-next-line no-await-in-loop
 8 |         const version = await getPythonVersion(py).catch(log.error);
 9 |         if (version && isSupportedPythonVersion(version)) {
10 |             return { python: py, version };
11 |         }
12 |     }
13 | 
14 |     throw new Error(
15 |         `No Python binaries in [${pythonsToCheck.join(', ')}] found or supported version (3.8+)`
16 |     );
17 | };
18 | 


--------------------------------------------------------------------------------
/src/main/python/version.ts:
--------------------------------------------------------------------------------
 1 | import { Version } from '../../common/common-types';
 2 | import { parse, versionGte } from '../../common/version';
 3 | import { promisifiedSpawn } from '../childProc';
 4 | 
 5 | export const getPythonVersion = async (python: string) => {
 6 |     const version = await promisifiedSpawn(python, ['--version']);
 7 |     return parse(version);
 8 | };
 9 | 
10 | export const isSupportedPythonVersion = (version: Version) => versionGte(version, '3.7.0');
11 | 


--------------------------------------------------------------------------------
/src/main/systemInfo.ts:
--------------------------------------------------------------------------------
1 | import { cpu, graphics } from 'systeminformation';
2 | import { lazy } from '../common/util';
3 | 
4 | export const getGpuInfo = lazy(() => graphics());
5 | export const getCpuInfo = lazy(() => cpu());
6 | 


--------------------------------------------------------------------------------
/src/main/util.ts:
--------------------------------------------------------------------------------
1 | import { constants } from 'fs';
2 | import fs from 'fs/promises';
3 | 
4 | export const checkFileExists = (file: string): Promise<boolean> =>
5 |     fs.access(file, constants.F_OK).then(
6 |         () => true,
7 |         () => false
8 |     );
9 | 


--------------------------------------------------------------------------------
/src/public/Info.plist:
--------------------------------------------------------------------------------
 1 | <?xml version="1.0" encoding="UTF-8"?>
 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 | <plist version="1.0">
 4 |     <dict>
 5 |         <key>LSMinimumSystemVersion</key>
 6 |         <string>11.0.0</string>
 7 |         <key>CFBundleDocumentTypes</key>
 8 |         <array>
 9 |             <dict>
10 |                 <key>CFBundleTypeExtensions</key>
11 |                 <array>
12 |                     <string>chn</string>
13 |                     <string>CHN</string>
14 |                 </array>
15 |                 <key>CFBundleTypeIconFile</key>
16 |                 <string>file_chn.icns</string>
17 |                 <key>CFBundleTypeName</key>
18 |                 <string>chaiNNer Chain File</string>
19 |                 <key>CFBundleTypeRole</key>
20 |                 <string>Editor</string>
21 |                 <key>LSHandlerRank</key>
22 |                 <string>Default</string>
23 |             </dict>
24 |         </array>
25 |     </dict>
26 | </plist>
27 | 


--------------------------------------------------------------------------------
/src/public/dmg-background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/dmg-background.png


--------------------------------------------------------------------------------
/src/public/fonts/Noto Emoji/NotoEmoji-VariableFont_wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/fonts/Noto Emoji/NotoEmoji-VariableFont_wght.ttf


--------------------------------------------------------------------------------
/src/public/fonts/Open Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/fonts/Open Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf


--------------------------------------------------------------------------------
/src/public/fonts/Open Sans/OpenSans-VariableFont_wdth,wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/fonts/Open Sans/OpenSans-VariableFont_wdth,wght.ttf


--------------------------------------------------------------------------------
/src/public/fonts/Roboto Mono/RobotoMono-VariableFont_wght.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/fonts/Roboto Mono/RobotoMono-VariableFont_wght.ttf


--------------------------------------------------------------------------------
/src/public/icons/cross_platform/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/cross_platform/icon.icns


--------------------------------------------------------------------------------
/src/public/icons/cross_platform/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/cross_platform/icon.ico


--------------------------------------------------------------------------------
/src/public/icons/cross_platform/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/cross_platform/icon.png


--------------------------------------------------------------------------------
/src/public/icons/mac/file_chn.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/mac/file_chn.icns


--------------------------------------------------------------------------------
/src/public/icons/mac/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/mac/icon.icns


--------------------------------------------------------------------------------
/src/public/icons/png/1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/1024x1024.png


--------------------------------------------------------------------------------
/src/public/icons/png/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/128x128.png


--------------------------------------------------------------------------------
/src/public/icons/png/16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/16x16.png


--------------------------------------------------------------------------------
/src/public/icons/png/24x24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/24x24.png


--------------------------------------------------------------------------------
/src/public/icons/png/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/256x256.png


--------------------------------------------------------------------------------
/src/public/icons/png/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/32x32.png


--------------------------------------------------------------------------------
/src/public/icons/png/48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/48x48.png


--------------------------------------------------------------------------------
/src/public/icons/png/512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/512x512.png


--------------------------------------------------------------------------------
/src/public/icons/png/64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/png/64x64.png


--------------------------------------------------------------------------------
/src/public/icons/win/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/win/icon.ico


--------------------------------------------------------------------------------
/src/public/icons/win/installing_loop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/icons/win/installing_loop.gif


--------------------------------------------------------------------------------
/src/public/splash_imgs/background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/splash_imgs/background.png


--------------------------------------------------------------------------------
/src/public/splash_imgs/front.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chaiNNer-org/chaiNNer/f76a45de229ae9af8dbcced003f3a18ff10a4c81/src/public/splash_imgs/front.png


--------------------------------------------------------------------------------
/src/renderer/components/Header/Header.tsx:
--------------------------------------------------------------------------------
 1 | import { Box, Center, HStack } from '@chakra-ui/react';
 2 | import { memo } from 'react';
 3 | import { DependencyManagerButton } from '../DependencyManagerButton';
 4 | import { NodeDocumentationButton } from '../NodeDocumentation/NodeDocumentationModal';
 5 | import { SettingsButton } from '../SettingsModal';
 6 | import { SystemStats } from '../SystemStats';
 7 | import { AppInfo } from './AppInfo';
 8 | import { ExecutionButtons } from './ExecutionButtons';
 9 | import { KoFiButton } from './KoFiButton';
10 | 
11 | export const Header = memo(() => {
12 |     return (
13 |         <Box
14 |             alignItems="center"
15 |             bg="var(--header-bg)"
16 |             borderRadius="lg"
17 |             borderWidth="0"
18 |             display="flex"
19 |             gap={4}
20 |             h="56px"
21 |             px={2}
22 |             w="full"
23 |         >
24 |             <Box>
25 |                 <AppInfo />
26 |             </Box>
27 |             <Center flexGrow="1">
28 |                 <ExecutionButtons />
29 |             </Center>
30 |             <HStack>
31 |                 <SystemStats />
32 |                 <NodeDocumentationButton />
33 |                 <DependencyManagerButton />
34 |                 <KoFiButton />
35 |                 <SettingsButton />
36 |             </HStack>
37 |         </Box>
38 |     );
39 | });
40 | 


--------------------------------------------------------------------------------
/src/renderer/components/Header/KoFiButton.tsx:
--------------------------------------------------------------------------------
 1 | import { IconButton, Tooltip } from '@chakra-ui/react';
 2 | import { memo } from 'react';
 3 | import { SiKofi } from 'react-icons/si';
 4 | import { links } from '../../../common/links';
 5 | import { log } from '../../../common/log';
 6 | import { ipcRenderer } from '../../safeIpc';
 7 | 
 8 | export const KoFiButton = memo(() => {
 9 |     return (
10 |         <Tooltip
11 |             closeOnClick
12 |             closeOnMouseDown
13 |             borderRadius={8}
14 |             label="Support chaiNNer on Ko-fi"
15 |             px={2}
16 |             py={1}
17 |         >
18 |             <IconButton
19 |                 aria-label="Support chaiNNer"
20 |                 icon={<SiKofi />}
21 |                 size="md"
22 |                 variant="outline"
23 |                 onClick={() => {
24 |                     ipcRenderer.invoke('open-url', links.kofi).catch(() => {
25 |                         log.error('Failed to open Ko-fi url');
26 |                     });
27 |                 }}
28 |             >
29 |                 Support chaiNNer
30 |             </IconButton>
31 |         </Tooltip>
32 |     );
33 | });
34 | 


--------------------------------------------------------------------------------
/src/renderer/components/IfVisible.tsx:
--------------------------------------------------------------------------------
 1 | import { Box } from '@chakra-ui/react';
 2 | import { memo } from 'react';
 3 | import { useInView } from 'react-intersection-observer';
 4 | 
 5 | export interface IfVisibleProps {
 6 |     height: string | number;
 7 |     visibleOffset?: number;
 8 |     forceVisible?: boolean;
 9 |     children: React.ReactNode;
10 | }
11 | export const IfVisible = memo(
12 |     ({ height, visibleOffset = 200, forceVisible = false, children }: IfVisibleProps) => {
13 |         const { ref, entry } = useInView({
14 |             rootMargin: `${visibleOffset}px 0px ${visibleOffset}px 0px`,
15 |         });
16 | 
17 |         const finalVisibility = forceVisible || (entry?.isIntersecting ?? false);
18 | 
19 |         return (
20 |             <Box
21 |                 height={typeof height === 'number' ? `${height}px` : height}
22 |                 ref={ref}
23 |                 style={{ contain: 'layout size' }}
24 |             >
25 |                 {finalVisibility && (
26 |                     <>
27 |                         <Box display="flex" />
28 |                         {children}
29 |                         <Box display="flex" />
30 |                     </>
31 |                 )}
32 |             </Box>
33 |         );
34 |     }
35 | );
36 | 


--------------------------------------------------------------------------------
/src/renderer/components/NodeDocumentation/DropDownOptions.tsx:
--------------------------------------------------------------------------------
 1 | import { memo } from 'react';
 2 | import { InputOption } from '../../../common/common-types';
 3 | import { TypeTag } from '../TypeTag';
 4 | import { SupportHighlighting } from './HighlightContainer';
 5 | 
 6 | interface DropDownOptionProps {
 7 |     option: InputOption;
 8 | }
 9 | const DropDownOption = memo(({ option }: DropDownOptionProps) => {
10 |     return (
11 |         <TypeTag
12 |             fontSize="small"
13 |             height="auto"
14 |             key={option.value}
15 |             mt="-0.2rem"
16 |             verticalAlign="middle"
17 |         >
18 |             <SupportHighlighting>{option.option}</SupportHighlighting>
19 |         </TypeTag>
20 |     );
21 | });
22 | 
23 | interface DropDownOptionsProps {
24 |     options: readonly InputOption[];
25 | }
26 | export const DropDownOptions = memo(({ options }: DropDownOptionsProps) => {
27 |     return (
28 |         <>
29 |             {options.map((o) => (
30 |                 <DropDownOption
31 |                     key={o.value}
32 |                     option={o}
33 |                 />
34 |             ))}
35 |         </>
36 |     );
37 | });
38 | 


--------------------------------------------------------------------------------
/src/renderer/components/NodeDocumentation/SchemaLink.tsx:
--------------------------------------------------------------------------------
 1 | import { Text } from '@chakra-ui/react';
 2 | import { memo } from 'react';
 3 | import { useContext } from 'use-context-selector';
 4 | import { NodeSchema } from '../../../common/common-types';
 5 | import { NodeDocumentationContext } from '../../contexts/NodeDocumentationContext';
 6 | 
 7 | export const SchemaLink = memo(({ schema }: { schema: NodeSchema }) => {
 8 |     const { openNodeDocumentation } = useContext(NodeDocumentationContext);
 9 | 
10 |     return (
11 |         <Text
12 |             _hover={{
13 |                 textDecoration: 'underline',
14 |             }}
15 |             as="i"
16 |             backgroundColor="var(--bg-700)"
17 |             borderRadius={4}
18 |             color="var(--link-color)"
19 |             cursor="pointer"
20 |             fontWeight="bold"
21 |             px={2}
22 |             py={1}
23 |             userSelect="text"
24 |             whiteSpace="nowrap"
25 |             onClick={() => openNodeDocumentation(schema.schemaId)}
26 |         >
27 |             {schema.name}
28 |         </Text>
29 |     );
30 | });
31 | 


--------------------------------------------------------------------------------
/src/renderer/components/NodeSelectorPanel/SubcategoryHeading.tsx:
--------------------------------------------------------------------------------
 1 | import { Divider, HStack, Text } from '@chakra-ui/react';
 2 | import { memo } from 'react';
 3 | import { NodeGroup } from '../../../common/common-types';
 4 | 
 5 | interface SubcategoryHeadingProps {
 6 |     group: NodeGroup;
 7 |     collapsed?: boolean;
 8 | }
 9 | 
10 | export const SubcategoryHeading = memo(({ group, collapsed = false }: SubcategoryHeadingProps) => {
11 |     return (
12 |         <HStack
13 |             h={6}
14 |             w="full"
15 |         >
16 |             {collapsed ? (
17 |                 <Divider orientation="horizontal" />
18 |             ) : (
19 |                 <>
20 |                     <Divider orientation="horizontal" />
21 |                     <Text
22 |                         casing="uppercase"
23 |                         color="#71809699"
24 |                         fontSize="sm"
25 |                         py={0.5}
26 |                         whiteSpace="nowrap"
27 |                     >
28 |                         {group.name}
29 |                     </Text>
30 |                     <Divider orientation="horizontal" />
31 |                 </>
32 |             )}
33 |         </HStack>
34 |     );
35 | });
36 | 


--------------------------------------------------------------------------------
/src/renderer/components/SearchBar.tsx:
--------------------------------------------------------------------------------
 1 | import { CloseIcon, SearchIcon } from '@chakra-ui/icons';
 2 | import { Input, InputGroup, InputLeftElement, InputRightElement } from '@chakra-ui/react';
 3 | import { ChangeEventHandler, memo } from 'react';
 4 | 
 5 | interface SearchBarProps {
 6 |     value: string;
 7 |     onChange: ChangeEventHandler<HTMLInputElement>;
 8 |     onClose: () => void;
 9 |     onClick: () => void;
10 | }
11 | 
12 | export const SearchBar = memo(({ value, onChange, onClose, onClick }: SearchBarProps) => (
13 |     <InputGroup borderRadius={0}>
14 |         <InputLeftElement
15 |             color="var(--fg-300)"
16 |             pointerEvents="none"
17 |         >
18 |             <SearchIcon />
19 |         </InputLeftElement>
20 |         <Input
21 |             borderRadius={0}
22 |             placeholder="Search..."
23 |             spellCheck={false}
24 |             type="text"
25 |             value={value}
26 |             variant="filled"
27 |             onChange={onChange}
28 |             onClick={onClick}
29 |         />
30 |         <InputRightElement
31 |             _hover={{ color: 'var(--fg-000)' }}
32 |             style={{
33 |                 color: 'var(--fg-300)',
34 |                 cursor: 'pointer',
35 |                 display: value ? undefined : 'none',
36 |                 fontSize: '66%',
37 |             }}
38 |             onClick={onClose}
39 |         >
40 |             <CloseIcon />
41 |         </InputRightElement>
42 |     </InputGroup>
43 | ));
44 | 


--------------------------------------------------------------------------------
/src/renderer/components/groups/RequiredGroup.tsx:
--------------------------------------------------------------------------------
 1 | import { memo, useMemo } from 'react';
 2 | import { GenericInput } from '../../../common/common-types';
 3 | import { getUniqueKey } from '../../../common/group-inputs';
 4 | import { getRequireCondition } from '../../../common/nodes/groupStacks';
 5 | import { GroupProps } from './props';
 6 | 
 7 | export const RequiredGroup = memo(
 8 |     ({ inputs, nodeState, group, ItemRenderer }: GroupProps<'required'>) => {
 9 |         const { schema, testCondition } = nodeState;
10 | 
11 |         const isRequired = useMemo(() => {
12 |             const condition = getRequireCondition(schema, group);
13 |             return testCondition(condition);
14 |         }, [schema, group, testCondition]);
15 | 
16 |         const requiredInputs: GenericInput[] = useMemo(() => {
17 |             return inputs.map((i) => ({ ...i, optional: false }));
18 |         }, [inputs]);
19 | 
20 |         return (
21 |             <>
22 |                 {(isRequired ? requiredInputs : inputs).map((item) => (
23 |                     <ItemRenderer
24 |                         item={item}
25 |                         key={getUniqueKey(item)}
26 |                         nodeState={nodeState}
27 |                     />
28 |                 ))}
29 |             </>
30 |         );
31 |     }
32 | );
33 | 


--------------------------------------------------------------------------------
/src/renderer/components/groups/props.ts:
--------------------------------------------------------------------------------
 1 | import { Group, GroupKind, OfKind } from '../../../common/common-types';
 2 | import { GroupInputs, InputItem } from '../../../common/group-inputs';
 3 | import { NodeState } from '../../helpers/nodeState';
 4 | 
 5 | export type InputItemRenderer = (props: {
 6 |     item: InputItem;
 7 |     nodeState: NodeState;
 8 | }) => JSX.Element | null;
 9 | 
10 | export interface GroupProps<Kind extends GroupKind> {
11 |     group: OfKind<Group, Kind>;
12 |     inputs: GroupInputs[Kind];
13 |     nodeState: NodeState;
14 |     ItemRenderer: InputItemRenderer;
15 | }
16 | 


--------------------------------------------------------------------------------
/src/renderer/components/groups/util.ts:
--------------------------------------------------------------------------------
1 | import { Input } from '../../../common/common-types';
2 | import { InputItem } from '../../../common/group-inputs';
3 | 
4 | export const someInput = (item: InputItem, condFn: (input: Input) => boolean): boolean => {
5 |     if (item.kind !== 'group') return condFn(item);
6 |     return item.inputs.some((i) => someInput(i, condFn));
7 | };
8 | 


--------------------------------------------------------------------------------
/src/renderer/components/inputs/elements/AdvancedNumberInput.scss:
--------------------------------------------------------------------------------
1 | .small-stepper svg {
2 |     height: 0.75em;
3 | }
4 | 


--------------------------------------------------------------------------------
/src/renderer/components/inputs/elements/Checkbox.scss:
--------------------------------------------------------------------------------
 1 | @use '../../../colors.scss';
 2 | 
 3 | .chainner-node-checkbox > .chakra-checkbox__control {
 4 |     transition-property: border-color;
 5 |     transition-duration: var(--chakra-transition-duration-normal);
 6 | 
 7 |     /* stylelint-disable-next-line no-duplicate-selectors */
 8 |     & {
 9 |         border-color: inherit;
10 |         background: var(--bg-700);
11 | 
12 |         &:hover {
13 |             /* stylelint-disable-next-line custom-property-pattern */
14 |             border-color: var(--chakra-colors-whiteAlpha-400);
15 |             background: var(--bg-700);
16 |         }
17 |     }
18 | 
19 |     &[data-checked] {
20 |         border-color: rgba(255 255 255/50%);
21 |         background: var(--bg-700);
22 | 
23 |         &:hover {
24 |             border-color: rgba(255 255 255/50%);
25 |             background: var(--bg-700);
26 |         }
27 |     }
28 | 
29 |     & svg {
30 |         color: var(--chakra-colors-chakra-body-text);
31 |         width: 1em;
32 |         cursor: pointer;
33 |         overflow: visible;
34 |         stroke-width: 2.75 !important;
35 | 
36 |         polyline {
37 |             cursor: pointer;
38 |         }
39 |     }
40 | }
41 | 


--------------------------------------------------------------------------------
/src/renderer/components/inputs/elements/Checkbox.tsx:
--------------------------------------------------------------------------------
 1 | import { Box, Checkbox as ChakraCheckbox } from '@chakra-ui/react';
 2 | import { ReactNode, memo } from 'react';
 3 | import { DropDownInput, InputSchemaValue } from '../../../../common/common-types';
 4 | import './Checkbox.scss';
 5 | 
 6 | type ArrayItem<T> = T extends readonly (infer I)[] ? I : never;
 7 | type Option = ArrayItem<DropDownInput['options']>;
 8 | 
 9 | export interface CheckboxProps {
10 |     value: InputSchemaValue;
11 |     onChange: (value: InputSchemaValue) => void;
12 |     isDisabled?: boolean;
13 |     yes: Option;
14 |     no: Option;
15 |     label: string;
16 |     afterText?: ReactNode;
17 | }
18 | 
19 | export const Checkbox = memo(
20 |     ({ value, onChange, isDisabled, yes, no, label, afterText }: CheckboxProps) => {
21 |         return (
22 |             <ChakraCheckbox
23 |                 className="chainner-node-checkbox"
24 |                 colorScheme="gray"
25 |                 isChecked={value === yes.value}
26 |                 isDisabled={isDisabled}
27 |                 onChange={(e) => {
28 |                     const selected = e.target.checked ? yes : no;
29 |                     onChange(selected.value);
30 |                 }}
31 |             >
32 |                 <Box
33 |                     as="span"
34 |                     fontSize="14px"
35 |                     verticalAlign="text-top"
36 |                 >
37 |                     {label}
38 |                 </Box>
39 |                 {afterText}
40 |             </ChakraCheckbox>
41 |         );
42 |     }
43 | );
44 | 


--------------------------------------------------------------------------------
/src/renderer/components/inputs/elements/ColorKindSelector.tsx:
--------------------------------------------------------------------------------
 1 | import { Button, ButtonGroup } from '@chakra-ui/react';
 2 | import { memo, useMemo } from 'react';
 3 | import { ColorKind } from '../../../../common/common-types';
 4 | 
 5 | const KIND_ORDER: readonly ColorKind[] = ['grayscale', 'rgb', 'rgba'];
 6 | const KIND_LABEL: Readonly<Record<ColorKind, string>> = {
 7 |     grayscale: 'Gray',
 8 |     rgb: 'RGB',
 9 |     rgba: 'RGBA',
10 | };
11 | 
12 | interface ColorKindSelectorProps {
13 |     kinds: ReadonlySet<ColorKind>;
14 |     current: ColorKind;
15 |     onSelect: (kind: ColorKind) => void;
16 | }
17 | export const ColorKindSelector = memo(({ kinds, current, onSelect }: ColorKindSelectorProps) => {
18 |     const kindArray = useMemo(() => {
19 |         return [...kinds].sort((a, b) => KIND_ORDER.indexOf(a) - KIND_ORDER.indexOf(b));
20 |     }, [kinds]);
21 | 
22 |     return (
23 |         <ButtonGroup
24 |             isAttached
25 |             size="sm"
26 |             w="full"
27 |         >
28 |             {kindArray.map((k) => {
29 |                 return (
30 |                     <Button
31 |                         borderRadius="lg"
32 |                         key={k}
33 |                         variant={current === k ? 'solid' : 'ghost'}
34 |                         w="full"
35 |                         onClick={() => onSelect(k)}
36 |                     >
37 |                         {KIND_LABEL[k]}
38 |                     </Button>
39 |                 );
40 |             })}
41 |         </ButtonGroup>
42 |     );
43 | });
44 | 


--------------------------------------------------------------------------------
/src/renderer/components/inputs/props.ts:
--------------------------------------------------------------------------------
 1 | import { Type } from '@chainner/navi';
 2 | import {
 3 |     Condition,
 4 |     Input,
 5 |     InputKind,
 6 |     OfKind,
 7 |     PartialBy,
 8 |     SchemaId,
 9 |     Size,
10 | } from '../../../common/common-types';
11 | 
12 | export interface InputProps<Kind extends InputKind, Value extends string | number = never> {
13 |     readonly value: Value | undefined;
14 |     readonly setValue: (input: Value) => void;
15 |     readonly resetValue: () => void;
16 |     readonly input: Omit<PartialBy<OfKind<Input, Kind>, 'id'>, 'type' | 'conversion'>;
17 |     readonly definitionType: Type;
18 |     readonly isLocked: boolean;
19 |     readonly isConnected: boolean;
20 |     readonly inputKey: string;
21 |     readonly size: Readonly<Size> | undefined;
22 |     readonly setSize: (size: Readonly<Size>) => void;
23 |     readonly inputType: Type;
24 |     readonly nodeId?: string;
25 |     readonly nodeSchemaId?: SchemaId;
26 |     readonly testCondition: (condition: Condition) => boolean;
27 |     readonly sequenceType?: Type;
28 | }
29 | 


--------------------------------------------------------------------------------
/src/renderer/components/outputs/TaggedOutput.tsx:
--------------------------------------------------------------------------------
 1 | import { memo } from 'react';
 2 | import { ModelDataTags } from './elements/ModelDataTags';
 3 | import { OutputProps } from './props';
 4 | 
 5 | interface TagData {
 6 |     tags?: readonly string[] | null;
 7 | }
 8 | 
 9 | export const TaggedOutput = memo(({ output, useOutputData, animated }: OutputProps) => {
10 |     const { current } = useOutputData<TagData>(output.id);
11 | 
12 |     return (
13 |         <ModelDataTags
14 |             loading={animated}
15 |             tags={current?.tags || undefined}
16 |         />
17 |     );
18 | });
19 | 


--------------------------------------------------------------------------------
/src/renderer/components/outputs/props.ts:
--------------------------------------------------------------------------------
 1 | import { Type } from '@chainner/navi';
 2 | import { NodeSchema, Output, OutputId, Size } from '../../../common/common-types';
 3 | 
 4 | export interface UseOutputData<T> {
 5 |     /** The current output data. Current here means most recent + up to date (= same input hash). */
 6 |     readonly current: T | undefined;
 7 |     /** The most recent output data. */
 8 |     readonly last: T | undefined;
 9 |     /** Whether the most recent output data ({@link last}) is not the current output data ({@link current}). */
10 |     readonly stale: boolean;
11 | }
12 | 
13 | export interface OutputProps {
14 |     readonly output: Output;
15 |     readonly id: string;
16 |     readonly schema: NodeSchema;
17 |     readonly definitionType: Type;
18 |     readonly type: Type;
19 |     readonly useOutputData: <T>(outputId: OutputId) => UseOutputData<T>;
20 |     readonly animated: boolean;
21 |     readonly size: Readonly<Size> | undefined;
22 |     readonly setSize: (size: Readonly<Size>) => void;
23 |     readonly sequenceType?: Type;
24 | }
25 | 


--------------------------------------------------------------------------------
/src/renderer/components/settings/SettingContainer.tsx:
--------------------------------------------------------------------------------
 1 | import { Flex, HStack, Text, VStack } from '@chakra-ui/react';
 2 | import { PropsWithChildren, memo } from 'react';
 3 | 
 4 | interface ContainerProps {
 5 |     title: string;
 6 |     description: string;
 7 | }
 8 | 
 9 | export const SettingContainer = memo(
10 |     ({ title, description, children }: PropsWithChildren<ContainerProps>) => {
11 |         return (
12 |             <Flex
13 |                 align="center"
14 |                 w="full"
15 |             >
16 |                 <VStack
17 |                     alignContent="left"
18 |                     alignItems="left"
19 |                     w="full"
20 |                 >
21 |                     <Text
22 |                         flex="1"
23 |                         textAlign="left"
24 |                     >
25 |                         {title}
26 |                     </Text>
27 |                     <Text
28 |                         flex="1"
29 |                         fontSize="xs"
30 |                         marginTop={0}
31 |                         textAlign="left"
32 |                     >
33 |                         {description}
34 |                     </Text>
35 |                 </VStack>
36 |                 <HStack>{children}</HStack>
37 |             </Flex>
38 |         );
39 |     }
40 | );
41 | 


--------------------------------------------------------------------------------
/src/renderer/components/settings/SettingItem.tsx:
--------------------------------------------------------------------------------
 1 | import { memo, useEffect } from 'react';
 2 | import { Setting, SettingValue } from '../../../common/common-types';
 3 | import { SettingComponents } from './components';
 4 | import { SettingsProps } from './props';
 5 | 
 6 | interface SettingItemProps {
 7 |     setting: Setting;
 8 |     value: SettingValue | undefined;
 9 |     setValue: (value: SettingValue) => void;
10 | }
11 | 
12 | export const SettingItem = memo(({ setting, value, setValue }: SettingItemProps) => {
13 |     const settingIsUnset = value === undefined;
14 |     useEffect(() => {
15 |         if (settingIsUnset) {
16 |             setValue(setting.default);
17 |         }
18 |     }, [setting, settingIsUnset, setValue]);
19 | 
20 |     if (value === undefined) {
21 |         return null;
22 |     }
23 | 
24 |     const Component = SettingComponents[setting.type] as (
25 |         props: SettingsProps<Setting['type']>
26 |     ) => JSX.Element;
27 | 
28 |     return (
29 |         <Component
30 |             setValue={setValue}
31 |             setting={setting}
32 |             value={value}
33 |         />
34 |     );
35 | });
36 | 


--------------------------------------------------------------------------------
/src/renderer/components/settings/props.ts:
--------------------------------------------------------------------------------
 1 | import { Setting } from '../../../common/common-types';
 2 | 
 3 | type OfType<S, Type extends string> = S extends { type: Type } ? S : never;
 4 | 
 5 | export interface SettingsProps<T extends Setting['type']> {
 6 |     setting: Omit<OfType<Setting, T>, 'default' | 'type' | 'key'>;
 7 |     value: OfType<Setting, T>['default'];
 8 |     setValue: (value: OfType<Setting, T>['default']) => void;
 9 | }
10 | 


--------------------------------------------------------------------------------
/src/renderer/contexts/CollapsedNodeContext.tsx:
--------------------------------------------------------------------------------
 1 | import { Box } from '@chakra-ui/react';
 2 | import React, { memo } from 'react';
 3 | import { createContext } from 'use-context-selector';
 4 | 
 5 | export const IsCollapsedContext = createContext<boolean>(false);
 6 | 
 7 | export const CollapsedNode = memo(({ children }: React.PropsWithChildren<unknown>) => {
 8 |     return (
 9 |         <Box
10 |             display="none"
11 |             style={{ contain: 'strict' }}
12 |         >
13 |             <IsCollapsedContext.Provider value>{children}</IsCollapsedContext.Provider>
14 |         </Box>
15 |     );
16 | });
17 | 


--------------------------------------------------------------------------------
/src/renderer/contexts/FakeExampleContext.tsx:
--------------------------------------------------------------------------------
 1 | import React, { memo } from 'react';
 2 | import { createContext } from 'use-context-selector';
 3 | import { useMemoObject } from '../hooks/useMemo';
 4 | 
 5 | interface FakeNodeContextState {
 6 |     isFake: boolean;
 7 | }
 8 | 
 9 | export const FakeNodeContext = createContext<Readonly<FakeNodeContextState>>({
10 |     isFake: false,
11 | });
12 | 
13 | export const FakeNodeProvider = memo(
14 |     ({ children, isFake }: React.PropsWithChildren<FakeNodeContextState>) => {
15 |         const value = useMemoObject<FakeNodeContextState>({ isFake });
16 | 
17 |         return <FakeNodeContext.Provider value={value}>{children}</FakeNodeContext.Provider>;
18 |     }
19 | );
20 | 


--------------------------------------------------------------------------------
/src/renderer/contexts/HotKeyContext.tsx:
--------------------------------------------------------------------------------
 1 | import React, { memo, useCallback, useState } from 'react';
 2 | import { createContext } from 'use-context-selector';
 3 | import { noop } from '../../common/util';
 4 | import { useMemoObject } from '../hooks/useMemo';
 5 | import { ipcRenderer } from '../safeIpc';
 6 | 
 7 | interface HotkeysContextState {
 8 |     hotkeysEnabled: boolean;
 9 |     setHotkeysEnabled: (value: boolean) => void;
10 | }
11 | 
12 | export const HotkeysContext = createContext<Readonly<HotkeysContextState>>({
13 |     hotkeysEnabled: true,
14 |     setHotkeysEnabled: noop,
15 | });
16 | 
17 | export const HotkeysProvider = memo(({ children }: React.PropsWithChildren<unknown>) => {
18 |     const [enabled, setEnabled] = useState(true);
19 | 
20 |     const setHotkeysEnabled = useCallback(
21 |         (value: boolean) => {
22 |             setEnabled(value);
23 |             ipcRenderer.send(value ? 'enable-menu' : 'disable-menu');
24 |         },
25 |         [setEnabled]
26 |     );
27 | 
28 |     const value = useMemoObject<HotkeysContextState>({
29 |         hotkeysEnabled: enabled,
30 |         setHotkeysEnabled,
31 |     });
32 | 
33 |     return <HotkeysContext.Provider value={value}>{children}</HotkeysContext.Provider>;
34 | });
35 | 


--------------------------------------------------------------------------------
/src/renderer/contexts/InputContext.tsx:
--------------------------------------------------------------------------------
 1 | import React, { memo } from 'react';
 2 | import { createContext } from 'use-context-selector';
 3 | import { useMemoObject } from '../hooks/useMemo';
 4 | 
 5 | interface InputContextState {
 6 |     /**
 7 |      * Whether the input is inactive (unused) due to some other input value.
 8 |      *
 9 |      * Such inactive inputs are usually not rendered, but they have to be if
10 |      * they have a connection.
11 |      */
12 |     conditionallyInactive: boolean;
13 | }
14 | 
15 | export const InputContext = createContext<Readonly<InputContextState>>({
16 |     conditionallyInactive: false,
17 | });
18 | 
19 | export const WithInputContext = memo(
20 |     ({ conditionallyInactive, children }: React.PropsWithChildren<InputContextState>) => {
21 |         const value = useMemoObject<InputContextState>({
22 |             conditionallyInactive,
23 |         });
24 | 
25 |         return <InputContext.Provider value={value}>{children}</InputContext.Provider>;
26 |     }
27 | );
28 | 


--------------------------------------------------------------------------------
/src/renderer/env.ts:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | 
3 | export const getCacheLocation = (userDataPath: string, cacheKey: string) => {
4 |     return path.join(userDataPath, '/cache/', cacheKey);
5 | };
6 | 


--------------------------------------------------------------------------------
/src/renderer/helpers/colorTools.ts:
--------------------------------------------------------------------------------
 1 | import { Color } from './color';
 2 | 
 3 | /**
 4 |  * Lightens (percentage > 0) or darkens (percentage < 0) the given hex color.
 5 |  */
 6 | export const shadeColor = (color: string, percent: number) => {
 7 |     return Color.fromHex(color)
 8 |         .channelWise((c) => Math.min(Math.round(c * (1 + percent / 100)), 255))
 9 |         .hex();
10 | };
11 | 
12 | export const interpolateColor = (color1: string, color2: string, factor = 0.5) =>
13 |     Color.fromHex(color1).lerpLinear(Color.fromHex(color2), factor).hex();
14 | 
15 | export const createConicGradient = (colors: readonly string[]): string => {
16 |     if (colors.length === 1) return colors[0];
17 | 
18 |     const handleColorString = colors
19 |         .map((color, index) => {
20 |             const percent = index / colors.length;
21 |             const nextPercent = (index + 1) / colors.length;
22 |             return `${color} ${percent * 100}% ${nextPercent * 100}%`;
23 |         })
24 |         .join(', ');
25 |     return `conic-gradient(from 90deg, ${handleColorString})`;
26 | };
27 | 


--------------------------------------------------------------------------------
/src/renderer/helpers/naviHelpers.ts:
--------------------------------------------------------------------------------
1 | import { FunctionCallExpression, Type, evaluate } from '@chainner/navi';
2 | import { getChainnerScope } from '../../common/types/chainner-scope';
3 | 
4 | export const typeToString = (type: Type): Type => {
5 |     return evaluate(new FunctionCallExpression('toString', [type]), getChainnerScope());
6 | };
7 | 


--------------------------------------------------------------------------------
/src/renderer/helpers/types.ts:
--------------------------------------------------------------------------------
1 | export type SetState<T> = React.Dispatch<React.SetStateAction<T>>;
2 | export type GetSetState<T> = readonly [T, SetState<T>];
3 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useAutomaticFeatures.ts:
--------------------------------------------------------------------------------
 1 | import { getIncomers, useReactFlow } from 'reactflow';
 2 | import { useContext } from 'use-context-selector';
 3 | import { EdgeData, NodeData, SchemaId } from '../../common/common-types';
 4 | import { BackendContext } from '../contexts/BackendContext';
 5 | 
 6 | /**
 7 |  * Determines whether a node should use automatic ahead-of-time features, such as individually running the node or determining certain type features automatically.
 8 |  */
 9 | export const useAutomaticFeatures = (id: string, schemaId: SchemaId) => {
10 |     const { schemata } = useContext(BackendContext);
11 |     const schema = schemata.get(schemaId);
12 | 
13 |     const { getEdges, getNodes, getNode } = useReactFlow<NodeData, EdgeData>();
14 |     const thisNode = getNode(id);
15 | 
16 |     // A node should not use automatic features if it has incoming connections
17 |     const hasIncomingConnections =
18 |         thisNode && getIncomers(thisNode, getNodes(), getEdges()).length > 0;
19 | 
20 |     // Same if it has any static input values
21 |     const hasStaticValueInput = schema.inputs.some((i) => i.kind === 'static');
22 |     // We should only use automatic features if the node has side effects
23 |     const { hasSideEffects } = schema;
24 | 
25 |     return {
26 |         isAutomatic: hasSideEffects && !hasIncomingConnections && !hasStaticValueInput,
27 |         hasIncomingConnections,
28 |     };
29 | };
30 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useChangeCounter.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useRef, useState } from 'react';
 2 | import { SetState } from '../helpers/types';
 3 | 
 4 | export type ChangeCounter = number & { __changeCounter: true };
 5 | 
 6 | export const nextChangeCount = (count: number): number => (count + 1) % 1_000_000;
 7 | 
 8 | export const useChangeCounter = () => {
 9 |     const [counter, setCounter] = useState(0);
10 |     const counterRef = useRef(0);
11 | 
12 |     const change = useCallback(() => {
13 |         // we have to wrap at some point, so I just arbitrarily chose 1 million
14 |         setCounter((prev) => {
15 |             const newValue = nextChangeCount(prev);
16 |             counterRef.current = newValue;
17 |             return newValue;
18 |         });
19 |     }, [setCounter]);
20 | 
21 |     return [counter as ChangeCounter, change, counterRef] as const;
22 | };
23 | 
24 | export const wrapChanges = <T>(setter: SetState<T>, addChange: () => void): SetState<T> => {
25 |     return (value) => {
26 |         setter(value);
27 |         addChange();
28 |     };
29 | };
30 | export const wrapRefChanges = <T>(
31 |     setter: Readonly<React.MutableRefObject<SetState<T>>>,
32 |     addChange: () => void
33 | ): SetState<T> => {
34 |     return (value) => {
35 |         setter.current(value);
36 |         addChange();
37 |     };
38 | };
39 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useDevicePixelRatio.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect, useState } from 'react';
 2 | 
 3 | export const useDevicePixelRatio = (): number => {
 4 |     const [value, setValue] = useState(window.devicePixelRatio);
 5 | 
 6 |     useEffect(() => {
 7 |         const update = () => setValue(window.devicePixelRatio);
 8 |         const mediaMatcher = window.matchMedia(`screen and (resolution: ${value}dppx)`);
 9 |         mediaMatcher.addEventListener('change', update);
10 | 
11 |         return () => {
12 |             mediaMatcher.removeEventListener('change', update);
13 |         };
14 |     }, [value]);
15 | 
16 |     return value;
17 | };
18 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useEventBacklog.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useEffect, useMemo, useRef } from 'react';
 2 | 
 3 | export interface EventBacklog<T> {
 4 |     readonly push: (event: T) => void;
 5 |     readonly processAll: () => void;
 6 | }
 7 | 
 8 | export interface BacklogOption<T> {
 9 |     process: (event: T[]) => void;
10 |     interval: number;
11 | }
12 | 
13 | export const useEventBacklog = <T>({ process, interval }: BacklogOption<T>): EventBacklog<T> => {
14 |     const backlogRef = useRef<T[]>([]);
15 |     const push = useCallback((event: T): void => {
16 |         backlogRef.current.push(event);
17 |     }, []);
18 | 
19 |     const processRef = useRef(process);
20 |     useEffect(() => {
21 |         processRef.current = process;
22 |     }, [process]);
23 | 
24 |     const processAll = useCallback(() => {
25 |         if (backlogRef.current.length > 0) {
26 |             const backlog = backlogRef.current;
27 |             backlogRef.current = [];
28 |             processRef.current(backlog);
29 |         }
30 |     }, []);
31 | 
32 |     useEffect(() => {
33 |         const timeout = setInterval(processAll, interval);
34 |         return () => clearInterval(timeout);
35 |     }, [processAll, interval]);
36 | 
37 |     return useMemo(() => ({ push, processAll }), [push, processAll]);
38 | };
39 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useHotkeys.ts:
--------------------------------------------------------------------------------
 1 | import { useHotkeys as useHotkeysImpl } from 'react-hotkeys-hook';
 2 | import { useContext } from 'use-context-selector';
 3 | import { noop } from '../../common/util';
 4 | import { HotkeysContext } from '../contexts/HotKeyContext';
 5 | 
 6 | export const useHotkeys = (keys: string, callback: () => void): void => {
 7 |     const { hotkeysEnabled } = useContext(HotkeysContext);
 8 | 
 9 |     const fn = hotkeysEnabled ? callback : noop;
10 | 
11 |     useHotkeysImpl(keys, fn, [fn]);
12 | };
13 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useInterval.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect } from 'react';
 2 | 
 3 | /**
 4 |  * Executes the given effect indefinitely every `delay` ms.
 5 |  *
 6 |  * The interval gets reset every time the callback changes.
 7 |  */
 8 | export const useInterval = (callback: () => void, delay: number) => {
 9 |     useEffect(() => {
10 |         const id = setInterval(callback, delay);
11 |         return () => clearInterval(id);
12 |     }, [delay, callback]);
13 | };
14 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useIpcRendererListener.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect } from 'react';
 2 | import { ChannelArgs, SendChannels } from '../../common/safeIpc';
 3 | import { ipcRenderer } from '../safeIpc';
 4 | // eslint-disable-next-line import/no-nodejs-modules
 5 | import type { IpcRendererEvent } from 'electron/renderer';
 6 | 
 7 | export const useIpcRendererListener = <C extends keyof SendChannels>(
 8 |     channel: C,
 9 |     listener: (event: IpcRendererEvent, ...args: ChannelArgs<C>) => void
10 | ) => {
11 |     useEffect(() => {
12 |         ipcRenderer.on(channel, listener);
13 |         return () => {
14 |             ipcRenderer.removeListener(channel, listener);
15 |         };
16 |     }, [channel, listener]);
17 | };
18 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useIsCollapsedNode.ts:
--------------------------------------------------------------------------------
1 | import { useContext } from 'use-context-selector';
2 | import { IsCollapsedContext } from '../contexts/CollapsedNodeContext';
3 | 
4 | export const useIsCollapsedNode = (): boolean => {
5 |     return useContext(IsCollapsedContext);
6 | };
7 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useLastDirectory.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback } from 'react';
 2 | import { EMPTY_OBJECT } from '../../common/util';
 3 | import { useStored } from './useStored';
 4 | 
 5 | export interface UseLastDirectory {
 6 |     readonly lastDirectory: string | undefined;
 7 |     readonly setLastDirectory: (dir: string) => void;
 8 | }
 9 | 
10 | export const useLastDirectory = (key: string): UseLastDirectory => {
11 |     const [lastDirectories, setLastDirectories] = useStored<Record<string, string | undefined>>(
12 |         'lastDirectories',
13 |         EMPTY_OBJECT
14 |     );
15 | 
16 |     const setLastDirectory = useCallback(
17 |         (dir: string) => {
18 |             setLastDirectories((prev) => ({ ...prev, [key]: dir }));
19 |         },
20 |         [setLastDirectories, key]
21 |     );
22 | 
23 |     return {
24 |         lastDirectory: lastDirectories[key],
25 |         setLastDirectory,
26 |     };
27 | };
28 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useLastWindowSize.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useEffect } from 'react';
 2 | import { debounce } from '../../common/util';
 3 | import { useIpcRendererListener } from './useIpcRendererListener';
 4 | import { useMutSetting } from './useSettings';
 5 | 
 6 | export const useLastWindowSize = () => {
 7 |     const [, setSize] = useMutSetting('lastWindowSize');
 8 | 
 9 |     useEffect(() => {
10 |         const listener = debounce(() => {
11 |             setSize((prev) => {
12 |                 if (prev.maximized) return prev;
13 |                 return {
14 |                     maximized: false,
15 |                     width: window.outerWidth,
16 |                     height: window.outerHeight,
17 |                 };
18 |             });
19 |         }, 100);
20 | 
21 |         window.addEventListener('resize', listener);
22 |         return () => window.removeEventListener('resize', listener);
23 |     }, [setSize]);
24 | 
25 |     useIpcRendererListener(
26 |         'window-maximized-change',
27 |         useCallback(
28 |             (_, maximized) => {
29 |                 setSize((prev) => ({ ...prev, maximized }));
30 |             },
31 |             [setSize]
32 |         )
33 |     );
34 | };
35 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useMemo.ts:
--------------------------------------------------------------------------------
 1 | import { useMemo } from 'react';
 2 | 
 3 | /**
 4 |  * A `useMemo` variant that compares the array items for reference equality.
 5 |  */
 6 | export const useMemoArray = <T extends readonly unknown[]>(array: T): T =>
 7 |     // eslint-disable-next-line react-hooks/exhaustive-deps
 8 |     useMemo(() => array, array);
 9 | 
10 | /**
11 |  * A `useMemo` variant that compares the object values (using `Object.values`) for reference
12 |  * equality.
13 |  */
14 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
15 | export const useMemoObject = <T extends Readonly<Record<string | number, any>>>(
16 |     obj: T
17 |     // eslint-disable-next-line react-hooks/exhaustive-deps
18 | ): Readonly<T> => useMemo(() => obj, Object.values(obj));
19 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useNodeMenu.scss:
--------------------------------------------------------------------------------
 1 | .useNodeMenu-child {
 2 |     display: none;
 3 | }
 4 | 
 5 | .useNodeMenu-container:hover + .useNodeMenu-child {
 6 |     display: block;
 7 | }
 8 | 
 9 | .useNodeMenu-child:hover {
10 |     display: block;
11 | }
12 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/usePrevious.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect, useRef } from 'react';
 2 | 
 3 | export const usePrevious = <T>(value: T): T | undefined => {
 4 |     const ref = useRef<T>();
 5 |     useEffect(() => {
 6 |         ref.current = value;
 7 |     });
 8 |     return ref.current;
 9 | };
10 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useSessionStorage.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect, useState } from 'react';
 2 | 
 3 | export const getSessionStorageOrDefault = <T>(key: string, defaultValue: T): T => {
 4 |     const stored = sessionStorage.getItem(key);
 5 |     if (!stored) {
 6 |         return defaultValue;
 7 |     }
 8 |     return JSON.parse(stored) as T;
 9 | };
10 | 
11 | export const useSessionStorage = <T>(key: string, defaultValue: T) => {
12 |     const [value, setValue] = useState(() => getSessionStorageOrDefault(key, defaultValue));
13 | 
14 |     useEffect(() => {
15 |         sessionStorage.setItem(key, JSON.stringify(value));
16 |     }, [key, value]);
17 | 
18 |     return [value, setValue] as const;
19 | };
20 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useSettings.ts:
--------------------------------------------------------------------------------
 1 | import { SetStateAction, useCallback } from 'react';
 2 | import { useContext } from 'use-context-selector';
 3 | import { ChainnerSettings } from '../../common/settings/settings';
 4 | import { SettingsContext } from '../contexts/SettingsContext';
 5 | 
 6 | export const useSettings = () => {
 7 |     return useContext(SettingsContext).settings;
 8 | };
 9 | 
10 | export const useMutSetting = <K extends keyof ChainnerSettings>(key: K) => {
11 |     const { settings, setSetting } = useContext(SettingsContext);
12 | 
13 |     const set = useCallback(
14 |         (update: SetStateAction<ChainnerSettings[K]>) => {
15 |             setSetting(key, update);
16 |         },
17 |         [key, setSetting]
18 |     );
19 | 
20 |     return [settings[key], set] as const;
21 | };
22 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useStored.ts:
--------------------------------------------------------------------------------
 1 | import { Dispatch, SetStateAction, useCallback } from 'react';
 2 | import { useContext } from 'use-context-selector';
 3 | import { SettingsContext } from '../contexts/SettingsContext';
 4 | 
 5 | const getStored = <T>(storage: Record<string, unknown>, key: string, defaultValue: T): T => {
 6 |     return (storage[key] as T | undefined) ?? defaultValue;
 7 | };
 8 | 
 9 | export const useStored = <T>(
10 |     key: string,
11 |     defaultValue: T
12 | ): readonly [T, Dispatch<SetStateAction<T>>] => {
13 |     const { settings, setSetting } = useContext(SettingsContext);
14 | 
15 |     const setValue = useCallback(
16 |         (value: SetStateAction<T>) => {
17 |             setSetting('storage', (prev) => {
18 |                 const newValue =
19 |                     typeof value === 'function'
20 |                         ? (value as (prev: T) => T)(getStored(prev, key, defaultValue))
21 |                         : value;
22 |                 return { ...prev, [key]: newValue };
23 |             });
24 |         },
25 |         [setSetting, key, defaultValue]
26 |     );
27 | 
28 |     return [getStored(settings.storage, key, defaultValue), setValue] as const;
29 | };
30 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useThemeColor.ts:
--------------------------------------------------------------------------------
 1 | import { useColorMode } from '@chakra-ui/react';
 2 | import { useMemo } from 'react';
 3 | import { lazy } from '../../common/util';
 4 | import { useSettings } from './useSettings';
 5 | 
 6 | const light = lazy(() => getComputedStyle(document.documentElement));
 7 | const dark = lazy(() => getComputedStyle(document.documentElement));
 8 | 
 9 | export const useThemeColor = (name: `--${string}`): string => {
10 |     const { colorMode } = useColorMode();
11 |     const { theme } = useSettings();
12 |     return useMemo(() => {
13 |         const styles = colorMode === 'dark' ? dark() : light();
14 |         return styles.getPropertyValue(name).trim();
15 |         // eslint-disable-next-line react-hooks/exhaustive-deps
16 |     }, [colorMode, name, theme]);
17 | };
18 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useTypeColor.ts:
--------------------------------------------------------------------------------
 1 | import { Type } from '@chainner/navi';
 2 | import { useMemo } from 'react';
 3 | import { getTypeAccentColors } from '../helpers/accentColors';
 4 | import { useSettings } from './useSettings';
 5 | 
 6 | export const useTypeColor = (type: Type) => {
 7 |     const { theme } = useSettings();
 8 |     return useMemo(() => getTypeAccentColors(type, theme), [type, theme]);
 9 | };
10 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useValidDropDownValue.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect } from 'react';
 2 | import { DropDownInput, InputSchemaValue } from '../../common/common-types';
 3 | 
 4 | export const useValidDropDownValue = (
 5 |     value: InputSchemaValue | undefined,
 6 |     setValue: (value: InputSchemaValue) => void,
 7 |     input: Pick<DropDownInput, 'options' | 'def'>
 8 | ) => {
 9 |     let valid = value ?? input.def;
10 |     if (input.options.every((o) => o.value !== valid)) {
11 |         valid = input.def;
12 |     }
13 | 
14 |     // reset to valid value
15 |     const resetTo = valid !== value ? valid : undefined;
16 |     useEffect(() => {
17 |         if (resetTo !== undefined) {
18 |             setValue(resetTo);
19 |         }
20 |     }, [resetTo, setValue]);
21 | 
22 |     return valid;
23 | };
24 | 


--------------------------------------------------------------------------------
/src/renderer/hooks/useWatchFiles.ts:
--------------------------------------------------------------------------------
 1 | import { useCallback, useEffect } from 'react';
 2 | import { log } from '../../common/log';
 3 | import { ipcRenderer } from '../safeIpc';
 4 | // eslint-disable-next-line import/no-nodejs-modules
 5 | import type { IpcRendererEvent } from 'electron/renderer';
 6 | 
 7 | export const useWatchFiles = (files: readonly string[], onChange: () => void): void => {
 8 |     const cb = useCallback(
 9 |         (event: IpcRendererEvent, eventType: 'add' | 'change' | 'unlink', path: string) => {
10 |             if (files.includes(path)) {
11 |                 onChange();
12 |             }
13 |         },
14 |         [files, onChange]
15 |     );
16 | 
17 |     useEffect(() => {
18 |         if (files.length === 0) return;
19 | 
20 |         ipcRenderer.invoke('watch-files', files).catch(log.error);
21 |         ipcRenderer.on('file-changed', cb);
22 | 
23 |         return () => {
24 |             ipcRenderer.invoke('unwatch-files', files).catch(log.error);
25 |             ipcRenderer.removeListener('file-changed', cb);
26 |         };
27 |     }, [cb, files]);
28 | };
29 | 


--------------------------------------------------------------------------------
/src/renderer/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 | import { DEFAULT_OPTIONS } from '../common/i18n';
4 | import { log } from '../common/log';
5 | 
6 | i18n.use(initReactI18next).init(DEFAULT_OPTIONS).catch(log.error);
7 | 


--------------------------------------------------------------------------------
/src/renderer/index.tsx:
--------------------------------------------------------------------------------
 1 | import electronLog from 'electron-log/renderer';
 2 | import { createRoot } from 'react-dom/client';
 3 | import { QueryClient, QueryClientProvider } from 'react-query';
 4 | import { LEVEL_NAME, log } from '../common/log';
 5 | import { App } from './app';
 6 | 
 7 | electronLog.transports.ipc.level = 'info';
 8 | electronLog.transports.console.level = 'debug';
 9 | log.addTransport({
10 |     log: ({ level, message, additional }) => {
11 |         electronLog[LEVEL_NAME[level]](message, ...additional);
12 |     },
13 | });
14 | 
15 | const queryClient = new QueryClient();
16 | 
17 | const container = document.getElementById('root');
18 | const root = createRoot(container!);
19 | root.render(
20 |     <QueryClientProvider client={queryClient}>
21 |         <App />
22 |     </QueryClientProvider>
23 | );
24 | 


--------------------------------------------------------------------------------
/src/renderer/renderer.ts:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * This file will automatically be loaded by webpack and run in the "renderer" context.
 3 |  * To learn more about the differences between the "main" and the "renderer" context in
 4 |  * Electron, visit:
 5 |  *
 6 |  * https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
 7 |  *
 8 |  * By default, Node.js integration in this file is disabled. When enabling Node.js integration
 9 |  * in a renderer process, please be aware of potential security implications. You can read
10 |  * more about security risks here:
11 |  *
12 |  * https://electronjs.org/docs/tutorial/security
13 |  *
14 |  * To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
15 |  * flag:
16 |  *
17 |  * ```
18 |  *  // Create the browser window.
19 |  *  mainWindow = new BrowserWindow({
20 |  *    width: 800,
21 |  *    height: 600,
22 |  *    webPreferences: {
23 |  *      nodeIntegration: true
24 |  *    }
25 |  *  });
26 |  * ```
27 |  */
28 | 
29 | import './global.scss';
30 | import './index';
31 | 


--------------------------------------------------------------------------------
/src/renderer/theme.ts:
--------------------------------------------------------------------------------
 1 | import { Theme, extendTheme } from '@chakra-ui/react';
 2 | 
 3 | // This is the initial theme config on startup
 4 | const dark = {
 5 |     initialColorMode: 'dark',
 6 |     useSystemColorMode: false,
 7 | } as const;
 8 | 
 9 | // TODO: This should be used later after reading the theme settings.
10 | // When light, change the theme to this values before displaying the
11 | // window. Need to figure out where and when to load the new theme.
12 | // Currently this does nothing.
13 | const light = {
14 |     initialColorMode: 'light',
15 |     useSystemColorMode: false,
16 | } as const;
17 | 
18 | // TODO: This should be used later to dynamically change the theme,
19 | // when the OS changes from dark to light or vice versa. Need to
20 | // figure out where and when to load the new theme. Currently this
21 | // does nothing.
22 | 
23 | const system = {
24 |     initialColorMode: 'system',
25 |     useSystemColorMode: true,
26 | } as const;
27 | 
28 | const grays = [50, 100, 200, 300, 400, 500, 600, 650, 700, 750, 800, 850, 900];
29 | const colors = {
30 |     gray: Object.fromEntries(grays.map((v) => [v, `var(--theme-${v})`])),
31 | };
32 | 
33 | const fonts = {
34 |     heading: `Open Sans, sans-serif`,
35 |     body: `Open Sans, sans-serif`,
36 |     monospace: `Roboto-Mono, monospace`,
37 | };
38 | 
39 | export const darktheme = extendTheme({ config: dark, colors, fonts } as const) as Theme;
40 | 
41 | export const lighttheme = extendTheme({ config: light, colors, fonts } as const) as Theme;
42 | 
43 | export const systemtheme = extendTheme({ config: system, colors, fonts } as const) as Theme;
44 | 


--------------------------------------------------------------------------------
/tests/common/SaveFile.test.ts:
--------------------------------------------------------------------------------
 1 | import * as fs from 'fs';
 2 | import * as path from 'path';
 3 | import { expect, test } from 'vitest';
 4 | import { RawSaveFile, SaveFile } from '../../src/main/SaveFile';
 5 | 
 6 | const dataDir = path.join(__dirname, '..', 'data');
 7 | 
 8 | for (const file of fs.readdirSync(dataDir)) {
 9 |     const filePath = path.join(dataDir, file);
10 | 
11 |     test(`Read save file ${file}`, async () => {
12 |         const parsed = await SaveFile.read(filePath);
13 |         expect(parsed).toMatchSnapshot();
14 |     });
15 |     test(`Write save file ${file}`, async () => {
16 |         const json = SaveFile.stringify(await SaveFile.read(filePath), '0.0.0-test');
17 |         const obj = JSON.parse(json) as RawSaveFile;
18 |         delete obj.migration;
19 |         delete obj.timestamp;
20 |         expect(obj).toMatchSnapshot();
21 |     });
22 | }
23 | 


--------------------------------------------------------------------------------
/tests/common/chainner-scope.test.ts:
--------------------------------------------------------------------------------
 1 | import { FunctionCallExpression, NamedExpression, evaluate } from '@chainner/navi';
 2 | import { test } from 'vitest';
 3 | import { getChainnerScope } from '../../src/common/types/chainner-scope';
 4 | import { assertNever } from '../../src/common/util';
 5 | 
 6 | test(`Chainner scope is correct`, () => {
 7 |     const scope = getChainnerScope();
 8 | 
 9 |     for (const [name, def] of scope.entries()) {
10 |         switch (def.type) {
11 |             case 'parameter':
12 |                 break;
13 |             case 'variable':
14 |             case 'struct':
15 |                 evaluate(new NamedExpression(name), scope);
16 |                 break;
17 |             case 'intrinsic-function':
18 |             case 'function':
19 |                 evaluate(
20 |                     new FunctionCallExpression(
21 |                         name,
22 |                         def.definition.parameters.map((p) => p.type)
23 |                     ),
24 |                     scope
25 |                 );
26 |                 break;
27 |             default:
28 |                 return assertNever(def);
29 |         }
30 |     }
31 | });
32 | 


--------------------------------------------------------------------------------
/tests/common/util.test.ts:
--------------------------------------------------------------------------------
 1 | import { expect, test } from 'vitest';
 2 | import { compareNumber, sameNumber } from '../../src/common/util';
 3 | 
 4 | test('sameNumber', () => {
 5 |     // true
 6 |     expect(sameNumber(0, 0)).toBe(true);
 7 |     expect(sameNumber(Infinity, Infinity)).toBe(true);
 8 |     expect(sameNumber(-Infinity, -Infinity)).toBe(true);
 9 |     expect(sameNumber(NaN, NaN)).toBe(true);
10 | 
11 |     // false
12 |     expect(sameNumber(-2, 0)).toBe(false);
13 | });
14 | 
15 | test('compareNumber', () => {
16 |     const numbers = [
17 |         0,
18 |         -0,
19 |         1,
20 |         -1,
21 |         2,
22 |         -2,
23 |         Number.MAX_VALUE,
24 |         Number.MIN_VALUE,
25 |         Number.EPSILON,
26 |         Number.MAX_SAFE_INTEGER,
27 |         Number.MIN_SAFE_INTEGER,
28 |         Infinity,
29 |         -Infinity,
30 |         NaN,
31 |     ];
32 | 
33 |     const a = [...numbers].sort(compareNumber);
34 |     const b = [...numbers].reverse().sort(compareNumber);
35 | 
36 |     expect(a).toStrictEqual(b);
37 | });
38 | 


--------------------------------------------------------------------------------
/tests/data/box-median-blur.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.8.1","content":{"nodes":[{"data":{"schemaId":"chainner:image:blur","inputData":{"1":3,"2":7},"id":"13626da8-98e4-4fc6-91ef-6b0e8e875619"},"id":"13626da8-98e4-4fc6-91ef-6b0e8e875619","position":{"x":480,"y":400},"type":"regularNode","selected":false,"height":339,"width":257,"zIndex":50},{"data":{"schemaId":"chainner:image:median_blur","inputData":{"1":4},"id":"e7c3d964-716d-43d7-83b9-83825c35a7e2"},"id":"e7c3d964-716d-43d7-83b9-83825c35a7e2","position":{"x":768,"y":400},"type":"regularNode","selected":false,"height":257,"width":257,"zIndex":50}],"edges":[{"id":"e8a88e5a-6d1f-44a6-9e42-e5f1b633d564","sourceHandle":"13626da8-98e4-4fc6-91ef-6b0e8e875619-0","targetHandle":"e7c3d964-716d-43d7-83b9-83825c35a7e2-0","source":"13626da8-98e4-4fc6-91ef-6b0e8e875619","target":"e7c3d964-716d-43d7-83b9-83825c35a7e2","type":"main","animated":false,"data":{},"zIndex":49}],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-06-06T16:48:59.273Z","checksum":"cbc68bacb9e1af4e16846bb25502daa9","migration":8}


--------------------------------------------------------------------------------
/tests/data/canny-edge-detection.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.11.6","content":{"nodes":[{"data":{"schemaId":"chainner:image:view","inputData":{},"id":"b82af4e7-689e-46a2-873f-d41bc570401e"},"id":"b82af4e7-689e-46a2-873f-d41bc570401e","position":{"x":645,"y":240},"type":"regularNode","selected":false,"height":371,"width":241,"zIndex":50},{"data":{"schemaId":"chainner:image:canny_edge_detection","inputData":{"1":100,"2":300},"id":"d6f6e8e6-3699-44eb-95c1-bb9bd951a32b"},"id":"d6f6e8e6-3699-44eb-95c1-bb9bd951a32b","position":{"x":330,"y":240},"type":"regularNode","selected":false,"height":327,"width":266,"zIndex":50},{"data":{"schemaId":"chainner:image:load","inputData":{},"id":"d708003f-ca9e-4178-a775-4f35735dae02"},"id":"d708003f-ca9e-4178-a775-4f35735dae02","position":{"x":30,"y":240},"type":"regularNode","selected":false,"height":487,"width":259,"zIndex":50}],"edges":[{"id":"1270dca4-eab0-4bb0-857a-6bd27bd1aa33","sourceHandle":"d708003f-ca9e-4178-a775-4f35735dae02-0","targetHandle":"d6f6e8e6-3699-44eb-95c1-bb9bd951a32b-0","source":"d708003f-ca9e-4178-a775-4f35735dae02","target":"d6f6e8e6-3699-44eb-95c1-bb9bd951a32b","type":"main","animated":false,"data":{},"zIndex":49},{"id":"77306abe-b237-4b21-abe9-5750fe43bd66","sourceHandle":"d6f6e8e6-3699-44eb-95c1-bb9bd951a32b-0","targetHandle":"b82af4e7-689e-46a2-873f-d41bc570401e-0","source":"d6f6e8e6-3699-44eb-95c1-bb9bd951a32b","target":"b82af4e7-689e-46a2-873f-d41bc570401e","type":"main","animated":false,"data":{},"zIndex":49}],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-08-31T04:55:31.338Z","checksum":"1c98b81bffb6983c881f27aea8bccabb","migration":15}


--------------------------------------------------------------------------------
/tests/data/convert-to-ncnn.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.10.1","content":{"nodes":[{"data":{"schemaId":"chainner:onnx:load_model","inputData":{},"id":"056923aa-5161-4efb-b1a8-1a15a867ae26"},"id":"056923aa-5161-4efb-b1a8-1a15a867ae26","position":{"x":390,"y":345},"type":"regularNode","selected":false,"height":252,"width":256,"zIndex":50},{"data":{"schemaId":"chainner:onnx:convert_to_ncnn","inputData":{"1":0},"id":"3e1a29d5-06de-4ab7-ac98-480c88156a73"},"id":"3e1a29d5-06de-4ab7-ac98-480c88156a73","position":{"x":675,"y":345},"type":"regularNode","selected":false,"height":252,"width":242,"zIndex":50},{"data":{"schemaId":"chainner:ncnn:save_model","inputData":{},"id":"9256866c-414a-4bd7-8b66-804922ac8875"},"id":"9256866c-414a-4bd7-8b66-804922ac8875","position":{"x":945,"y":345},"type":"regularNode","selected":true,"height":270,"width":256,"zIndex":70}],"edges":[{"id":"1eaa9bee-3615-45de-8bb6-6b79230ff664","sourceHandle":"3e1a29d5-06de-4ab7-ac98-480c88156a73-0","targetHandle":"9256866c-414a-4bd7-8b66-804922ac8875-0","source":"3e1a29d5-06de-4ab7-ac98-480c88156a73","target":"9256866c-414a-4bd7-8b66-804922ac8875","type":"main","animated":false,"data":{},"zIndex":69},{"id":"c83b7ba6-3179-43ba-8c7f-9b44a2f8ca45","sourceHandle":"056923aa-5161-4efb-b1a8-1a15a867ae26-0","targetHandle":"3e1a29d5-06de-4ab7-ac98-480c88156a73-0","source":"056923aa-5161-4efb-b1a8-1a15a867ae26","target":"3e1a29d5-06de-4ab7-ac98-480c88156a73","type":"main","animated":false,"data":{},"zIndex":49}],"viewport":{"x":-113.11594255284149,"y":57.879643031246246,"zoom":0.6263322193120638}},"timestamp":"2022-08-13T02:58:43.342Z","checksum":"8b7a040d325bbda5f9fb8bca141054db","migration":15}


--------------------------------------------------------------------------------
/tests/data/copy-to-clipboard.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.12.3","content":{"nodes":[{"data":{"schemaId":"chainner:image:load","inputData":{},"id":"5fab26d4-8aee-472d-a76a-c8360b7c879e"},"id":"5fab26d4-8aee-472d-a76a-c8360b7c879e","position":{"x":-409.9270007828063,"y":434.0347224631112},"type":"regularNode","selected":false,"height":488,"width":257,"zIndex":50},{"data":{"schemaId":"chainner:utility:copy_to_clipboard","inputData":{},"id":"bd03ae2b-2de9-4ce6-a80e-0deef9d77d32"},"id":"bd03ae2b-2de9-4ce6-a80e-0deef9d77d32","position":{"x":10.940350900119938,"y":667.0756396555195},"type":"regularNode","selected":true,"height":154,"width":242,"zIndex":70}],"edges":[{"id":"8ff6f8b5-f99f-4d35-9429-1c29850c0770","sourceHandle":"5fab26d4-8aee-472d-a76a-c8360b7c879e-2","targetHandle":"bd03ae2b-2de9-4ce6-a80e-0deef9d77d32-0","source":"5fab26d4-8aee-472d-a76a-c8360b7c879e","target":"bd03ae2b-2de9-4ce6-a80e-0deef9d77d32","type":"main","animated":false,"data":{},"zIndex":69}],"viewport":{"x":434.34962404436874,"y":-334.55802893290115,"zoom":0.8705505632961259}},"timestamp":"2022-09-11T18:50:46.480Z","checksum":"84ccdb64066a8d7bc69f225bffb19cb3","migration":16}


--------------------------------------------------------------------------------
/tests/data/crop-content.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.9.2","content":{"nodes":[{"data":{"schemaId":"chainner:image:crop_content","inputData":{},"id":"561b20d5-ff13-4424-930e-352caeb48aff"},"id":"561b20d5-ff13-4424-930e-352caeb48aff","position":{"x":896,"y":528},"type":"regularNode","selected":true,"height":175,"width":241,"zIndex":70}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-06-26T16:08:51.948Z","checksum":"e4ba10ded4459c6fcf627fc35f4fa611","migration":12}


--------------------------------------------------------------------------------
/tests/data/image-adjustments.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.8.1","content":{"nodes":[{"data":{"schemaId":"chainner:image:threshold","inputData":{"1":69,"2":76,"3":"2"},"id":"23a197cf-248a-4c35-8aa4-5c9a32863d44"},"id":"23a197cf-248a-4c35-8aa4-5c9a32863d44","position":{"x":625.8070606398546,"y":592.9030240407762},"type":"regularNode","selected":false,"height":392,"width":242,"zIndex":50},{"data":{"schemaId":"chainner:image:brightness_and_contrast","inputData":{"1":24,"2":43},"id":"59b0e388-685e-4b7b-8ffe-c64bc98a350f"},"id":"59b0e388-685e-4b7b-8ffe-c64bc98a350f","position":{"x":571.3328481394495,"y":163.52957902171042},"type":"regularNode","selected":false,"height":310,"width":278,"zIndex":50},{"data":{"schemaId":"chainner:image:hue_and_saturation","inputData":{"1":-61,"2":68},"id":"5acd95ce-3be7-42d0-9388-2e681fe39d1c"},"id":"5acd95ce-3be7-42d0-9388-2e681fe39d1c","position":{"x":1034.6005807596155,"y":179.7658590325766},"type":"regularNode","selected":false,"height":310,"width":242,"zIndex":50},{"data":{"schemaId":"chainner:image:threshold_adaptive","inputData":{"1":90,"2":0,"3":0,"4":9,"5":2},"id":"75cbaa5e-7d31-4990-b46d-8087e550a729"},"id":"75cbaa5e-7d31-4990-b46d-8087e550a729","position":{"x":1007.135509649303,"y":537.8809142551769},"type":"regularNode","selected":false,"height":572,"width":262,"zIndex":50}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-05-26T19:24:24.086Z","checksum":"5e390b4028ca1e3d9849869c97336359","migration":7}


--------------------------------------------------------------------------------
/tests/data/image-metrics.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.8.1","content":{"nodes":[{"data":{"schemaId":"chainner:image:image_metrics","inputData":{},"id":"0a3ed516-9505-462d-9ada-c409da4c4d67"},"id":"0a3ed516-9505-462d-9ada-c409da4c4d67","position":{"x":624,"y":560},"type":"regularNode","selected":true,"height":295,"width":241,"zIndex":70}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-06-07T02:21:44.292Z","checksum":"b32ee40fefb7b627c825ae653b232c0a","migration":8}


--------------------------------------------------------------------------------
/tests/data/model-scale.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.9.2","content":{"nodes":[{"data":{"schemaId":"chainner:pytorch:load_model","inputData":{"0":"C:\\DS3TexUp\\Cupscale 1.39.0f1\\CupscaleData\\models\\ESRGAN\\4x-AnimeSharp.pth"},"id":"336d5195-2137-49c4-8efc-6aa1bd99c4c1"},"id":"336d5195-2137-49c4-8efc-6aa1bd99c4c1","position":{"x":-1,"y":219},"type":"regularNode","selected":false,"height":418,"width":517,"zIndex":50},{"data":{"schemaId":"chainner:pytorch:model_dim","inputData":{},"id":"66afd92a-138e-42b7-a5d3-a45737f31ed4"},"id":"66afd92a-138e-42b7-a5d3-a45737f31ed4","position":{"x":239.78031643091583,"y":713.9047452339597},"type":"regularNode","selected":true,"height":240,"width":503,"zIndex":70}],"edges":[{"id":"ef31ea17-2e21-402b-8366-7ebacc4fc084","sourceHandle":"336d5195-2137-49c4-8efc-6aa1bd99c4c1-0","targetHandle":"66afd92a-138e-42b7-a5d3-a45737f31ed4-0","source":"336d5195-2137-49c4-8efc-6aa1bd99c4c1","target":"66afd92a-138e-42b7-a5d3-a45737f31ed4","type":"main","animated":false,"data":{},"zIndex":69}],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-07-02T17:37:21.861Z","checksum":"94dcaecb771e45bf85981d3b74643183","migration":12}


--------------------------------------------------------------------------------
/tests/data/normal-map-gen-invert.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.21.2","content":{"nodes":[{"data":{"schemaId":"chainner:image:normal_generator","inputData":{"1":0,"2":0,"3":0,"4":1,"5":"sobel","6":1,"7":"none","8":0.25,"9":0.5,"10":0.3,"11":0.25,"12":0.2,"13":0.15,"14":0.1,"15":0.1,"16":0,"17":0,"18":0},"id":"ac216866-3f16-46ed-812d-2898f062e479"},"id":"ac216866-3f16-46ed-812d-2898f062e479","position":{"x":608,"y":240},"type":"regularNode","selected":false,"height":440,"width":295}],"edges":[],"viewport":{"x":-392.7852907370326,"y":74.69533137641815,"zoom":1.1486983549970369}},"timestamp":"2024-02-09T11:26:33.293Z","checksum":"e0eba6b2e538e07bc74410795a989949","migration":40}


--------------------------------------------------------------------------------
/tests/data/onnx-interpolate.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.11.0","content":{"nodes":[{"data":{"schemaId":"chainner:onnx:interpolate_models","inputData":{"2":67},"id":"fbb2100e-2568-4427-b730-642e92b633b0","isDisabled":false},"id":"fbb2100e-2568-4427-b730-642e92b633b0","position":{"x":384,"y":144},"type":"regularNode","selected":false,"height":381,"width":247,"zIndex":50}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-08-19T02:33:40.330Z","checksum":"1826a1b882ec4c54636066932cdeafb1","migration":15}


--------------------------------------------------------------------------------
/tests/data/opacity.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.9.0","content":{"nodes":[{"data":{"schemaId":"chainner:image:opacity","inputData":{"1":72},"id":"6c9e3fce-fd01-492b-8abc-a175b104041e"},"id":"6c9e3fce-fd01-492b-8abc-a175b104041e","position":{"x":480,"y":400},"type":"regularNode","selected":true,"height":241,"width":241,"zIndex":70}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-06-16T19:30:08.129Z","checksum":"07014c123db5bd7ebbb1d5a50421555f","migration":11}


--------------------------------------------------------------------------------
/tests/data/resize-to-side.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.12.1","content":{"nodes":[{"data":{"schemaId":"chainner:image:save","inputData":{"4":"png"},"id":"0a558c45-2c62-4d94-a006-f3e24a40d2f5"},"id":"0a558c45-2c62-4d94-a006-f3e24a40d2f5","position":{"x":1485,"y":480},"type":"regularNode","selected":false,"height":422,"width":265,"zIndex":50},{"data":{"schemaId":"chainner:image:resize_to_side","inputData":{"1":2160,"2":"width","3":-1},"id":"89ad8b89-a34e-4327-846d-4c66ab4006e8"},"id":"89ad8b89-a34e-4327-846d-4c66ab4006e8","position":{"x":990,"y":375},"type":"regularNode","selected":false,"height":404,"width":283,"zIndex":50},{"data":{"schemaId":"chainner:image:load","inputData":{},"id":"fc9844d5-e9be-4f7d-8e01-b17944fb6874"},"id":"fc9844d5-e9be-4f7d-8e01-b17944fb6874","position":{"x":615,"y":330},"type":"regularNode","selected":false,"height":488,"width":257,"zIndex":50}],"edges":[{"id":"3046d390-6349-410c-b348-c0761399f39b","sourceHandle":"89ad8b89-a34e-4327-846d-4c66ab4006e8-0","targetHandle":"0a558c45-2c62-4d94-a006-f3e24a40d2f5-0","source":"89ad8b89-a34e-4327-846d-4c66ab4006e8","target":"0a558c45-2c62-4d94-a006-f3e24a40d2f5","type":"main","animated":false,"data":{},"zIndex":49},{"id":"64cee5d9-9521-4c12-8db0-52dcc9b42ca5","sourceHandle":"fc9844d5-e9be-4f7d-8e01-b17944fb6874-0","targetHandle":"89ad8b89-a34e-4327-846d-4c66ab4006e8-0","source":"fc9844d5-e9be-4f7d-8e01-b17944fb6874","target":"89ad8b89-a34e-4327-846d-4c66ab4006e8","type":"main","animated":false,"data":{},"zIndex":49}],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-09-06T13:37:05.838Z","checksum":"e3674a348d6215fefb7cfecf90fb65a1","migration":16}


--------------------------------------------------------------------------------
/tests/data/save video input.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.21.2","content":{"nodes":[{"data":{"schemaId":"chainner:image:save_video","inputData":{"2":"foo","3":"libx264","4":"mkv","5":"mkv","6":"mkv","7":"webm","8":"veryfast","9":36,"10":"auto","11":"auto","12":1,"13":"Same text","14":24},"inputHeight":{"2":80,"13":80},"nodeWidth":240,"id":"7cbc339b-88d0-4192-ab0f-99c37d9e97eb"},"id":"7cbc339b-88d0-4192-ab0f-99c37d9e97eb","position":{"x":288,"y":224},"type":"collector","selected":false,"height":560,"width":258},{"data":{"schemaId":"chainner:image:save_video","inputData":{"2":"foo","3":"libvpx-vp9","4":"mkv","5":"mkv","6":"mkv","7":"webm","8":"veryfast","9":41,"10":"auto","11":"auto","12":0,"13":"Sam","14":24},"inputHeight":{"2":80,"13":80},"nodeWidth":240,"id":"a84ab15c-c8a1-555c-912f-dbfb8aee8a93"},"id":"a84ab15c-c8a1-555c-912f-dbfb8aee8a93","position":{"x":864,"y":224},"type":"collector","selected":false,"height":432,"width":240},{"data":{"schemaId":"chainner:image:save_video","inputData":{"2":"foo","3":"libvpx-vp9","4":"mkv","5":"mkv","6":"mkv","7":"mp4","8":"veryfast","9":11,"10":"auto","11":"auto","12":1,"13":"Sam","14":24},"inputHeight":{"2":80,"13":80},"nodeWidth":240,"id":"cb19d737-a6ea-516f-88a0-0f74fce288c4"},"id":"cb19d737-a6ea-516f-88a0-0f74fce288c4","position":{"x":576,"y":224},"type":"collector","selected":false,"height":520,"width":258}],"edges":[],"viewport":{"x":-174,"y":-28,"zoom":1}},"timestamp":"2024-01-25T23:38:29.614Z","checksum":"abaed3aadc2b9b5593ca292af0cbd54a","migration":39}


--------------------------------------------------------------------------------
/tests/data/save-image-webp-lossless.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.19.1","content":{"nodes":[{"data":{"schemaId":"chainner:image:save","inputData":{"4":"webp-lossless","5":27,"6":"BC1_UNORM_SRGB","7":0,"8":0,"9":0,"10":0,"11":2167057,"12":0,"13":0,"14":0},"inputSize":{"2":{"width":240,"height":80},"3":{"width":240,"height":80}},"id":"08995baf-c169-4679-8372-4a6e8ee0b920"},"id":"08995baf-c169-4679-8372-4a6e8ee0b920","position":{"x":528,"y":336},"type":"regularNode","selected":true,"height":324,"width":240,"zIndex":70}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2023-08-12T17:06:45.210Z","checksum":"0194d4899f5d5593b1e996bf19ff74b5","migration":31}


--------------------------------------------------------------------------------
/tests/data/utilities.chn:
--------------------------------------------------------------------------------
1 | {"version":"0.8.1","content":{"nodes":[{"data":{"schemaId":"chainner:utility:text_append","inputData":{"0":"-","1":"Foo","2":"Bar","3":"","4":""},"id":"18acc79d-abf9-46f1-9b4b-651836a9d3b1"},"id":"18acc79d-abf9-46f1-9b4b-651836a9d3b1","position":{"x":592.03125,"y":283},"type":"regularNode","selected":false,"height":548,"width":242,"zIndex":50},{"data":{"schemaId":"chainner:utility:math","inputData":{"0":1,"1":"mul","2":5},"id":"56909a51-acd0-4e3d-b515-b3cb34e725c5"},"id":"56909a51-acd0-4e3d-b515-b3cb34e725c5","position":{"x":95.03125,"y":377},"type":"regularNode","selected":true,"height":384,"width":257,"zIndex":70},{"data":{"schemaId":"chainner:utility:note","inputData":{"0":"Hi mom!"},"id":"e21f5bf4-8171-43ce-a483-5152c5b7022e"},"id":"e21f5bf4-8171-43ce-a483-5152c5b7022e","position":{"x":282.03125,"y":81},"type":"regularNode","selected":false,"height":202,"width":258,"zIndex":50}],"edges":[],"viewport":{"x":0,"y":0,"zoom":1}},"timestamp":"2022-05-27T11:19:23.559Z","checksum":"0b701a58ee2c5edf56936dbd708f9d92","migration":7}


--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "compilerOptions": {
 3 |     "noEmit": true,
 4 |     "noImplicitAny": true,
 5 |     "lib": [
 6 |       "ESNext",
 7 |       "DOM",
 8 |       "DOM.Iterable"
 9 |     ],
10 |     "module": "commonjs",
11 |     "target": "ESNext",
12 |     "jsx": "react-jsx",
13 |     "allowJs": true,
14 |     "checkJs": false,
15 |     "strict": true,
16 |     "allowSyntheticDefaultImports": true,
17 |     "skipLibCheck": true,
18 |     "moduleResolution": "node",
19 |     "resolveJsonModule": true,
20 |     "esModuleInterop": true,
21 |     "outDir": "dist",
22 |   },
23 |   "include": [
24 |     "scripts",
25 |     "src",
26 |     "tests",
27 |   ]
28 | }
29 | 


--------------------------------------------------------------------------------
/vite/forge-types.ts:
--------------------------------------------------------------------------------
 1 | export {}; // Make this a module
 2 | 
 3 | declare global {
 4 |     // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Vite
 5 |     // plugin that tells the Electron app where to look for the Vite-bundled app code (depending on
 6 |     // whether you're running in development or production).
 7 |     const MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
 8 |     const MAIN_WINDOW_VITE_NAME: string;
 9 | 
10 |     namespace NodeJS {
11 |         interface Process {
12 |             // Used for hot reload after preload scripts.
13 |             viteDevServers: Record<string, import('vite').ViteDevServer>;
14 |         }
15 |     }
16 | 
17 |     type VitePluginConfig = ConstructorParameters<
18 |         typeof import('@electron-forge/plugin-vite').VitePlugin
19 |     >[0];
20 | 
21 |     interface VitePluginRuntimeKeys {
22 |         VITE_DEV_SERVER_URL: `${string}_VITE_DEV_SERVER_URL`;
23 |         VITE_NAME: `${string}_VITE_NAME`;
24 |     }
25 | }
26 | 
27 | declare module 'vite' {
28 |     interface ConfigEnv<K extends keyof VitePluginConfig = keyof VitePluginConfig> {
29 |         root: string;
30 |         forgeConfig: VitePluginConfig;
31 |         forgeConfigSelf: VitePluginConfig[K][number];
32 |     }
33 | }
34 | 


--------------------------------------------------------------------------------