diff --git a/.github/workflows/build.yml b/.github/workflows/build-and-test.yml similarity index 92% rename from .github/workflows/build.yml rename to .github/workflows/build-and-test.yml index 69948ab..2be0945 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build-and-test.yml @@ -6,8 +6,8 @@ on: - master jobs: - build_and_publish: - name: Build + build_and_test: + name: Build and Test runs-on: ubuntu-latest @@ -54,3 +54,7 @@ jobs: run: | pnpm build:css pnpm build + + - name: Test + run: | + pnpm test diff --git a/README.md b/README.md index 4e3aa15..c90b8ff 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # ComfyBox -An alternative UI to the backend server of the Stable Diffusion webapp [ComfyUI](https://github.com/comfyanonymous/ComfyUI). Build your workflow in a graph structure and have a custom Stable Diffusion interface created for you automatically. - -This project is *still under construction* and many features are missing, be aware of the tradeoffs if you're interested in using it. +ComfyBox is a frontend to Stable Diffusion that lets you create custom image generation interfaces without any code. It uses [ComfyUI](https://github.com/comfyanonymous/ComfyUI) under the hood for maximum power and extensibility. ![Screenshot](./static/screenshot.png) @@ -10,21 +8,22 @@ This project is *still under construction* and many features are missing, be awa ## Installation -1. Download the latest release [here](https://nightly.link/space-nuko/ComfyBox/workflows/build-and-publish/master/ComfyBox-dist) and extract it somewhere -2. Start the ComfyUI backend with `python main.py --enable-cors-header` -3. In the folder you extracted open the `run.bat`/`run.sh` script (requires Python 3 to be on your PATH). Alternatively you can serve the contents of the folder with a web server. +1. Download the latest release [here](https://nightly.link/space-nuko/ComfyBox/workflows/build-and-publish/master/ComfyBox-dist) and extract it somewhere. +2. Start the ComfyUI backend with `python main.py --enable-cors-header`. +3. In the folder you extracted open the `run.bat`/`run.sh` script (requires Python 3 to be on your PATH). Alternatively you can serve the contents of the folder with any web server. ## NOTE -This frontend isn't compatible with regular ComfyUI's workflow format since extra metadata is saved like panel layout, so you'll have to spend a bit of time recreating them. This project also isn't compatible with regular ComfyUI's frontend extension format, but useful extensions can be integrated into this repo with some effort. +This project is *still under construction* and some features are missing, be aware of the tradeoffs if you're interested in using it. -## Proposed Features -- All the power of ComfyUI with more convenience on top -- Autocreation of UI widgets from your workflow, quickly creating a personalized dashboard -- Arrange the UI however you like and attach custom classes/styles to each widget -- Custom widget types -- See the status of queued and finished generations and their configs in realtime -- Development with TypeScript +This frontend isn't compatible with regular ComfyUI's workflow format since extra metadata is saved like panel layout, so you'll have to spend a bit of time recreating them. This project also isn't compatible with regular ComfyUI's frontend extension format, but useful extensions can be integrated into the base repo with some effort. + +## Features +- *No-Code UI Builder* - A novel system for creating your own Stable Diffusion user interfaces from the basic components. +- *Extension Support* - All custom ComfyUI nodes are supported out of the box. +- *Prompt Queue* - Queue up multiple prompts without waiting for them to finish first. Inspect currently queued and executed prompts. +- *Prompt History* - Browse through previously generated prompts and their output images/parameters. +- *Mobile-Friendly Version* - Includes a version of the UI optimized for mobile use, while still supporting the same customized workflows of the desktop version. ## Development diff --git a/litegraph b/litegraph index 12254bf..8cd513a 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit 12254bf117b60fef8189ba51cb40ea47b3cc45f7 +Subproject commit 8cd513ae582c2fa3694853dda4a170f0afa11b77 diff --git a/mobile/index.html b/mobile/index.html index fa16259..b206f87 100644 --- a/mobile/index.html +++ b/mobile/index.html @@ -1,4 +1,3 @@ - @@ -9,7 +8,7 @@ -
+
diff --git a/package.json b/package.json index 53e65a9..ee0e8d0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "preview": "turbo run preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", - "test:unit": "vitest", + "test": "vitest run", + "test:watch": "vitest", "lint": "prettier --plugin-search-dir . --check . && eslint .", "format": "prettier --plugin-search-dir . --write .", "svelte-check": "svelte-check", @@ -43,6 +44,7 @@ "@gradio/gallery": "workspace:*", "@gradio/icons": "workspace:*", "@gradio/image": "workspace:*", + "@gradio/json": "workspace:*", "@gradio/tabs": "workspace:*", "@gradio/theme": "workspace:*", "@gradio/upload": "workspace:*", @@ -62,6 +64,7 @@ "img-comparison-slider": "^8.0.0", "pollen-css": "^4.6.2", "radix-icons-svelte": "^1.2.1", + "svelte-feather-icons": "^4.0.0", "svelte-preprocess": "^5.0.3", "svelte-select": "^5.5.3", "svelte-splitpanes": "^0.7.13", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1b4366..a42cbe3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,6 +31,9 @@ importers: '@gradio/image': specifier: workspace:* version: link:gradio/js/image + '@gradio/json': + specifier: workspace:* + version: link:gradio/js/json '@gradio/tabs': specifier: workspace:* version: link:gradio/js/tabs @@ -88,6 +91,9 @@ importers: radix-icons-svelte: specifier: ^1.2.1 version: 1.2.1 + svelte-feather-icons: + specifier: ^4.0.0 + version: 4.0.0 svelte-preprocess: specifier: ^5.0.3 version: 5.0.3(sass@1.61.0)(svelte@3.58.0)(typescript@5.0.3) @@ -872,6 +878,28 @@ importers: specifier: ^4.2.1 version: 4.3.1 + litegraph/packages/tests: + dependencies: + '@litegraph-ts/core': + specifier: workspace:* + version: link:../core + '@litegraph-ts/nodes-basic': + specifier: workspace:* + version: link:../nodes-basic + vitest: + specifier: ^0.31.0 + version: 0.31.0 + devDependencies: + '@litegraph-ts/tsconfig': + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: ^5.0.3 + version: 5.0.3 + vite: + specifier: ^4.2.1 + version: 4.3.1 + litegraph/packages/tsconfig: {} packages: @@ -2206,11 +2234,9 @@ packages: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: '@types/chai': 4.3.4 - dev: true /@types/chai@4.3.4: resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} - dev: true /@types/concat-stream@1.6.1: resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} @@ -2403,6 +2429,45 @@ packages: eslint-visitor-keys: 3.4.0 dev: true + /@vitest/expect@0.31.0: + resolution: {integrity: sha512-Jlm8ZTyp6vMY9iz9Ny9a0BHnCG4fqBa8neCF6Pk/c/6vkUk49Ls6UBlgGAU82QnzzoaUs9E/mUhq/eq9uMOv/g==} + dependencies: + '@vitest/spy': 0.31.0 + '@vitest/utils': 0.31.0 + chai: 4.3.7 + dev: false + + /@vitest/runner@0.31.0: + resolution: {integrity: sha512-H1OE+Ly7JFeBwnpHTrKyCNm/oZgr+16N4qIlzzqSG/YRQDATBYmJb/KUn3GrZaiQQyL7GwpNHVZxSQd6juLCgw==} + dependencies: + '@vitest/utils': 0.31.0 + concordance: 5.0.4 + p-limit: 4.0.0 + pathe: 1.1.0 + dev: false + + /@vitest/snapshot@0.31.0: + resolution: {integrity: sha512-5dTXhbHnyUMTMOujZPB0wjFjQ6q5x9c8TvAsSPUNKjp1tVU7i9pbqcKPqntyu2oXtmVxKbuHCqrOd+Ft60r4tg==} + dependencies: + magic-string: 0.30.0 + pathe: 1.1.0 + pretty-format: 27.5.1 + dev: false + + /@vitest/spy@0.31.0: + resolution: {integrity: sha512-IzCEQ85RN26GqjQNkYahgVLLkULOxOm5H/t364LG0JYb3Apg0PsYCHLBYGA006+SVRMWhQvHlBBCyuByAMFmkg==} + dependencies: + tinyspy: 2.1.0 + dev: false + + /@vitest/utils@0.31.0: + resolution: {integrity: sha512-kahaRyLX7GS1urekRXN2752X4gIgOGVX4Wo8eDUGUkTWlGpXzf5ZS6N9RUUS+Re3XEE8nVGqNyxkSxF5HXlGhQ==} + dependencies: + concordance: 5.0.4 + loupe: 2.3.6 + pretty-format: 27.5.1 + dev: false + /@zerodevx/svelte-toast@0.9.3(svelte@3.58.0): resolution: {integrity: sha512-VPKWR4A9y01fyXRscu9HiTj7tV2hFrpRKZvGwMmaPXfHIXR1D9+NNsz0HXcQ7qZ0C5UaHS3n9uNtPtIcAXT7RQ==} peerDependencies: @@ -2422,13 +2487,11 @@ packages: /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} - dev: true /acorn@8.8.2: resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -2448,7 +2511,6 @@ packages: /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -2467,7 +2529,6 @@ packages: /ansi-styles@5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} - dev: true /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -2513,7 +2574,6 @@ packages: /assertion-error@1.1.0: resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} - dev: true /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -2655,6 +2715,10 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + /blueimp-md5@2.19.0: + resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -2712,6 +2776,11 @@ packages: streamsearch: 1.1.0 dev: true + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: false + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: @@ -2762,7 +2831,6 @@ packages: loupe: 2.3.6 pathval: 1.1.1 type-detect: 4.0.8 - dev: true /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -2788,7 +2856,6 @@ packages: /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} - dev: true /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} @@ -2965,6 +3032,20 @@ packages: typedarray: 0.0.6 dev: false + /concordance@5.0.4: + resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} + engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} + dependencies: + date-time: 3.1.0 + esutils: 2.0.3 + fast-diff: 1.2.0 + js-string-escape: 1.0.1 + lodash: 4.17.21 + md5-hex: 3.0.1 + semver: 7.5.0 + well-known-symbols: 2.0.0 + dev: false + /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true @@ -3189,6 +3270,13 @@ packages: topojson-client: 3.1.0 dev: false + /date-time@3.1.0: + resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} + engines: {node: '>=6'} + dependencies: + time-zone: 1.0.0 + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -3209,7 +3297,6 @@ packages: engines: {node: '>=6'} dependencies: type-detect: 4.0.8 - dev: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -3731,7 +3818,6 @@ packages: /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - dev: true /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} @@ -3819,6 +3905,10 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + /fast-diff@1.2.0: + resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} + dev: false + /fast-glob@3.2.12: resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} engines: {node: '>=8.6.0'} @@ -4005,7 +4095,6 @@ packages: /get-func-name@2.0.0: resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} - dev: true /get-intrinsic@1.2.0: resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} @@ -4822,6 +4911,11 @@ packages: resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==} dev: true + /js-string-escape@1.0.1: + resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} + engines: {node: '>= 0.8'} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -4880,6 +4974,10 @@ packages: hasBin: true dev: true + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: false + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -4943,7 +5041,6 @@ packages: /local-pkg@0.4.3: resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} engines: {node: '>=14'} - dev: true /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -4987,7 +5084,6 @@ packages: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: get-func-name: 2.0.0 - dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -5044,6 +5140,13 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /md5-hex@3.0.1: + resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} + engines: {node: '>=8'} + dependencies: + blueimp-md5: 2.19.0 + dev: false + /media-encoder-host-broker@7.0.79: resolution: {integrity: sha512-uAKboXNaXflOGvHMrj8eOjx3cPJkYDcCOI3mUpAoHBquDzqhgIDovF4yiedCT2YMUPtqPmHOlHUPofH5/3cDmA==} dependencies: @@ -5141,6 +5244,15 @@ packages: hasBin: true dev: false + /mlly@1.2.1: + resolution: {integrity: sha512-1aMEByaWgBPEbWV2BOPEMySRrzl7rIHXmQxam4DM8jVjalTQDjpN2ZKOLUrwyhfZQO7IXHml2StcHMhooDeEEQ==} + dependencies: + acorn: 8.8.2 + pathe: 1.1.0 + pkg-types: 1.0.3 + ufo: 1.1.2 + dev: false + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -5268,6 +5380,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: false + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -5338,9 +5457,12 @@ packages: engines: {node: '>=8'} dev: true + /pathe@1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + dev: false + /pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} - dev: true /performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} @@ -5368,6 +5490,14 @@ packages: find-up: 4.1.0 dev: true + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.2.1 + pathe: 1.1.0 + dev: false + /plotly.js-dist-min@2.10.1: resolution: {integrity: sha512-H0ls1C2uu2U+qWw76djo4/zOGtUKfMILwFhu7tCOaG/wH5ypujrYGCH03N9SQVf1SXcctTfW57USf8LmagSiPQ==} dev: false @@ -5477,6 +5607,15 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + /pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: false + /pretty-format@29.5.0: resolution: {integrity: sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5539,6 +5678,10 @@ packages: resolution: {integrity: sha512-svmiMd0ocpdTm9cvAz0klcZpnh639lVctj6psQiawd4pYalVzOG4cX+JizAgRckyTAsRVdzObP7D2EBrSfdghA==} dev: false + /react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: false + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true @@ -5783,7 +5926,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: true /set-cookie-parser@2.6.0: resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} @@ -5809,6 +5951,10 @@ packages: object-inspect: 1.12.3 dev: false + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: false + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true @@ -5909,6 +6055,10 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: false + /standardized-audio-context@25.3.45: resolution: {integrity: sha512-d1UVvbz0mDmEqNehvoTKlpSevRJ3YiVZ6kdboeaWX+8cl94H1w8x7c5RNdg0nqxiE049LMeF4tFPuDl5Vm78Kg==} dependencies: @@ -5917,6 +6067,10 @@ packages: tslib: 2.5.0 dev: false + /std-env@3.3.3: + resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5981,7 +6135,6 @@ packages: resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} dependencies: acorn: 8.8.2 - dev: true /style-mod@4.0.3: resolution: {integrity: sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==} @@ -6097,6 +6250,12 @@ packages: svelte: 3.58.0 dev: true + /svelte-feather-icons@4.0.0: + resolution: {integrity: sha512-4ieUsjp+VYa1r6y80jDt9zRiRUZyJNbESpRdHdJJhiBubyuXX96A7f1UZSK4olxzP6Qsg5ZAuyZlnmvD+/swAA==} + dependencies: + svelte: 3.58.0 + dev: false + /svelte-floating-ui@1.2.8: resolution: {integrity: sha512-8Ifi5CD2Ui7FX7NjJRmutFtXjrB8T/FMNoS2H8P81t5LHK4I9G4NIs007rLWG/nRl7y+zJUXa3tWuTjYXw/O5A==} dependencies: @@ -6400,6 +6559,11 @@ packages: dependencies: any-promise: 1.3.0 + /time-zone@1.0.0: + resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} + engines: {node: '>=4'} + dev: false + /tiny-glob@0.2.9: resolution: {integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==} dependencies: @@ -6413,18 +6577,27 @@ packages: /tinybench@2.4.0: resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==} - dev: true /tinypool@0.3.1: resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} engines: {node: '>=14.0.0'} dev: true + /tinypool@0.5.0: + resolution: {integrity: sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==} + engines: {node: '>=14.0.0'} + dev: false + /tinyspy@1.1.1: resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==} engines: {node: '>=14.0.0'} dev: true + /tinyspy@2.1.0: + resolution: {integrity: sha512-7eORpyqImoOvkQJCSkL0d0mB4NHHIFAy4b1u8PHdDa7SjGS2njzl6/lyGoZLm+eyYEtlUmFGE0rFj66SWxZgQQ==} + engines: {node: '>=14.0.0'} + dev: false + /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -6523,7 +6696,6 @@ packages: /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - dev: true /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} @@ -6556,6 +6728,10 @@ packages: engines: {node: '>=12.20'} hasBin: true + /ufo@1.1.2: + resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} + dev: false + /undici@5.20.0: resolution: {integrity: sha512-J3j60dYzuo6Eevbawwp1sdg16k5Tf768bxYK4TUJRH7cBM4kFCbf3mOnM/0E3vQYXvpxITbbWmBafaDbxLDz3g==} engines: {node: '>=12.18'} @@ -7010,6 +7186,27 @@ packages: extsprintf: 1.3.0 dev: false + /vite-node@0.31.0(@types/node@18.16.0): + resolution: {integrity: sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.2.1 + pathe: 1.1.0 + picocolors: 1.0.0 + vite: 4.3.1(@types/node@18.16.0) + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: false + /vite-plugin-checker@0.5.6(eslint@8.37.0)(typescript@5.0.3)(vite@4.2.1): resolution: {integrity: sha512-ftRyON0gORUHDxcDt2BErmsikKSkfvl1i2DoP6Jt2zDO9InfvM6tqO1RkXhSjkaXEhKPea6YOnhFaZxW3BzudQ==} engines: {node: '>=14.16'} @@ -7251,6 +7448,39 @@ packages: fsevents: 2.3.2 dev: true + /vite@4.3.1(@types/node@18.16.0): + resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 18.16.0 + esbuild: 0.17.18 + postcss: 8.4.21 + rollup: 3.21.0 + optionalDependencies: + fsevents: 2.3.2 + dev: false + /vite@4.3.1(@types/node@18.16.0)(sass@1.61.0): resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==} engines: {node: ^14.18.0 || >=16.0.0} @@ -7382,6 +7612,71 @@ packages: - terser dev: true + /vitest@0.31.0: + resolution: {integrity: sha512-JwWJS9p3GU9GxkG7eBSmr4Q4x4bvVBSswaCFf1PBNHiPx00obfhHRJfgHcnI0ffn+NMlIh9QGvG75FlaIBdKGA==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.4 + '@types/chai-subset': 1.3.3 + '@types/node': 18.16.0 + '@vitest/expect': 0.31.0 + '@vitest/runner': 0.31.0 + '@vitest/snapshot': 0.31.0 + '@vitest/spy': 0.31.0 + '@vitest/utils': 0.31.0 + acorn: 8.8.2 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.7 + concordance: 5.0.4 + debug: 4.3.4 + local-pkg: 0.4.3 + magic-string: 0.30.0 + pathe: 1.1.0 + picocolors: 1.0.0 + std-env: 3.3.3 + strip-literal: 1.0.1 + tinybench: 2.4.0 + tinypool: 0.5.0 + vite: 4.3.1(@types/node@18.16.0) + vite-node: 0.31.0(@types/node@18.16.0) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: false + /vscode-jsonrpc@6.0.0: resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} engines: {node: '>=8.0.0 || >=10.0.0'} @@ -7436,6 +7731,11 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false + /well-known-symbols@2.0.0: + resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} + engines: {node: '>=6'} + dev: false + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: @@ -7451,6 +7751,15 @@ packages: isexe: 2.0.0 dev: true + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: false + /word-wrap@1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} @@ -7546,6 +7855,11 @@ packages: engines: {node: '>=10'} dev: true + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: false + /z-schema@5.0.5: resolution: {integrity: sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==} engines: {node: '>=8.0.0'} diff --git a/public/workflows/defaultWorkflow.json b/public/workflows/defaultWorkflow.json index bd34463..7a38235 100644 --- a/public/workflows/defaultWorkflow.json +++ b/public/workflows/defaultWorkflow.json @@ -2,8 +2,8 @@ "createdBy": "ComfyBox", "version": 1, "workflow": { - "last_node_id": 465, - "last_link_id": 719, + "last_node_id": 467, + "last_link_id": 721, "nodes": [ { "id": 35, @@ -57,11 +57,11 @@ "hidden": false }, "widgets_values": [ - "worst quality" + "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, deformed, disfigured, poorly drawn face, mutation, mutated, extra limb, poorly drawn hands, missing limb, floating limbs, disconnected limbs, malformed hands, out of focus, long neck, long body, fuzzy, abstract" ], "color": "#223", "bgColor": "#335", - "comfyValue": "worst quality", + "comfyValue": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry, deformed, disfigured, poorly drawn face, mutation, mutated, extra limb, poorly drawn hands, missing limb, floating limbs, disconnected limbs, malformed hands, out of focus, long neck, long body, fuzzy, abstract", "shownOutputProperties": {}, "saveUserState": true }, @@ -299,11 +299,11 @@ "hidden": false }, "widgets_values": [ - "normal" + "karras" ], "color": "#223", "bgColor": "#335", - "comfyValue": "normal", + "comfyValue": "karras", "shownOutputProperties": {}, "saveUserState": true }, @@ -464,11 +464,11 @@ "hidden": false }, "widgets_values": [ - "euler" + "dpmpp_2s_ancestral" ], "color": "#223", "bgColor": "#335", - "comfyValue": "euler", + "comfyValue": "dpmpp_2s_ancestral", "shownOutputProperties": {}, "saveUserState": true }, @@ -548,11 +548,11 @@ "hidden": true }, "widgets_values": [ - "0.650" + "0.630" ], "color": "#223", "bgColor": "#335", - "comfyValue": 0.65, + "comfyValue": 0.63, "shownOutputProperties": { "min": { "type": "number", @@ -687,14 +687,14 @@ "flags": { "collapsed": true }, - "order": 245, + "order": 247, "mode": 0, "inputs": [ { "name": "value", "type": 0, "link": 87, - "label": "5.000" + "label": "1.000" } ], "outputs": [], @@ -816,11 +816,11 @@ "multiline": false }, "widgets_values": [ - "none" + "cached" ], "color": "#223", "bgColor": "#335", - "comfyValue": "none", + "comfyValue": "cached", "shownOutputProperties": {}, "saveUserState": true }, @@ -971,7 +971,7 @@ 66 ], "flags": {}, - "order": 244, + "order": 246, "mode": 0, "inputs": [ { @@ -1275,11 +1275,11 @@ "multiline": false }, "widgets_values": [ - "2" + "1536" ], "color": "#223", "bgColor": "#335", - "comfyValue": "2", + "comfyValue": "1536", "shownOutputProperties": {}, "saveUserState": true }, @@ -1370,11 +1370,11 @@ "multiline": false }, "widgets_values": [ - "2" + "1024" ], "color": "#223", "bgColor": "#335", - "comfyValue": "2", + "comfyValue": "1024", "shownOutputProperties": {}, "saveUserState": true }, @@ -2196,7 +2196,7 @@ 106 ], "flags": {}, - "order": 250, + "order": 252, "mode": 0, "inputs": [ { @@ -2274,7 +2274,7 @@ 46 ], "flags": {}, - "order": 251, + "order": 253, "mode": 0, "inputs": [ { @@ -2930,7 +2930,7 @@ 126 ], "flags": {}, - "order": 243, + "order": 245, "mode": 0, "inputs": [ { @@ -2994,7 +2994,7 @@ "title": "Comfy.ValueControl", "properties": { "tags": [], - "value": 149773913879886, + "value": 485829053103511, "action": "randomize", "min": 0, "max": 18446744073709552000, @@ -3091,11 +3091,11 @@ "hidden": false }, "widgets_values": [ - "149773913879886.000" + "485829053103511.000" ], "color": "#223", "bgColor": "#335", - "comfyValue": 149773913879886, + "comfyValue": 485829053103511, "shownOutputProperties": { "min": { "type": "number", @@ -3203,7 +3203,7 @@ "outputs": [ { "name": "", - "type": "number", + "type": "*", "links": [ 299 ], @@ -3851,11 +3851,11 @@ "hidden": false }, "widgets_values": [ - "512.000" + "768.000" ], "color": "#223", "bgColor": "#335", - "comfyValue": 512, + "comfyValue": 768, "shownOutputProperties": { "min": { "type": "number", @@ -4161,7 +4161,7 @@ "title": "Operation", "properties": { "A": 2, - "B": 1, + "B": 768, "OP": "*", "tags": [] } @@ -4207,7 +4207,7 @@ "title": "Operation", "properties": { "A": 2, - "B": 1, + "B": 512, "OP": "*", "tags": [] } @@ -4233,7 +4233,7 @@ "name": "value", "type": 0, "link": 184, - "label": "null" + "label": "22.000" } ], "outputs": [], @@ -4697,7 +4697,7 @@ "outputs": [ { "name": "", - "type": "number", + "type": "*", "links": [ 353 ], @@ -4747,7 +4747,7 @@ "outputs": [ { "name": "", - "type": "number", + "type": "*", "links": [ 354 ], @@ -6044,7 +6044,7 @@ "flags": { "collapsed": true }, - "order": 246, + "order": 248, "mode": 0, "inputs": [ { @@ -6615,7 +6615,7 @@ "flags": { "collapsed": true }, - "order": 248, + "order": 250, "mode": 2, "inputs": [ { @@ -6719,7 +6719,7 @@ "flags": { "collapsed": true }, - "order": 249, + "order": 251, "mode": 2, "inputs": [ { @@ -10087,11 +10087,11 @@ "hidden": false }, "widgets_values": [ - "a fluffy corgi wearing sunglasses" + "masterpiece, hyper detailed background, 1girl, solo a fluffy corgi girl wearing sunglasses, dark theme, baggy nylon jacket, gyaru, cyberpunk, airy, futuristic city, animal ear fluff, animal ears, smile, v, trees, leaves, nature, forest, overgrowth" ], "color": "#223", "bgColor": "#335", - "comfyValue": "a fluffy corgi wearing sunglasses", + "comfyValue": "masterpiece, hyper detailed background, 1girl, solo a fluffy corgi girl wearing sunglasses, dark theme, baggy nylon jacket, gyaru, cyberpunk, airy, futuristic city, animal ear fluff, animal ears, smile, v, trees, leaves, nature, forest, overgrowth", "shownOutputProperties": {}, "saveUserState": true }, @@ -11554,7 +11554,7 @@ "properties": { "tags": [], "defaultValue": null, - "index": null, + "index": 4, "updateMode": "append", "values": [] }, @@ -12163,89 +12163,6 @@ }, "saveUserState": true }, - { - "id": 104, - "type": "ui/gallery", - "pos": [ - 862.248693361997, - 640.4778575549986 - ], - "size": [ - 210, - 166 - ], - "flags": {}, - "order": 122, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "OUTPUT", - "link": null - }, - { - "name": "store", - "type": -1, - "link": 243, - "shape": 1 - }, - { - "name": "clear", - "type": -1, - "link": 657, - "shape": 1 - } - ], - "outputs": [ - { - "name": "selected_index", - "type": "number", - "links": [ - 168, - 184 - ], - "slot_index": 0 - }, - { - "name": "width", - "type": "number", - "links": [ - 356 - ], - "slot_index": 1 - }, - { - "name": "height", - "type": "number", - "links": [ - 357 - ], - "slot_index": 2 - }, - { - "name": "filename", - "type": "string", - "links": [ - 670 - ], - "slot_index": 3 - } - ], - "title": "UI.Gallery", - "properties": { - "tags": [], - "defaultValue": null, - "index": null, - "updateMode": "append", - "values": [] - }, - "widgets_values": [], - "color": "#223", - "bgColor": "#335", - "comfyValue": [], - "shownOutputProperties": {}, - "saveUserState": false - }, { "id": 116, "type": "image/cache", @@ -12935,82 +12852,6 @@ }, "saveUserState": true }, - { - "id": 418, - "type": "ui/image_upload", - "pos": [ - -555, - 1286 - ], - "size": [ - 210, - 138 - ], - "flags": {}, - "order": 225, - "mode": 2, - "inputs": [ - { - "name": "store", - "type": -1, - "link": 699, - "shape": 1, - "slot_index": 0 - } - ], - "outputs": [ - { - "name": "filename", - "type": "string", - "links": [ - 627, - 628 - ], - "slot_index": 0 - }, - { - "name": "width", - "type": "number", - "links": [ - 630 - ], - "slot_index": 1 - }, - { - "name": "height", - "type": "number", - "links": [ - 629 - ], - "slot_index": 2 - }, - { - "name": "image_count", - "type": "number", - "links": null - }, - { - "name": "changed", - "type": -2, - "links": null, - "shape": 1 - } - ], - "title": "UI.ImageUpload", - "properties": { - "defaultValue": null, - "tags": [ - "i2i" - ], - "fileCount": "single" - }, - "widgets_values": [], - "color": "#223", - "bgColor": "#335", - "comfyValue": [], - "shownOutputProperties": {}, - "saveUserState": false - }, { "id": 74, "type": "CheckpointLoaderSimple", @@ -13342,7 +13183,7 @@ 106 ], "flags": {}, - "order": 247, + "order": 249, "mode": 2, "inputs": [ { @@ -14081,6 +13922,234 @@ "color": "#432", "bgColor": "#653", "saveUserState": true + }, + { + "id": 418, + "type": "ui/image_upload", + "pos": [ + -555, + 1286 + ], + "size": [ + 210, + 138 + ], + "flags": {}, + "order": 225, + "mode": 2, + "inputs": [ + { + "name": "store", + "type": -1, + "link": 699, + "shape": 1, + "slot_index": 0 + } + ], + "outputs": [ + { + "name": "filename", + "type": "string", + "links": [ + 627, + 628, + 720 + ], + "slot_index": 0 + }, + { + "name": "width", + "type": "number", + "links": [ + 630 + ], + "slot_index": 1 + }, + { + "name": "height", + "type": "number", + "links": [ + 629 + ], + "slot_index": 2 + }, + { + "name": "image_count", + "type": "number", + "links": null + }, + { + "name": "changed", + "type": -2, + "links": null, + "shape": 1 + } + ], + "title": "UI.ImageUpload", + "properties": { + "defaultValue": null, + "tags": [ + "i2i" + ], + "fileCount": "single" + }, + "widgets_values": [], + "color": "#223", + "bgColor": "#335", + "comfyValue": [], + "shownOutputProperties": {}, + "saveUserState": false + }, + { + "id": 466, + "type": "actions/set_prompt_thumbnails", + "pos": [ + -328, + 1334 + ], + "size": [ + 260.4, + 26 + ], + "flags": { + "collapsed": true + }, + "order": 243, + "mode": 2, + "inputs": [ + { + "name": "filenames", + "type": "*", + "link": 720 + } + ], + "outputs": [], + "title": "Comfy.SetPromptThumbnailsAction", + "properties": { + "tags": [ + "txt2img", + "i2i" + ], + "defaultFolderType": "input" + }, + "saveUserState": true + }, + { + "id": 104, + "type": "ui/gallery", + "pos": [ + 862.248693361997, + 640.4778575549986 + ], + "size": [ + 210, + 166 + ], + "flags": {}, + "order": 122, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "OUTPUT", + "link": null + }, + { + "name": "store", + "type": -1, + "link": 243, + "shape": 1 + }, + { + "name": "clear", + "type": -1, + "link": 657, + "shape": 1 + } + ], + "outputs": [ + { + "name": "selected_index", + "type": "number", + "links": [ + 168, + 184 + ], + "slot_index": 0 + }, + { + "name": "width", + "type": "number", + "links": [ + 356 + ], + "slot_index": 1 + }, + { + "name": "height", + "type": "number", + "links": [ + 357 + ], + "slot_index": 2 + }, + { + "name": "filename", + "type": "string", + "links": [ + 670, + 721 + ], + "slot_index": 3 + } + ], + "title": "UI.Gallery", + "properties": { + "tags": [], + "defaultValue": null, + "index": 22, + "updateMode": "append", + "values": [] + }, + "widgets_values": [], + "color": "#223", + "bgColor": "#335", + "comfyValue": [], + "shownOutputProperties": {}, + "saveUserState": false + }, + { + "id": 467, + "type": "actions/set_prompt_thumbnails", + "pos": [ + 1116, + 772 + ], + "size": [ + 260.4, + 26 + ], + "flags": { + "collapsed": true + }, + "order": 244, + "mode": 0, + "inputs": [ + { + "name": "filenames", + "type": "*", + "link": 721 + } + ], + "outputs": [], + "title": "Comfy.SetPromptThumbnailsAction", + "properties": { + "tags": [ + "hr" + ], + "defaultFolderType": "input" + }, + "saveUserState": true } ], "links": [ @@ -16731,6 +16800,22 @@ 119, 0, "MODEL" + ], + [ + 720, + 418, + 0, + 466, + 0, + "*" + ], + [ + 721, + 104, + 3, + 467, + 0, + "*" ] ], "groups": [ @@ -20051,9 +20136,9 @@ }, "canvas": { "offset": [ - 269.1028161789952, - -277.9338410386712 + 55.778573178994975, + -62.68933903867331 ], - "scale": 0.5644739300537776 + "scale": 0.6209213230591556 } } \ No newline at end of file diff --git a/src/lib/ComfyGraph.ts b/src/lib/ComfyGraph.ts index d89b11e..b0eaa55 100644 --- a/src/lib/ComfyGraph.ts +++ b/src/lib/ComfyGraph.ts @@ -1,4 +1,4 @@ -import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions, LGraphCanvas } from "@litegraph-ts/core"; +import { LConnectionKind, LGraph, LGraphNode, type INodeSlot, type SlotIndex, LiteGraph, getStaticProperty, type LGraphAddNodeOptions, LGraphCanvas, type LGraphRemoveNodeOptions } from "@litegraph-ts/core"; import GraphSync from "./GraphSync"; import EventEmitter from "events"; import type TypedEmitter from "typed-emitter"; @@ -22,14 +22,8 @@ type ComfyGraphEvents = { } export default class ComfyGraph extends LGraph { - graphSync: GraphSync; eventBus: TypedEmitter = new EventEmitter() as TypedEmitter; - constructor() { - super(); - this.graphSync = new GraphSync(this) - } - override onConfigure() { console.debug("Configured"); this.eventBus.emit("configured", this); @@ -50,8 +44,7 @@ export default class ComfyGraph extends LGraph { } override onNodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) { - layoutState.nodeAdded(node) - this.graphSync.onNodeAdded(node); + layoutState.nodeAdded(node, options) // All nodes whether they come from base litegraph or ComfyBox should // have tags added to them. Can't override serialization for existing @@ -92,7 +85,7 @@ export default class ComfyGraph extends LGraph { if (get(uiState).autoAddUI) { console.warn("ADD", node.type, options) - if (!("svelteComponentType" in node) && options.addedByDeserialize == null) { + if (!("svelteComponentType" in node) && options.addedBy == null) { console.debug("[ComfyGraph] AutoAdd UI") const comfyNode = node as ComfyGraphNode; const widgetNodesAdded = [] @@ -127,9 +120,8 @@ export default class ComfyGraph extends LGraph { this.eventBus.emit("nodeAdded", node); } - override onNodeRemoved(node: LGraphNode) { - layoutState.nodeRemoved(node); - this.graphSync.onNodeRemoved(node); + override onNodeRemoved(node: LGraphNode, options: LGraphRemoveNodeOptions) { + layoutState.nodeRemoved(node, options); console.debug("Removed", node); this.eventBus.emit("nodeRemoved", node); diff --git a/src/lib/ComfyGraphCanvas.ts b/src/lib/ComfyGraphCanvas.ts index 4b014c6..72ec18f 100644 --- a/src/lib/ComfyGraphCanvas.ts +++ b/src/lib/ComfyGraphCanvas.ts @@ -1,4 +1,4 @@ -import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem } from "@litegraph-ts/core"; +import { BuiltInSlotShape, LGraph, LGraphCanvas, LGraphNode, LiteGraph, NodeMode, type MouseEventExt, type Vector2, type Vector4, TitleMode, type ContextMenuItem, type IContextMenuItem, Subgraph } from "@litegraph-ts/core"; import type ComfyApp from "./components/ComfyApp"; import queueState from "./stores/queueState"; import { get } from "svelte/store"; @@ -60,7 +60,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas { let state = get(queueState); let color = null; - if (node.id === +state.runningNodeId) { + if (node.id === +state.runningNodeID) { color = "#0f0"; // this.app can be null inside the constructor if rendering is taking place already } else if (this.app && this.app.dragOverNode && node.id === this.app.dragOverNode.id) { @@ -322,6 +322,34 @@ export default class ComfyGraphCanvas extends LGraphCanvas { } } + private convertToSubgraph(_value: IContextMenuItem, _options, mouseEvent, prevMenu, callback?: (node: LGraphNode) => void) { + if (Object.keys(this.selected_nodes).length === 0) + return + + const selected = Object.values(this.selected_nodes).filter(n => n != null); + this.selected_nodes = {} + + const subgraph = LiteGraph.createNode(Subgraph); + subgraph.buildFromNodes(selected) + + this.graph.add(subgraph) + } + + override getCanvasMenuOptions(): ContextMenuItem[] { + const options = super.getCanvasMenuOptions(); + + options.push( + { + content: "Convert to Subgraph", + has_submenu: false, + disabled: Object.keys(this.selected_nodes).length === 0, + callback: this.convertToSubgraph.bind(this) + }, + ) + + return options + } + override getNodeMenuOptions(node: LGraphNode): ContextMenuItem[] { const options = super.getNodeMenuOptions(node); diff --git a/src/lib/GraphSync.ts b/src/lib/GraphSync.ts deleted file mode 100644 index 9614594..0000000 --- a/src/lib/GraphSync.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { LGraph, LGraphNode } from "@litegraph-ts/core"; -import type ComfyApp from "./components/ComfyApp"; -import type { Unsubscriber, Writable } from "svelte/store"; -import type { ComfyWidgetNode } from "./nodes"; -import type ComfyGraph from "./ComfyGraph"; - -type WidgetSubStore = { - store: WidgetUIStateStore, - unsubscribe: Unsubscriber -} - -/* - * Responsible for watching for and synchronizing state changes from the - * frontend to the litegraph instance. - * - * The other way around is unnecessary since the nodes in ComfyBox can't be - * interacted with. If that were true the implementation would be way more - * complex since litegraph doesn't (currently) expose a global - * event-emitter-like thing for when nodes/widgets are changed. - * - * Assumptions: - * - Widgets can't be added to a node after they're created (messes up the indices in WidgetSubStore[]) - * - Widgets can't be interacted with from the graph, only from the frontend - * - Only one workflow/graph can ever be loaded into the program - */ -export default class GraphSync { - graph: LGraph; - - // nodeId -> widgetSubStore - private stores: Record = {} - - constructor(graph: ComfyGraph) { - this.graph = graph; - } - - onNodeAdded(node: LGraphNode) { - // TODO assumes only a single graph's widget state. - - if ("svelteComponentType" in node) { - this.addStore(node as ComfyWidgetNode); - } - - this.graph.setDirtyCanvas(true, true); - } - - onNodeRemoved(node: LGraphNode) { - if ("svelteComponentType" in node) { - this.removeStore(node as ComfyWidgetNode); - } - - this.graph.setDirtyCanvas(true, true); - } - - private addStore(node: ComfyWidgetNode) { - if (this.stores[node.id]) { - console.warn("[GraphSync] Stores already exist!", node.id, this.stores[node.id]) - } - - const unsub = node.value.subscribe((v) => this.onWidgetStateChanged(node, v)) - this.stores[node.id] = ({ store: node.value, unsubscribe: unsub }); - - console.debug("[GraphSync] NEWSTORE", this.stores[node.id]) - } - - private removeStore(node: ComfyWidgetNode) { - console.debug("[GraphSync] DELSTORE", this.stores[node.id]) - this.stores[node.id].unsubscribe() - delete this.stores[node.id] - } - - /* - * Fired when a single widget's value changes. - */ - private onWidgetStateChanged(node: ComfyWidgetNode, value: any) { - this.graph.setDirtyCanvas(true, true); - } -} diff --git a/src/lib/ImageViewer.ts b/src/lib/ImageViewer.ts index 66a7467..efe56f6 100644 --- a/src/lib/ImageViewer.ts +++ b/src/lib/ImageViewer.ts @@ -1,6 +1,10 @@ +import { negmod } from "./utils"; + export class ImageViewer { root: HTMLDivElement; lightboxModal: HTMLDivElement; + currentImages: string[] = [] + selectedIndex: number = -1; currentGallery: HTMLDivElement | null = null; private static _instance: ImageViewer; @@ -22,6 +26,8 @@ export class ImageViewer { // A full size 'lightbox' preview modal shown when left clicking on gallery previews closeModal() { this.lightboxModal.style.display = "none"; + this.currentImages = [] + this.selectedIndex = -1; this.currentGallery = null; } @@ -36,84 +42,75 @@ export class ImageViewer { return visibleGalleryButtons; } - static selected_gallery_button(gallery: HTMLDivElement): HTMLButtonElement | null { - var allCurrentButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small.selected'); + static selected_gallery_button(gallery: HTMLDivElement): [HTMLButtonElement | null, number] { + var allCurrentButtons = gallery.querySelectorAll('.preview > .thumbnails > .thumbnail-item.thumbnail-small'); + console.log(allCurrentButtons) var visibleCurrentButton = null; - allCurrentButtons.forEach((elem) => { - if (elem.parentElement.offsetParent) { + let index = -1; + allCurrentButtons.forEach((elem, i) => { + if (elem.parentElement.offsetParent && elem.classList.contains("selected")) { visibleCurrentButton = elem; + index = i; } }) - return visibleCurrentButton; + return [visibleCurrentButton, index]; } - showModal(event: Event) { - const source = (event.target || event.srcElement) as HTMLImageElement; - const galleryElem = source.closest("div.block") - console.debug("[ImageViewer] showModal", event, source, galleryElem); - if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) { - console.error("No buttons found on gallery element!", galleryElem) - return; - } + showModal(imageUrls: string[], index: number, galleryElem?: HTMLDivElement) { + this.currentImages = imageUrls + this.selectedIndex = index this.currentGallery = galleryElem; - this.modalImage.src = source.src - if (this.modalImage.style.display === 'none') { - this.lightboxModal.style.setProperty('background-image', 'url(' + source.src + ')'); - } + this.setModalImageSrc(imageUrls[index]) this.lightboxModal.style.display = "flex"; setTimeout(() => { this.modalImage.focus() }, 200) - - event.stopPropagation() } - static negmod(n: number, m: number) { - return ((n % m) + m) % m; + static get_gallery_urls(galleryElem: HTMLDivElement): string[] { + return ImageViewer.all_gallery_buttons(galleryElem) + .map(b => (b.children[0] as HTMLImageElement).src) } - updateOnBackgroundChange() { + refreshImages() { + if (this.currentGallery) { + this.currentImages = ImageViewer.get_gallery_urls(this.currentGallery) + let [_currentButton, index] = ImageViewer.selected_gallery_button(this.currentGallery); + this.selectedIndex = index; + } + + const selectedImageUrl = this.currentImages[this.selectedIndex]; + this.setModalImageSrc(selectedImageUrl) + } + + private setModalImageSrc(src: string, isTiling: boolean = false) { const modalImage = this.modalImage - if (modalImage && modalImage.offsetParent && this.currentGallery) { - let currentButton = ImageViewer.selected_gallery_button(this.currentGallery); - - if (currentButton?.children?.length > 0 && modalImage.src != currentButton.children[0].src) { - modalImage.src = currentButton.children[0].src; - if (modalImage.style.display === 'none') { - this.lightboxModal.style.setProperty('background-image', `url(${modalImage.src})`) - } - } + const modal = this.lightboxModal + modalImage.src = src; + if (isTiling) { + modalImage.style.display = 'none'; + modal.style.setProperty('background-image', `url(${modalImage.src})`) + } else { + modalImage.style.display = 'block'; + modal.style.setProperty('background-image', 'none') } } modalImageSwitch(offset: number) { - if (!this.currentGallery) - return + this.selectedIndex = negmod(this.selectedIndex + offset, this.currentImages.length); + const selectedImageUrl = this.currentImages[this.selectedIndex]; - var galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery); + this.setModalImageSrc(selectedImageUrl) - if (galleryButtons.length > 1) { - var currentButton = ImageViewer.selected_gallery_button(this.currentGallery); - - var result = -1 - galleryButtons.forEach((v, i) => { - if (v == currentButton) { - result = i - } - }) - - if (result != -1) { - const nextButton = galleryButtons[ImageViewer.negmod((result + offset), galleryButtons.length)] + if (this.currentGallery) { + const galleryButtons = ImageViewer.all_gallery_buttons(this.currentGallery); + const nextButton = galleryButtons[this.selectedIndex]; + if (nextButton) { nextButton.click() - const modalImage = this.modalImage; - const modal = this.lightboxModal - modalImage.src = nextButton.children[0].src; - if (modalImage.style.display === 'none') { - modal.style.setProperty('background-image', `url(${modalImage.src})`) - } - setTimeout(() => { modal.focus() }, 10) } } + + setTimeout(() => { this.lightboxModal.focus() }, 10) } modalNextImage(event) { @@ -140,7 +137,7 @@ export class ImageViewer { } } - setupImageForLightbox(e: HTMLImageElement) { + setupGalleryImageForLightbox(e: HTMLImageElement) { if (e.dataset.modded === "true") return; @@ -161,7 +158,22 @@ export class ImageViewer { const initiallyZoomed = true this.modalZoomSet(this.modalImage, initiallyZoomed) evt.preventDefault() - this.showModal(evt) + + const source = evt.target as HTMLImageElement; + + const galleryElem = source.closest("div.block") + console.debug("[ImageViewer] showModal", event, source, galleryElem); + if (!galleryElem || ImageViewer.all_gallery_buttons(galleryElem).length === 0) { + console.error("No buttons found on gallery element!", galleryElem) + return; + } + + let urls = ImageViewer.get_gallery_urls(galleryElem) + const [_currentButton, index] = ImageViewer.selected_gallery_button(galleryElem) + console.warn("Gallery!", index, urls, galleryElem) + + this.showModal(urls, index, galleryElem) + evt.stopPropagation(); }, true); } @@ -181,17 +193,8 @@ export class ImageViewer { } modalTileImageToggle(event: Event) { - const modalImage = this.modalImage - const modal = this.lightboxModal - const isTiling = modalImage.style.display === 'none'; - if (isTiling) { - modalImage.style.display = 'block'; - modal.style.setProperty('background-image', 'none') - } else { - modalImage.style.display = 'none'; - modal.style.setProperty('background-image', `url(${modalImage.src})`) - } - + const isTiling = this.modalImage.style.display === 'none'; + this.setModalImageSrc(this.modalImage.src, isTiling) event.stopPropagation() } } diff --git a/src/lib/api.ts b/src/lib/api.ts index fa7fb35..b4c4ab1 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,34 +1,92 @@ -type PromptRequestBody = { - client_id: string, - prompt: any, - extra_data: any, - front: boolean, - number: number | undefined +import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp"; +import type TypedEmitter from "typed-emitter"; +import EventEmitter from "events"; +import type { GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes"; +import type { SerializedLGraph } from "@litegraph-ts/core"; + +export type ComfyPromptRequest = { + client_id?: string, + prompt: SerializedPromptInputsAll, + extra_data: ComfyPromptExtraData, + front?: boolean, + number?: number } export type QueueItemType = "queue" | "history"; -export type ComfyAPIQueueStatus = { - exec_info: { - queue_remaining: number | "X"; - } +export type ComfyAPIStatusExecInfo = { + queueRemaining: number | "X"; } -export default class ComfyAPI extends EventTarget { - private registered: Set = new Set(); +export type ComfyAPIStatusResponse = { + execInfo?: ComfyAPIStatusExecInfo, + error?: string +} + +export type ComfyAPIQueueResponse = { + running: ComfyAPIHistoryItem[], + pending: ComfyAPIHistoryItem[], + error?: string +} + +export type NodeID = string; +export type PromptID = string; // UUID + +export type ComfyAPIHistoryItem = [ + number, // prompt number + PromptID, + SerializedPromptInputsAll, + ComfyPromptExtraData, + NodeID[] // good outputs +] + +export type ComfyAPIPromptResponse = { + promptID?: PromptID, + error?: string +} + +export type ComfyAPIHistoryEntry = { + prompt: ComfyAPIHistoryItem, + outputs: SerializedPromptOutputs +} + +export type ComfyAPIHistoryResponse = { + history: Record, + error?: string +} + +export type ComfyPromptPNGInfo = { + workflow: SerializedLGraph +} + +export type ComfyPromptExtraData = { + extra_pnginfo?: ComfyPromptPNGInfo, + client_id?: string, // UUID + subgraphs: string[], + thumbnails?: GalleryOutputEntry[] +} + +type ComfyAPIEvents = { + status: (status: ComfyAPIStatusResponse | null, error?: Error | null) => void, + progress: (progress: Progress) => void, + reconnecting: () => void, + reconnected: () => void, + executing: (promptID: PromptID | null, runningNodeID: NodeID | null) => void, + executed: (promptID: PromptID, nodeID: NodeID, output: SerializedPromptOutput) => void, + execution_cached: (promptID: PromptID, nodes: NodeID[]) => void, + execution_error: (promptID: PromptID, message: string) => void, +} + +export default class ComfyAPI { + private eventBus: TypedEmitter = new EventEmitter() as TypedEmitter; socket: WebSocket | null = null; clientId: string | null = null; hostname: string | null = null; port: number | null = 8188; - constructor() { - super(); - } - - override addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: AddEventListenerOptions | boolean) { - super.addEventListener(type, callback, options); - this.registered.add(type); + addEventListener(type: E, callback: ComfyAPIEvents[E]) { + this.eventBus.addListener(type, callback); } /** @@ -39,9 +97,9 @@ export default class ComfyAPI extends EventTarget { try { const resp = await fetch(this.getBackendUrl() + "/prompt"); const status = await resp.json(); - this.dispatchEvent(new CustomEvent("status", { detail: status })); + this.eventBus.emit("status", { execInfo: { queueRemaining: status.exec_info.queue_remaining } }); } catch (error) { - this.dispatchEvent(new CustomEvent("status", { detail: null })); + this.eventBus.emit("status", { error: error.toString() }); } }, 1000); } @@ -77,7 +135,7 @@ export default class ComfyAPI extends EventTarget { this.socket.addEventListener("open", () => { opened = true; if (isReconnect) { - this.dispatchEvent(new CustomEvent("reconnected")); + this.eventBus.emit("reconnected"); } }); @@ -94,8 +152,8 @@ export default class ComfyAPI extends EventTarget { this.createSocket(true); }, 300); if (opened) { - this.dispatchEvent(new CustomEvent("status", { detail: null })); - this.dispatchEvent(new CustomEvent("reconnecting")); + this.eventBus.emit("status", null); + this.eventBus.emit("reconnecting"); } }); @@ -108,32 +166,28 @@ export default class ComfyAPI extends EventTarget { this.clientId = msg.data.sid; sessionStorage["Comfy.SessionId"] = this.clientId; } - this.dispatchEvent(new CustomEvent("status", { detail: msg.data.status })); + this.eventBus.emit("status", { execInfo: { queueRemaining: msg.data.status.exec_info.queue_remaining } }); break; case "progress": - this.dispatchEvent(new CustomEvent("progress", { detail: msg.data })); + this.eventBus.emit("progress", msg.data as Progress); break; case "executing": - this.dispatchEvent(new CustomEvent("executing", { detail: msg.data })); + this.eventBus.emit("executing", msg.data.prompt_id, msg.data.node); break; case "executed": - this.dispatchEvent(new CustomEvent("executed", { detail: msg.data })); + this.eventBus.emit("executed", msg.data.prompt_id, msg.data.node, msg.data.output); break; case "execution_cached": - this.dispatchEvent(new CustomEvent("execution_cached", { detail: msg.data })); + this.eventBus.emit("execution_cached", msg.data.prompt_id, msg.data.nodes); break; case "execution_error": - this.dispatchEvent(new CustomEvent("execution_error", { detail: msg.data })); + this.eventBus.emit("execution_error", msg.data.prompt_id, msg.data.message); break; default: - if (this.registered.has(msg.type)) { - this.dispatchEvent(new CustomEvent(msg.type, { detail: msg.data })); - } else { - throw new Error("Unknown message type"); - } + console.warn("Unhandled message:", event.data); } } catch (error) { - console.warn("Unhandled message:", event.data); + console.error("Error handling message", event.data, error); } }); } @@ -149,27 +203,27 @@ export default class ComfyAPI extends EventTarget { * Gets a list of extension urls * @returns An array of script urls to import */ - async getExtensions() { - const resp = await fetch(this.getBackendUrl() + `/extensions`, { cache: "no-store" }); - return await resp.json(); + async getExtensions(): Promise { + return fetch(this.getBackendUrl() + `/extensions`, { cache: "no-store" }) + .then(resp => resp.json()) } /** * Gets a list of embedding names * @returns An array of script urls to import */ - async getEmbeddings() { - const resp = await fetch(this.getBackendUrl() + "/embeddings", { cache: "no-store" }); - return await resp.json(); + async getEmbeddings(): Promise { + return fetch(this.getBackendUrl() + "/embeddings", { cache: "no-store" }) + .then(resp => resp.json()) } /** * Loads node object definitions for the graph * @returns The node definitions */ - async getNodeDefs() { - const resp = await fetch(this.getBackendUrl() + "/object_info", { cache: "no-store" }); - return await resp.json(); + async getNodeDefs(): Promise { + return fetch(this.getBackendUrl() + "/object_info", { cache: "no-store" }) + .then(resp => resp.json()) } /** @@ -177,82 +231,59 @@ export default class ComfyAPI extends EventTarget { * @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue * @param {object} prompt The prompt data to queue */ - async queuePrompt(number: number, { output, workflow }) { - const body: PromptRequestBody = { - client_id: this.clientId, - prompt: output, - extra_data: { extra_pnginfo: { workflow } }, - front: false, - number: number - }; + async queuePrompt(body: ComfyPromptRequest): Promise { + body.client_id = this.clientId; - if (number === -1) { + if (body.number === -1) { body.front = true; - } else if (number != 0) { - body.number = number; } - const res = await fetch(this.getBackendUrl() + "/prompt", { + let postBody = null; + try { + postBody = JSON.stringify(body) + } + catch (error) { + return Promise.reject({ error }) + } + + return fetch(this.getBackendUrl() + "/prompt", { method: "POST", headers: { "Content-Type": "application/json", }, - body: JSON.stringify(body), - }); - - if (res.status !== 200) { - throw { - response: await res.text(), - }; - } - } - - /** - * Loads a list of items (queue or history) - * @param {string} type The type of items to load, queue or history - * @returns The items of the specified type grouped by their status - */ - async getItems(type: QueueItemType) { - if (type === "queue") { - return this.getQueue(); - } - return this.getHistory(); + body: postBody + }) + .then(res => res.json()) + .then(raw => { return { promptID: raw.prompt_id } }) + .catch(res => { throw res.text() }) + .catch(error => { return { error } }) } /** * Gets the current state of the queue * @returns The currently running and queued items */ - async getQueue() { - try { - const res = await fetch(this.getBackendUrl() + "/queue"); - const data = await res.json(); - return { - // Running action uses a different endpoint for cancelling - Running: data.queue_running.map((prompt) => ({ - prompt, - remove: { name: "Cancel", cb: () => this.interrupt() }, - })), - Pending: data.queue_pending.map((prompt) => ({ prompt })), - }; - } catch (error) { - console.error(error); - return { Running: [], Pending: [], error }; - } + async getQueue(): Promise { + return fetch(this.getBackendUrl() + "/queue") + .then(res => res.json()) + .then(data => { + return { + running: data.queue_running, + pending: data.queue_pending, + } + }) + .catch(error => { return { running: [], pending: [], error } }) } /** * Gets the prompt execution history * @returns Prompt history including node outputs */ - async getHistory() { - try { - const res = await fetch(this.getBackendUrl() + "/history"); - return { History: Object.values(await res.json()) }; - } catch (error) { - console.error(error); - return { History: [], error }; - } + async getHistory(): Promise { + return fetch(this.getBackendUrl() + "/history") + .then(res => res.json()) + .then(history => { return { history } }) + .catch(error => { return { history: {}, error } }) } /** @@ -260,18 +291,21 @@ export default class ComfyAPI extends EventTarget { * @param {*} type The endpoint to post to * @param {*} body Optional POST data */ - private async postItem(type: string, body: any) { + private async postItem(type: QueueItemType, body: any): Promise { try { - await fetch("/" + type, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: body ? JSON.stringify(body) : undefined, - }); - } catch (error) { - console.error(error); + body = body ? JSON.stringify(body) : body } + catch (error) { + return Promise.reject(error) + } + + return fetch(this.getBackendUrl() + "/" + type, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: body + }); } /** @@ -279,22 +313,22 @@ export default class ComfyAPI extends EventTarget { * @param {string} type The type of item to delete, queue or history * @param {number} id The id of the item to delete */ - async deleteItem(type: string, id: number) { - await this.postItem(type, { delete: [id] }); + async deleteItem(type: QueueItemType, id: number): Promise { + return this.postItem(type, { delete: [id] }); } /** * Clears the specified list * @param {string} type The type of list to clear, queue or history */ - async clearItems(type: string) { - await this.postItem(type, { clear: true }); + async clearItems(type: QueueItemType): Promise { + return this.postItem(type, { clear: true }); } /** * Interrupts the execution of the running prompt */ - async interrupt() { - await this.postItem("interrupt", null); + async interrupt(): Promise { + return fetch(this.getBackendUrl() + "/interrupt", { method: "POST" }); } } diff --git a/src/lib/components/AccordionContainer.svelte b/src/lib/components/AccordionContainer.svelte index 6b99cc2..978da9e 100644 --- a/src/lib/components/AccordionContainer.svelte +++ b/src/lib/components/AccordionContainer.svelte @@ -179,8 +179,8 @@ } } - :global(.label-wrap > span:not(.icon)) { - /* color: var(--block-title-text-color); */ + :global(.label-wrap > span) { + color: var(--block-title-text-color); font-size: 16px; } diff --git a/src/lib/components/ComfyApp.svelte b/src/lib/components/ComfyApp.svelte index 589efe1..68b2a9a 100644 --- a/src/lib/components/ComfyApp.svelte +++ b/src/lib/components/ComfyApp.svelte @@ -31,7 +31,7 @@ let containerElem: HTMLDivElement; let resizeTimeout: NodeJS.Timeout | null; let hasShownUIHelpToast: boolean = false; - let uiTheme: string = ""; + let uiTheme: string = "gradio-dark"; let fileInput: HTMLInputElement = undefined; let debugLayout: boolean = false; @@ -78,7 +78,7 @@ } } - let propsSidebarSize = 15; //15; + let propsSidebarSize = 0; function toggleProps() { if (propsSidebarSize == 0) { @@ -90,11 +90,11 @@ } } - let queueSidebarSize = 15; + let queueSidebarSize = 20; function toggleQueue() { if (queueSidebarSize == 0) { - queueSidebarSize = 15; + queueSidebarSize = 20; app.resizeCanvas(); } else { @@ -182,6 +182,13 @@ async function doRefreshCombos() { await app.refreshComboInNodes(true) } + + $: if (uiTheme === "gradio-dark") { + document.getElementById("app").classList.add("dark") + } + else { + document.getElementById("app").classList.remove("dark") + } @@ -190,7 +197,7 @@ {/if} -
+
@@ -264,7 +271,8 @@ Theme @@ -276,12 +284,13 @@
- diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index 181bf98..0cf54e2 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -1,159 +1,527 @@ -
-
- {#each _entries as entry} -
- thumbnail -
- {entry.name} -
-
- {/each} + + +
+

Prompt Details

-
- {#if $queueState.runningNodeId || $queueState.progress} -
- Node: {getNodeInfo($queueState.runningNodeId)} -
-
- + {#if selectedPrompt} + + {/if} + + +
+
+ {#if _entries.length > 0} + {#each _entries as entry} +
showPrompt(entry, e)}> + {#if entry.images.length > 0} +
+ {#each entry.images.slice(0, 4) as image, i} +
+ showLightbox(entry, i, e)} + src={image} + alt="thumbnail" /> +
+ {/each} +
+ {:else} + + {/if} +
+
+ {entry.message} +
+
+ {entry.submessage} +
+
+
+
+ {#if entry.date != null} + + {entry.date} + + {/if} +
+ {/each} + {:else} +
+
+
+ +
+
+ (No entries) +
+
{/if} - {#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0} -
-
- Queued prompts: {$queueState.queueRemaining}. +
+
+
switchMode("queue")} + class:mode-selected={mode === "queue"}> + Queue +
+
switchMode("history")} + class:mode-selected={mode === "history"}> + History +
+
+
+
+ {#if inProgress} + +
+ Queued prompts: {$queueState.queueRemaining}
-
- {:else} -
+ {:else}
Nothing queued.
+ {/if} +
+ {#if queued} +
+ Node: {getNodeInfo($queueState.runningNodeID)} +
+
+ +
+
+
{/if}
diff --git a/src/lib/components/LightboxModal.svelte b/src/lib/components/LightboxModal.svelte index 2c490b3..167999e 100644 --- a/src/lib/components/LightboxModal.svelte +++ b/src/lib/components/LightboxModal.svelte @@ -95,12 +95,12 @@ position: absolute; top: 50%; width: auto; - padding: 16px; + padding: 60px; margin-top: -50px; color: white; font-weight: bold; - font-size: 20px; - transition: 0.6s ease; + font-size: 40px; + transition: 0.3s ease; border-radius: 0 3px 3px 0; user-select: none; -webkit-user-select: none; @@ -113,6 +113,6 @@ .modalPrev:hover, .modalNext:hover { - background-color: rgba(0, 0, 0, 0.8); + background-color: rgba(180, 180, 180, 0.8); } diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte new file mode 100644 index 0000000..856882e --- /dev/null +++ b/src/lib/components/Modal.svelte @@ -0,0 +1,61 @@ + + + + (showModal = false)} + on:click|self={() => dialog.close()} +> +
+ + + + +
+
+ + diff --git a/src/lib/components/ProgressBar.svelte b/src/lib/components/ProgressBar.svelte index 61d13bf..7b5682f 100644 --- a/src/lib/components/ProgressBar.svelte +++ b/src/lib/components/ProgressBar.svelte @@ -23,17 +23,17 @@ diff --git a/src/lib/components/Spinner.svelte b/src/lib/components/Spinner.svelte new file mode 100644 index 0000000..8174f2a --- /dev/null +++ b/src/lib/components/Spinner.svelte @@ -0,0 +1,24 @@ + + + diff --git a/src/lib/components/WidgetContainer.svelte b/src/lib/components/WidgetContainer.svelte index 58d784e..cc13603 100644 --- a/src/lib/components/WidgetContainer.svelte +++ b/src/lib/components/WidgetContainer.svelte @@ -50,7 +50,7 @@ $: if ($queueState && widget && widget.node) { - dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id; + dragItem.isNodeExecuting = $queueState.runningNodeID === widget.node.id; } function getWidgetClass() { @@ -72,7 +72,7 @@
diff --git a/src/lib/components/gradio/app/Accordion.svelte b/src/lib/components/gradio/app/Accordion.svelte index a26f663..1b958f7 100644 --- a/src/lib/components/gradio/app/Accordion.svelte +++ b/src/lib/components/gradio/app/Accordion.svelte @@ -13,7 +13,7 @@ } -
+
{label} â–¼ diff --git a/src/lib/nodes/ComfyActionNodes.ts b/src/lib/nodes/ComfyActionNodes.ts index a4489ff..ca6533c 100644 --- a/src/lib/nodes/ComfyActionNodes.ts +++ b/src/lib/nodes/ComfyActionNodes.ts @@ -7,6 +7,7 @@ import { get } from "svelte/store"; import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode"; import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes"; import type { NotifyOptions } from "$lib/notify"; +import type { FileData as GradioFileData } from "@gradio/upload"; import { convertComfyOutputToGradio, uploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils"; export class ComfyQueueEvents extends ComfyGraphNode { @@ -659,3 +660,54 @@ LiteGraph.registerNodeType({ desc: "Uploads an image from the specified ComfyUI folder into its input folder", type: "actions/store_images" }) + +export interface ComfySetPromptThumbnailsActionProperties extends ComfyGraphNodeProperties { + defaultFolderType: string | null +} + +export class ComfySetPromptThumbnailsAction extends ComfyGraphNode { + override properties: ComfySetPromptThumbnailsActionProperties = { + tags: [], + defaultFolderType: "input", + } + + static slotLayout: SlotLayout = { + inputs: [ + { name: "filenames", type: "*" }, + ] + } + + _value: any = null; + + override getPromptThumbnails(): GalleryOutputEntry[] | null { + const data = this.getInputData(0) + + const folderType = this.properties.folderType || "input"; + + const convertString = (s: string): GalleryOutputEntry => { + return { filename: data, subfolder: "", type: folderType } + } + + if (typeof data === "string") { + return [convertString(data)] + } + else if (data != null && typeof data === "object") { + if ("filename" in data && "type" in data) + return [data as GalleryOutputEntry]; + } + else if (Array.isArray(data) && data.length > 0) { + if (typeof data[0] === "string") + return data.map(convertString) + else if (typeof data[0] === "object" && "filename" in data[0] && "type" in data[0]) + return data as GalleryOutputEntry[] + } + return null; + } +} + +LiteGraph.registerNodeType({ + class: ComfySetPromptThumbnailsAction, + title: "Comfy.SetPromptThumbnailsAction", + desc: "When a subgraph containing this node is executed, sets the thumbnails in the queue sidebar to the input filename(s).", + type: "actions/set_prompt_thumbnails" +}) diff --git a/src/lib/nodes/ComfyBackendNode.ts b/src/lib/nodes/ComfyBackendNode.ts index 4c1e3c3..c6039e6 100644 --- a/src/lib/nodes/ComfyBackendNode.ts +++ b/src/lib/nodes/ComfyBackendNode.ts @@ -1,7 +1,7 @@ import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas"; import ComfyGraphNode from "./ComfyGraphNode"; import ComfyWidgets from "$lib/widgets" -import type { ComfyWidgetNode } from "./ComfyWidgetNodes"; +import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes"; import { BuiltInSlotType, type SerializedLGraphNode } from "@litegraph-ts/core"; import type IComfyInputSlot from "$lib/IComfyInputSlot"; import type { ComfyInputConfig } from "$lib/IComfyInputSlot"; @@ -110,7 +110,7 @@ export class ComfyBackendNode extends ComfyGraphNode { } } - override onExecuted(outputData: any) { + override onExecuted(outputData: GalleryOutput) { console.warn("onExecuted outputs", outputData) this.triggerSlot(0, outputData) } diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 9d8924b..b91c0ac 100644 --- a/src/lib/nodes/ComfyGraphNode.ts +++ b/src/lib/nodes/ComfyGraphNode.ts @@ -3,7 +3,7 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp"; import type ComfyWidget from "$lib/components/widgets/ComfyWidget"; import { LGraph, LGraphNode, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SerializedLGraphNode, type Vector2, type INodeOutputSlot, LConnectionKind, type SlotType, LGraphCanvas, getStaticPropertyOnInstance, type PropertyLayout, type SlotLayout } from "@litegraph-ts/core"; import type { SvelteComponentDev } from "svelte/internal"; -import type { ComfyWidgetNode } from "./ComfyWidgetNodes"; +import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes"; import type IComfyInputSlot from "$lib/IComfyInputSlot"; import uiState from "$lib/stores/uiState"; import { get } from "svelte/store"; @@ -48,7 +48,14 @@ export default class ComfyGraphNode extends LGraphNode { * Triggered when the backend sends a finished output back with this node's ID. * Valid for output nodes like SaveImage and PreviewImage. */ - onExecuted?(output: any): void; + onExecuted?(output: GalleryOutput): void; + + /* + * When a prompt is queued, this will be called on the node if it can + * provide any thumbnails for use with the prompt queue. Useful for HR Fix + * or img2img workloads. + */ + getPromptThumbnails?(): GalleryOutputEntry[] | null /* * Allows you to manually specify an auto-config for certain input slot diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts index b64928e..e3e9667 100644 --- a/src/lib/nodes/ComfyWidgetNodes.ts +++ b/src/lib/nodes/ComfyWidgetNodes.ts @@ -161,7 +161,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { } private triggerChangeEvent(value: any) { - console.debug("[Widget] trigger changed", this, value) + // console.debug("[Widget] trigger changed", this, value) const changedOutput = this.outputs[this.changedIndex] if (changedOutput.type === BuiltInSlotType.EVENT) this.triggerSlot(this.changedIndex, value) @@ -271,7 +271,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { } } - console.debug("Property copy", input, this.properties) + // console.debug("Property copy", input, this.properties) this.setValue(get(this.value)) @@ -288,7 +288,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode { if (layoutEntry && layoutEntry.parent) { layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1) } - console.debug("propsChanged", this) + // console.debug("propsChanged", this) this.propsChanged.set(get(this.propsChanged) + 1) } diff --git a/src/lib/notify.ts b/src/lib/notify.ts index 531c0ba..7808ef7 100644 --- a/src/lib/notify.ts +++ b/src/lib/notify.ts @@ -45,6 +45,11 @@ function notifyToast(text: string, options: NotifyOptions) { '--toastBackground': 'var(--color-blue-500)', } } + else if (options.type === "warning") { + toastOptions.theme = { + '--toastBackground': 'var(--color-yellow-500)', + } + } else if (options.type === "error") { toastOptions.theme = { '--toastBackground': 'var(--color-red-500)', diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts index a74b18b..19e79c9 100644 --- a/src/lib/stores/layoutState.ts +++ b/src/lib/stores/layoutState.ts @@ -1,7 +1,7 @@ import { get, writable } from 'svelte/store'; import type { Readable, Writable } from 'svelte/store'; import type ComfyApp from "$lib/components/ComfyApp" -import { type LGraphNode, type IWidget, type LGraph, NodeMode } from "@litegraph-ts/core" +import { type LGraphNode, type IWidget, type LGraph, NodeMode, type LGraphRemoveNodeOptions, type LGraphAddNodeOptions } from "@litegraph-ts/core" import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action'; import type { ComfyWidgetNode } from '$lib/nodes'; @@ -79,7 +79,7 @@ export type LayoutState = { * If true, a saved workflow is being deserialized, so ignore any * nodeAdded/nodeRemoved events. * - * TODO: instead use LGraphAddNodeOptions.addedByDeserialize + * TODO: instead use LGraphAddNodeOptions.addedBy */ isConfiguring: boolean, @@ -649,8 +649,8 @@ type LayoutStateOps = { addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial, index?: number) => WidgetLayout, findDefaultContainerForInsertion: () => ContainerLayout | null, updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[], - nodeAdded: (node: LGraphNode) => void, - nodeRemoved: (node: LGraphNode) => void, + nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void, + nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void, groupItems: (dragItems: IDragItem[], attrs?: Partial) => ContainerLayout, ungroup: (container: ContainerLayout) => void, getCurrentSelection: () => IDragItem[], @@ -757,19 +757,6 @@ function updateChildren(parent: IDragItem, newChildren?: IDragItem[]): IDragItem return state.allItems[parent.id].children } -function nodeAdded(node: LGraphNode) { - const state = get(store) - if (state.isConfiguring) - return; - - const parent = findDefaultContainerForInsertion(); - - console.debug("[layoutState] nodeAdded", node) - if ("svelteComponentType" in node) { - addWidget(parent, node as ComfyWidgetNode); - } -} - function removeEntry(state: LayoutState, id: DragItemID) { const entry = state.allItems[id] if (entry.children && entry.children.length > 0) { @@ -788,7 +775,34 @@ function removeEntry(state: LayoutState, id: DragItemID) { delete state.allItems[id] } -function nodeRemoved(node: LGraphNode) { +function nodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) { + const state = get(store) + if (state.isConfiguring) + return; + + if (options.addedBy === "moveIntoSubgraph" || options.addedBy === "moveOutOfSubgraph") { + // All we need to do is update the nodeID linked to this node. + const item = state.allItemsByNode[options.prevNodeId] + delete state.allItemsByNode[options.prevNodeId] + state.allItemsByNode[node.id] = item + return; + } + + const parent = findDefaultContainerForInsertion(); + + console.debug("[layoutState] nodeAdded", node) + if ("svelteComponentType" in node) { + addWidget(parent, node as ComfyWidgetNode); + } +} + +function nodeRemoved(node: LGraphNode, options: LGraphRemoveNodeOptions) { + if (options.removedBy === "moveIntoSubgraph" || options.removedBy === "moveOutOfSubgraph") { + // This node is being moved into a subgraph, so it will be readded under + // a new node ID shortly. + return + } + const state = get(store) console.debug("[layoutState] nodeRemoved", node) diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index 8f3076f..13ee1a1 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -1,54 +1,287 @@ -import type { ComfyAPIQueueStatus } from "$lib/api"; -import type { Progress } from "$lib/components/ComfyApp"; -import { writable, type Writable } from "svelte/store"; +import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyPromptExtraData, NodeID, PromptID } from "$lib/api"; +import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp"; +import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes"; +import notify from "$lib/notify"; +import { get, writable, type Writable } from "svelte/store"; export type QueueItem = { name: string } +export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown"; + type QueueStateOps = { - statusUpdated: (status: ComfyAPIQueueStatus | null) => void, - executingUpdated: (runningNodeId: string | null) => void, - progressUpdated: (progress: Progress | null) => void + queueUpdated: (resp: ComfyAPIQueueResponse) => void, + historyUpdated: (resp: ComfyAPIHistoryResponse) => void, + statusUpdated: (status: ComfyAPIStatusResponse | null) => void, + executingUpdated: (promptID: PromptID | null, runningNodeID: NodeID | null) => void, + executionCached: (promptID: PromptID, nodes: NodeID[]) => void, + executionError: (promptID: PromptID, message: string) => void, + progressUpdated: (progress: Progress) => void + afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void + onExecuted: (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => void +} + +export type QueueEntry = { + /* Data preserved on page refresh */ + number: number, + queuedAt?: Date, + finishedAt?: Date, + promptID: PromptID, + prompt: SerializedPromptInputsAll, + extraData: ComfyPromptExtraData, + goodOutputs: NodeID[], + + /* Data not sent by Comfy's API, lost on page refresh */ + /* Prompt outputs, collected while the prompt is still executing */ + outputs: SerializedPromptOutputs, + + /* Nodes in of the workflow that have finished running so far. */ + nodesRan: Set, + cachedNodes: Set +} + +export type CompletedQueueEntry = { + entry: QueueEntry, + status: QueueEntryStatus, + message?: string, + error?: string, } export type QueueState = { + queueRunning: Writable, + queuePending: Writable, + queueCompleted: Writable, queueRemaining: number | "X" | null; - runningNodeId: number | null; - progress: Progress | null + runningNodeID: number | null; + progress: Progress | null, + isInterrupting: boolean } type WritableQueueStateStore = Writable & QueueStateOps; -const store: Writable = writable({ queueRemaining: null, runningNodeId: null, progress: null }) +const store: Writable = writable({ + queueRunning: writable([]), + queuePending: writable([]), + queueCompleted: writable([]), + queueRemaining: null, + runningNodeID: null, + progress: null, + isInterrupting: false +}) -function statusUpdated(status: ComfyAPIQueueStatus | null) { +function toQueueEntry(resp: ComfyAPIHistoryItem): QueueEntry { + const [num, promptID, prompt, extraData, goodOutputs] = resp + return { + number: num, + queuedAt: null, // TODO when ComfyUI passes the date + finishedAt: null, + promptID, + prompt, + extraData, + goodOutputs, + outputs: {}, + nodesRan: new Set(), // TODO can ComfyUI send this too? + cachedNodes: new Set() + } +} + +function toCompletedQueueEntry(resp: ComfyAPIHistoryEntry): CompletedQueueEntry { + const entry = toQueueEntry(resp.prompt) + entry.outputs = resp.outputs; + return { + entry, + status: Object.values(entry.outputs).length > 0 ? "success" : "all_cached", + error: null + } +} + +function queueUpdated(resp: ComfyAPIQueueResponse) { + console.debug("[queueState] queueUpdated", resp.running.length, resp.pending.length) store.update((s) => { - if (status !== null) - s.queueRemaining = status.exec_info.queue_remaining; + s.queueRunning.set(resp.running.map(toQueueEntry)); + s.queuePending.set(resp.pending.map(toQueueEntry)); + s.queueRemaining = resp.pending.length; return s }) } -function executingUpdated(runningNodeId: string | null) { +function historyUpdated(resp: ComfyAPIHistoryResponse) { + console.debug("[queueState] historyUpdated", Object.values(resp.history).length) store.update((s) => { - s.progress = null; - s.runningNodeId = parseInt(runningNodeId); + const values = Object.values(resp.history) // TODO Order by prompt finished date! + s.queueCompleted.set(values.map(toCompletedQueueEntry)); return s }) } -function progressUpdated(progress: Progress | null) { +function progressUpdated(progress: Progress) { + // console.debug("[queueState] progressUpdated", progress) store.update((s) => { s.progress = progress; return s }) } +function statusUpdated(status: ComfyAPIStatusResponse | null) { + console.debug("[queueState] statusUpdated", status) + store.update((s) => { + if (status !== null) + s.queueRemaining = status.execInfo.queueRemaining; + return s + }) +} + +function findEntryInPending(promptID: PromptID): [number, QueueEntry | null, Writable | null] { + const state = get(store); + let index = get(state.queuePending).findIndex(e => e.promptID === promptID) + if (index !== -1) + return [index, get(state.queuePending)[index], state.queuePending] + + index = get(state.queueRunning).findIndex(e => e.promptID === promptID) + if (index !== -1) + return [index, get(state.queueRunning)[index], state.queueRunning] + + return [-1, null, null] +} + +function moveToCompleted(index: number, queue: Writable, status: QueueEntryStatus, message?: string, error?: string) { + const state = get(store) + + const entry = get(queue)[index]; + console.debug("[queueState] Move to completed", entry.promptID, index, status, message, error) + entry.finishedAt = new Date() // Now + queue.update(qp => { qp.splice(index, 1); return qp }); + state.queueCompleted.update(qc => { + const completed: CompletedQueueEntry = { entry, status, message, error } + qc.push(completed) + return qc + }) + + store.set(state) +} + +function executingUpdated(promptID: PromptID, runningNodeID: NodeID | null) { + console.debug("[queueState] executingUpdated", promptID, runningNodeID) + store.update((s) => { + s.progress = null; + + const [index, entry, queue] = findEntryInPending(promptID); + if (runningNodeID != null) { + if (entry != null) { + entry.nodesRan.add(runningNodeID) + } + s.runningNodeID = parseInt(runningNodeID); + } + else { + // Prompt finished executing. + if (entry != null) { + const totalNodesInPrompt = Object.keys(entry.prompt).length + if (entry.cachedNodes.size >= Object.keys(entry.prompt).length) { + notify("Prompt was cached, nothing to run.", { type: "warning" }) + moveToCompleted(index, queue, "all_cached", "(Execution was cached)"); + } + else if (entry.nodesRan.size >= totalNodesInPrompt) { + moveToCompleted(index, queue, "success") + } + else { + notify("Interrupted prompt.") + moveToCompleted(index, queue, "interrupted", `Interrupted after ${entry.nodesRan.size}/${totalNodesInPrompt} nodes`) + } + } + else { + console.debug("[queueState] Could not find in pending! (executingUpdated)", promptID) + } + s.progress = null; + s.runningNodeID = null; + } + return s + }) +} + +function executionCached(promptID: PromptID, nodes: NodeID[]) { + console.debug("[queueState] executionCached", promptID, nodes) + store.update(s => { + const [index, entry, queue] = findEntryInPending(promptID); + if (entry != null) { + for (const nodeID of nodes) { + entry.nodesRan.add(nodeID); + entry.cachedNodes.add(nodeID); + } + } + else { + console.error("[queueState] Could not find in pending! (executionCached)", promptID, "pending", JSON.stringify(get(get(store).queuePending).map(p => p.promptID)), "running", JSON.stringify(get(get(store).queueRunning).map(p => p.promptID))) + } + s.isInterrupting = false; // TODO move to start + s.progress = null; + s.runningNodeID = null; + return s + }) +} + +function executionError(promptID: PromptID, message: string) { + console.debug("[queueState] executionError", promptID, message) + store.update(s => { + const [index, entry, queue] = findEntryInPending(promptID); + if (entry != null) { + moveToCompleted(index, queue, "error", "Error executing", message) + } + else { + console.error("[queueState] Could not find in pending! (executionError)", promptID) + } + s.progress = null; + s.runningNodeID = null; + return s + }) +} + +function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) { + console.debug("[queueState] afterQueued", promptID, Object.keys(prompt)) + store.update(s => { + const entry: QueueEntry = { + number, + queuedAt: new Date(), // Now + finishedAt: null, + promptID, + prompt, + extraData, + goodOutputs: [], + outputs: {}, + nodesRan: new Set(), + cachedNodes: new Set() + } + s.queuePending.update(qp => { qp.push(entry); return qp }) + console.debug("[queueState] ADD PROMPT", promptID) + s.isInterrupting = false; + return s + }) +} + +function onExecuted(promptID: PromptID, nodeID: NodeID, output: GalleryOutput) { + console.debug("[queueState] onExecuted", promptID, nodeID, output) + store.update(s => { + const [index, entry, queue] = findEntryInPending(promptID) + if (entry != null) { + entry.outputs[nodeID] = output; + queue.set(get(queue)) + } + else { + console.error("[queueState] Could not find in pending! (onExecuted)", promptID) + } + return s + }) +} + const queueStateStore: WritableQueueStateStore = { ...store, + queueUpdated, + historyUpdated, statusUpdated, + progressUpdated, executingUpdated, - progressUpdated + executionCached, + executionError, + afterQueued, + onExecuted } export default queueStateStore; diff --git a/src/lib/stores/uiState.ts b/src/lib/stores/uiState.ts index 332fb10..c827741 100644 --- a/src/lib/stores/uiState.ts +++ b/src/lib/stores/uiState.ts @@ -10,11 +10,17 @@ export type UIState = { uiUnlocked: boolean, uiEditMode: UIEditMode, + reconnecting: boolean, isSavingToLocalStorage: boolean } -export type WritableUIStateStore = Writable; -const store: WritableUIStateStore = writable( +type UIStateOps = { + reconnecting: () => void, + reconnected: () => void, +} + +export type WritableUIStateStore = Writable & UIStateOps; +const store: Writable = writable( { graphLocked: false, nodesLocked: false, @@ -22,11 +28,22 @@ const store: WritableUIStateStore = writable( uiUnlocked: false, uiEditMode: "widgets", + reconnecting: false, isSavingToLocalStorage: false }) +function reconnecting() { + store.update(s => { s.reconnecting = true; return s; }) +} + +function reconnected() { + store.update(s => { s.reconnecting = false; return s; }) +} + const uiStateStore: WritableUIStateStore = { - ...store + ...store, + reconnecting, + reconnected } export default uiStateStore; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 62e72a0..c9378f3 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -13,6 +13,10 @@ export function clamp(n: number, min: number, max: number): number { return Math.min(Math.max(n, min), max) } +export function negmod(n: number, m: number): number { + return ((n % m) + m) % m; +} + export function range(size: number, startAt: number = 0): ReadonlyArray { return [...Array(size).keys()].map(i => i + startAt); } @@ -104,7 +108,7 @@ export function promptToGraphVis(prompt: SerializedPrompt): string { export function getNodeInfo(nodeId: number): string { let app = (window as any).app; - if (!app) + if (!app || !app.lGraph) return String(nodeId); const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId); @@ -135,6 +139,12 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat }); } +export function convertComfyOutputToComfyURL(output: GalleryOutputEntry): string { + const params = new URLSearchParams(output) + const url = `http://${location.hostname}:8188` // TODO make configurable + return url + "/view?" + params +} + export function convertFilenameToComfyURL(filename: string, subfolder: string = "", type: "input" | "output" | "temp" = "output"): string { diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte index 30c4880..999813a 100644 --- a/src/lib/widgets/ComboWidget.svelte +++ b/src/lib/widgets/ComboWidget.svelte @@ -246,6 +246,22 @@ width: auto; --font-size: 13px; --height: 32px; + --background: var(--input-background-fill); + --selected-item-color: var(--body-text-color); + --input-color: var(--body-text-color); + --chevron-color: var(--body-text-color); + --border: 1px solid var(--input-border-color); + --border-hover: 1px solid var(--input-border-color-hover); + --border-focused: 1px solid var(--input-border-color-focus); + --border-radius-focused: 0px; + --border-radius: 0px; + --list-background: var(--comfy-dropdown-list-background); + --item-border: var(--comfy-dropdown-border-color); + --item-color: var(--body-text-color); + --item-color-hover: var(--comfy-dropdown-item-color-hover); + --item-background-hover: var(--comfy-dropdown-item-background-hover); + --item-color-active: var(--comfy-dropdown-item-color-active); + --item-background-active: var(--comfy-dropdown-item-background-active); } :global(.svelte-select-list) { @@ -263,10 +279,11 @@ .comfy-select-list { width: 30rem; + color: var(--item-color); > :global(.virtual-list-wrapper) { box-shadow: var(--block-shadow); - background-color: white; + background-color: var(--list-background); } .comfy-empty-list { @@ -279,35 +296,34 @@ } .comfy-select-item { - border: 1px solid var(--neutral-300); + border: 1px solid var(--item-border); border-top: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: flex; align-items: center; - background-color: white; + background-color: var(--list-background); font-size: 14px; padding: 0.2rem; + .comfy-select-label { + } + &.mobile { font-size: 16px; padding: 1.2rem; } &.hover { - color: white; - background: var(--neutral-400); + color: var(--item-color-hover); + background: var(--item-background-hover); cursor: pointer; } &.active { - color: white; - background: var(--color-blue-500); - } - - .comfy-select-label { - + color: var(--item-color-active); + background: var(--item-background-active); } } diff --git a/src/lib/widgets/GalleryWidget.svelte b/src/lib/widgets/GalleryWidget.svelte index 8729025..0ffdd3a 100644 --- a/src/lib/widgets/GalleryWidget.svelte +++ b/src/lib/widgets/GalleryWidget.svelte @@ -122,14 +122,14 @@ // the event might fire too early const callback = isMobile ? setupImageForMobileLightbox - : ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance) + : ImageViewer.instance.setupGalleryImageForLightbox.bind(ImageViewer.instance) setTimeout(() => { const images = element.querySelectorAll('div.block div > img') if (images != null) { images.forEach(callback); } - ImageViewer.instance.updateOnBackgroundChange(); + ImageViewer.instance.refreshImages(); }, 200) // Update index diff --git a/src/lib/widgets/TextWidget.svelte b/src/lib/widgets/TextWidget.svelte index bba0dc1..79c8dbc 100644 --- a/src/lib/widgets/TextWidget.svelte +++ b/src/lib/widgets/TextWidget.svelte @@ -51,12 +51,7 @@ padding: 2px; width: 100%; - :global(input[type=text]:disabled) { - @include disable-input; - } - :global(textarea:disabled) { - @include disable-input; - } + @include disable-inputs; } :global(span.hide) { diff --git a/src/main-mobile.ts b/src/main-mobile.ts index c62b15d..50655a4 100644 --- a/src/main-mobile.ts +++ b/src/main-mobile.ts @@ -5,6 +5,7 @@ import { f7 } from 'framework7-svelte'; import ComfyApp from '$lib/components/ComfyApp'; import uiState from '$lib/stores/uiState'; import { LiteGraph } from '@litegraph-ts/core'; +import ComfyGraph from '$lib/ComfyGraph'; Framework7.use(Framework7Svelte); diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte index a2be969..fb2c9ac 100644 --- a/src/mobile/GenToolbar.svelte +++ b/src/mobile/GenToolbar.svelte @@ -57,9 +57,9 @@
- {#if $queueState.runningNodeId || $queueState.progress} + {#if $queueState.runningNodeID || $queueState.progress}
- Node: {getNodeInfo($queueState.runningNodeId)} + Node: {getNodeInfo($queueState.runningNodeID)}
diff --git a/src/mobile/index.html b/src/mobile/index.html deleted file mode 100644 index c2e2ab8..0000000 --- a/src/mobile/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - -
- - - diff --git a/src/scss/global.scss b/src/scss/global.scss index 2663526..18fd3e8 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -7,14 +7,154 @@ body { overscroll-behavior-y: contain; } +#app { + background: var(--body-background-fill); +} + :root { --color-blue-500: #3985f5; + + --comfy-accent-soft: var(--neutral-300); + --comfy-disabled-label-color: var(--neutral-400); + --comfy-disabled-textbox-background-fill: var(--neutral-200); + --comfy-disabled-textbox-border-color: var(--neutral-300); + --comfy-disabled-textbox-text-color: var(--neutral-500); + --comfy-splitpanes-background-fill: var(--secondary-100); + --comfy-splitpanes-background-fill-hover: var(--secondary-300); + --comfy-splitpanes-background-fill-active: var(--secondary-400); + --comfy-dropdown-item-color-hover: white; + --comfy-dropdown-item-background-hover: var(--neutral-400); + --comfy-dropdown-item-color-active: var(--neutral-100); + --comfy-dropdown-item-background-active: var(--secondary-600); + --comfy-progress-bar-background: var(--neutral-300); + --comfy-progress-bar-foreground: var(--secondary-300); + --comfy-node-name-background: var(--color-red-300); + --comfy-node-name-foreground: var(--body-text-color); + --comfy-spinner-main-color: var(--neutral-400); + --comfy-spinner-accent-color: var(--secondary-500); +} + +.dark { + color-scheme: dark; + + --comfy-accent-soft: var(--neutral-600); + --comfy-disabled-label-color: var(--neutral-500); + --comfy-disabled-textbox-background-fill: var(--neutral-800); + --comfy-disabled-textbox-border-color: var(--neutral-700); + --comfy-disabled-textbox-text-color: var(--neutral-500); + --comfy-splitpanes-background-fill: var(--panel-border-color); + --comfy-splitpanes-background-fill-hover: var(--secondary-500); + --comfy-splitpanes-background-fill-active: var(--secondary-300); + --comfy-dropdown-list-background: var(--neutral-800); + --comfy-dropdown-item-color-hover: var(--neutral-400); + --comfy-dropdown-item-background-hover: var(--neutral-600); + --comfy-dropdown-item-background-active: var(--secondary-600); + --comfy-dropdown-border-color: var(--neutral-600); + --comfy-progress-bar-background: var(--neutral-500); + --comfy-progress-bar-foreground: var(--secondary-400); + --comfy-node-name-background: var(--neutral-700); + --comfy-node-name-foreground: var(--body-text-color); + --comfy-spinner-main-color: var(--neutral-600); + --comfy-spinner-accent-color: var(--secondary-600); +} + +.mobile { + --comfy-progress-bar-background: lightgrey; + --comfy-progress-bar-foreground: #B3D8A9 } @mixin disable-input { - -webkit-text-fill-color: var(--neutral-500); - background-color: var(--neutral-200); - border-color: var(--neutral-300); + -webkit-text-fill-color: var(--comfy-disabled-textbox-text-color); + background-color: var(--comfy-disabled-textbox-background-fill); + border-color: var(--comfy-disabled-textbox-border-color); box-shadow: 0 0 0 var(--shadow-spread) transparent, rgba(0, 0, 0, 0.08) 0px 2px 4px 0px inset; cursor: not-allowed; } + +@mixin disable-inputs { + :global(input[type=text]:disabled) { + @include disable-input; + } + :global(textarea:disabled) { + @include disable-input; + } + :global(label:has(input:disabled) > span) { + color: var(--comfy-disabled-label-color); + } + :global(label:has(textarea:disabled) > span) { + color: var(--comfy-disabled-label-color); + } +} + +hr { + color: var(--panel-border-color); +} + +select { + color: var(--body-text-color); + background: var(--block-background-fill); +} + +.container { + background: var(--body-background-fill) !important; + &.selected { + background: var(--ae-primary-color) !important; + > .block.padded { + background: var(--ae-primary-color) !important; + } + } + > .block { + background: var(--body-background-fill) !important; + // border-radius: var(--ae-panel-border-radius) !important; + } + &.z-index0 { + > .block { + background: var(--panel-background-fill) !important; + } + } + + // &:not(.edit) { + // &.z-index1 > .block { + // padding: calc(var(--ae-outside-gap-size) / 2) !important; + // border-width: 0px !important; + // } + + // > .block { + // border: solid var(--ae-panel-border-width) var(--ae-panel-border-color) !important; + // } + // } +} + +// button { +// filter: none; +// &.primary:active { +// filter: brightness(80%) +// } +// &.secondary:active { +// filter: brightness(80%) +// } +// } + + +button { + &.primary:active { + border-color: var(--button-primary-border-color-active) !important; + background: var(--button-primary-background-fill-active) !important; + color: var(--button-primary-text-color-active) !important; + } + &.secondary:active { + border-color: var(--button-secondary-border-color-active) !important; + background: var(--button-secondary-background-fill-active) !important; + color: var(--button-secondary-text-color-active) !important; + } +} + +.widget { + // padding: var(--ae-outside-gap-size); + // border: 1px solid var(--ae-panel-border-color); + .block { + // background: var(--ae-frame-bg-color) !important; + background: var(--block-background-fill) !important; + } + +} diff --git a/src/scss/gradio.scss b/src/scss/gradio.scss index 21d6a27..27df95a 100644 --- a/src/scss/gradio.scss +++ b/src/scss/gradio.scss @@ -1,365 +1,427 @@ :root { - --primary-50: #fff7ed; - --primary-100: #ffedd5; - --primary-200: #fed7aa; - --primary-300: #fdba74; - --primary-400: #fb923c; - --primary-500: #f97316; - --primary-600: #ea580c; - --primary-700: #c2410c; - --primary-800: #9a3412; - --primary-900: #7c2d12; - --primary-950: #6c2e12; - --secondary-50: #eff6ff; - --secondary-100: #dbeafe; - --secondary-200: #bfdbfe; - --secondary-300: #93c5fd; - --secondary-400: #60a5fa; - --secondary-500: #3b82f6; - --secondary-600: #2563eb; - --secondary-700: #1d4ed8; - --secondary-800: #1e40af; - --secondary-900: #1e3a8a; - --secondary-950: #1d3660; - --neutral-50: #f9fafb; - --neutral-100: #f3f4f6; - --neutral-200: #e5e7eb; - --neutral-300: #d1d5db; - --neutral-400: #9ca3af; - --neutral-500: #6b7280; - --neutral-600: #4b5563; - --neutral-700: #374151; - --neutral-800: #1f2937; - --neutral-900: #111827; - --neutral-950: #0b0f19; - --spacing-xxs: 1px; - --spacing-xs: 2px; - --spacing-sm: 4px; - --spacing-md: 6px; - --spacing-lg: 8px; - --spacing-xl: 10px; - --spacing-xxl: 16px; - --radius-xxs: 1px; - --radius-xs: 2px; - --radius-sm: 4px; - --radius-md: 6px; - --radius-lg: 8px; - --radius-xl: 12px; - --radius-xxl: 22px; - --text-xxs: 9px; - --text-xs: 10px; - --text-sm: 12px; - --text-md: 14px; - --text-lg: 16px; - --text-xl: 22px; - --text-xxl: 26px; - --color-accent: var(--primary-500); - --color-accent-soft: var(--primary-50); - --background-fill-primary: white; - --background-fill-secondary: var(--neutral-50); - --border-color-accent: var(--primary-300); - --border-color-primary: var(--neutral-200); - --text-color-code-background-fill: var(--neutral-200); - --text-color-code-border: var(--border-color-primary); - --link-text-color: var(--secondary-600); - --link-text-color-active: var(--secondary-600); - --link-text-color-hover: var(--secondary-700); - --link-text-color-visited: var(--secondary-500); - --body-text-color-subdued: var(--neutral-400); - --body-background-fill: var(--background-fill-primary); - --body-text-color: var(--neutral-800); - --body-text-size: var(--text-md); - --body-text-weight: 400; - --embed-radius: var(--radius-lg); - --shadow-drop: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px; - --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), - 0 1px 2px -1px rgb(0 0 0 / 0.1); - --shadow-inset: rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset; - --shadow-spread: 3px; - --block-background-fill: var(--background-fill-primary); - --block-border-color: var(--border-color-primary); - --block-border-width: 1px; - --block-info-text-color: var(--body-text-color-subdued); - --block-info-text-size: var(--text-sm); - --block-info-text-weight: 400; - --block-label-background-fill: var(--background-fill-primary); - --block-label-border-color: var(--border-color-primary); - --block-label-border-width: 1px; - --block-label-text-color: var(--neutral-500); - --block-label-icon-color: var(--block-label-text-color); - --block-label-margin: 0; - --block-label-padding: var(--spacing-sm) var(--spacing-lg); - --block-label-radius: calc(var(--radius-lg) - 1px) 0 - calc(var(--radius-lg) - 1px) 0; - --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 - calc(var(--radius-lg) - 1px); - --block-label-text-size: var(--text-sm); - --block-label-text-weight: 400; - --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); - --block-radius: var(--radius-lg); - --block-shadow: var(--shadow-drop); - --block-title-background-fill: none; - --block-title-border-color: none; - --block-title-border-width: 0px; - --block-title-text-color: var(--neutral-500); - --block-title-padding: 0; - --block-title-radius: none; - --block-title-text-size: var(--text-md); - --block-title-text-weight: 400; - --container-radius: var(--radius-lg); - --form-gap-width: 1px; - --layout-gap: var(--spacing-xxl); - --panel-background-fill: var(--background-fill-secondary); - --panel-border-color: var(--border-color-primary); - --panel-border-width: 0; - --section-header-text-size: var(--text-md); - --section-header-text-weight: 400; - --checkbox-background-color: var(--background-fill-primary); - --checkbox-background-color-focus: var(--background-fill-primary); - --checkbox-background-color-hover: var(--background-fill-primary); - --checkbox-background-color-selected: var(--secondary-600); - --checkbox-border-color: var(--neutral-300); - --checkbox-border-color-focus: var(--secondary-500); - --checkbox-border-color-hover: var(--neutral-300); - --checkbox-border-color-selected: var(--secondary-600); - --checkbox-border-radius: var(--radius-sm); - --checkbox-border-width: var(--input-border-width); - --checkbox-label-background-fill: linear-gradient( - to top, - var(--neutral-50), - white - ); - --checkbox-label-background-fill-hover: linear-gradient( - to top, - var(--neutral-100), - white - ); - --checkbox-label-background-fill-selected: var( - --checkbox-label-background-fill - ); - --checkbox-label-border-color: var(--border-color-primary); - --checkbox-label-border-color-hover: var(--border-color-primary); - --checkbox-label-border-width: var(--input-border-width); - --checkbox-label-gap: var(--spacing-lg); - --checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md)); - --checkbox-label-shadow: var(--shadow-drop); - --checkbox-label-text-size: var(--text-md); - --checkbox-label-text-weight: 400; - --checkbox-shadow: var(--input-shadow); - --checkbox-label-text-color: var(--body-text-color); - --checkbox-label-text-color-selected: var(--checkbox-label-text-color); - --error-background-fill: linear-gradient( - to right, - #fee2e2, - var(--background-fill-secondary) - ); - --error-border-color: #fecaca; - --error-border-width: 1px; - --error-text-color: #ef4444; - --prose-header-text-weight: 600; - --input-background-fill: white; - --input-background-fill-focus: var(--secondary-500); - --input-background-fill-hover: var(--input-background-fill); - --input-border-color: var(--border-color-primary); - --input-border-color-focus: var(--secondary-300); - --input-border-color-hover: var(--border-color-primary); - --input-border-width: 1px; - --input-padding: var(--spacing-xl); - --input-placeholder-color: var(--neutral-400); - --input-radius: var(--radius-lg); - --input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset); - --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--secondary-50), - var(--shadow-inset); - --input-text-size: var(--text-md); - --input-text-weight: 400; - --loader-color: var(--color-accent); - --prose-text-size: var(--text-md); - --prose-text-weight: 400; - --stat-background-fill: linear-gradient( - to right, - var(--primary-400), - var(--primary-200) - ); - --table-border-color: var(--neutral-300); - --table-even-background-fill: white; - --table-odd-background-fill: var(--neutral-50); - --table-radius: var(--radius-lg); - --table-row-focus: var(--color-accent-soft); - --button-border-width: var(--input-border-width); - --button-cancel-background-fill: linear-gradient( - to bottom right, - #fee2e2, - #fecaca - ); - --button-cancel-background-fill-hover: linear-gradient( - to bottom right, - #fee2e2, - #fee2e2 - ); - --button-cancel-border-color: #fecaca; - --button-cancel-border-color-hover: var(--button-cancel-border-color); - --button-cancel-text-color: #dc2626; - --button-cancel-text-color-hover: var(--button-cancel-text-color); - --button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg)); - --button-large-radius: var(--radius-lg); - --button-large-text-size: var(--text-lg); - --button-large-text-weight: 600; - --button-primary-background-fill: linear-gradient( - to bottom right, - var(--primary-100), - var(--primary-300) - ); - --button-primary-background-fill-hover: linear-gradient( - to bottom right, - var(--primary-100), - var(--primary-200) - ); - --button-primary-border-color: var(--primary-200); - --button-primary-border-color-hover: var(--button-primary-border-color); - --button-primary-text-color: var(--primary-600); - --button-primary-text-color-hover: var(--button-primary-text-color); - --button-secondary-background-fill: linear-gradient( - to bottom right, - var(--neutral-100), - var(--neutral-200) - ); - --button-secondary-background-fill-hover: linear-gradient( - to bottom right, - var(--neutral-100), - var(--neutral-100) - ); - --button-secondary-border-color: var(--neutral-200); - --button-secondary-border-color-hover: var(--button-secondary-border-color); - --button-secondary-text-color: var(--neutral-700); - --button-secondary-text-color-hover: var(--button-secondary-text-color); - --button-shadow: var(--shadow-drop); - --button-shadow-active: var(--shadow-inset); - --button-shadow-hover: var(--shadow-drop-lg); - --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); - --button-small-radius: var(--radius-lg); - --button-small-text-size: var(--text-md); - --button-small-text-weight: 400; - --button-transition: none; + --name: default; + --primary-50: #fff7ed; + --primary-100: #ffedd5; + --primary-200: #fed7aa; + --primary-300: #fdba74; + --primary-400: #fb923c; + --primary-500: #f97316; + --primary-600: #ea580c; + --primary-700: #c2410c; + --primary-800: #9a3412; + --primary-900: #7c2d12; + --primary-950: #6c2e12; + --secondary-50: #eff6ff; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --neutral-50: #f9fafb; + --neutral-100: #f3f4f6; + --neutral-200: #e5e7eb; + --neutral-300: #d1d5db; + --neutral-400: #9ca3af; + --neutral-500: #6b7280; + --neutral-600: #4b5563; + --neutral-700: #374151; + --neutral-800: #1f2937; + --neutral-900: #111827; + --neutral-950: #0b0f19; + --spacing-xxs: 1px; + --spacing-xs: 2px; + --spacing-sm: 4px; + --spacing-md: 6px; + --spacing-lg: 8px; + --spacing-xl: 10px; + --spacing-xxl: 16px; + --radius-xxs: 1px; + --radius-xs: 2px; + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 12px; + --radius-xxl: 22px; + --text-xxs: 9px; + --text-xs: 10px; + --text-sm: 12px; + --text-md: 14px; + --text-lg: 16px; + --text-xl: 22px; + --text-xxl: 26px; + --font: 'Source Sans Pro', 'ui-sans-serif', 'system-ui', sans-serif; + --font-mono: 'IBM Plex Mono', 'ui-monospace', 'Consolas', monospace; + --body-background-fill: var(--background-fill-primary); + --body-text-color: var(--neutral-800); + --body-text-size: var(--text-md); + --body-text-weight: 400; + --embed-radius: var(--radius-lg); + --color-accent: var(--primary-500); + --color-accent-soft: var(--primary-50); + --background-fill-primary: white; + --background-fill-secondary: var(--neutral-50); + --border-color-accent: var(--primary-300); + --border-color-primary: var(--neutral-200); + --link-text-color: var(--secondary-600); + --link-text-color-active: var(--secondary-600); + --link-text-color-hover: var(--secondary-700); + --link-text-color-visited: var(--secondary-500); + --body-text-color-subdued: var(--neutral-400); + --shadow-drop: rgba(0,0,0,0.05) 0px 1px 2px 0px; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --shadow-spread: 3px; + --block-background-fill: var(--background-fill-primary); + --block-border-color: var(--border-color-primary); + --block-border-width: 1px; + --block-info-text-color: var(--body-text-color-subdued); + --block-info-text-size: var(--text-sm); + --block-info-text-weight: 400; + --block-label-background-fill: var(--background-fill-primary); + --block-label-border-color: var(--border-color-primary); + --block-label-border-width: 1px; + --block-label-shadow: var(--block-shadow); + --block-label-text-color: var(--neutral-500); + --block-label-margin: 0; + --block-label-padding: var(--spacing-sm) var(--spacing-lg); + --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; + --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-size: var(--text-sm); + --block-label-text-weight: 400; + --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); + --block-radius: var(--radius-lg); + --block-shadow: var(--shadow-drop); + --block-title-background-fill: none; + --block-title-border-color: none; + --block-title-border-width: 0px; + --block-title-text-color: var(--neutral-500); + --block-title-padding: 0; + --block-title-radius: none; + --block-title-text-size: var(--text-md); + --block-title-text-weight: 400; + --container-radius: var(--radius-lg); + --form-gap-width: 1px; + --layout-gap: var(--spacing-xxl); + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel-border-width: 0; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --checkbox-background-color: var(--background-fill-primary); + --checkbox-background-color-focus: var(--checkbox-background-color); + --checkbox-background-color-hover: var(--checkbox-background-color); + --checkbox-background-color-selected: var(--secondary-600); + --checkbox-border-color: var(--neutral-300); + --checkbox-border-color-focus: var(--secondary-500); + --checkbox-border-color-hover: var(--neutral-300); + --checkbox-border-color-selected: var(--secondary-600); + --checkbox-border-radius: var(--radius-sm); + --checkbox-border-width: var(--input-border-width); + --checkbox-label-background-fill: linear-gradient(to top, var(--neutral-50), white); + --checkbox-label-background-fill-hover: linear-gradient(to top, var(--neutral-100), white); + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-gap: var(--spacing-lg); + --checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md)); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --checkbox-shadow: var(--input-shadow); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --error-background-fill: linear-gradient(to right, #fee2e2, var(--background-fill-secondary)); + --error-border-color: #fecaca; + --error-border-width: 1px; + --error-text-color: #ef4444; + --input-background-fill: white; + --input-background-fill-focus: var(--secondary-500); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color: var(--border-color-primary); + --input-border-color-focus: var(--secondary-300); + --input-border-color-hover: var(--input-border-color); + --input-border-width: 1px; + --input-padding: var(--spacing-xl); + --input-placeholder-color: var(--neutral-400); + --input-radius: var(--radius-lg); + --input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset); + --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--secondary-50), var(--shadow-inset); + --input-text-size: var(--text-md); + --input-text-weight: 400; + --loader-color: var(--color-accent); + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --prose-header-text-weight: 600; + --slider-color: auto; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-200)); + --table-border-color: var(--neutral-300); + --table-even-background-fill: white; + --table-odd-background-fill: var(--neutral-50); + --table-radius: var(--radius-lg); + --table-row-focus: var(--color-accent-soft); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill: linear-gradient(to bottom right, #fee2e2, #fecaca); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #fee2e2, #fee2e2); + --button-cancel-border-color: #fecaca; + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-text-color: #dc2626; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg)); + --button-large-radius: var(--radius-lg); + --button-large-text-size: var(--text-lg); + --button-large-text-weight: 600; + --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-100), var(--primary-300)); + --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-100), var(--primary-200)); + --button-primary-background-fill-active: linear-gradient(to bottom right, var(--primary-200), var(--primary-400)); + --button-primary-border-color: var(--primary-200); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-border-color-active: var(--button-primary-border-color); + --button-primary-text-color: var(--primary-600); + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-primary-text-color-active: var(--button-primary-text-color); + --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-100), var(--neutral-200)); + --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-100), var(--neutral-100)); + --button-secondary-background-fill-active: linear-gradient(to bottom right, var(--neutral-200), var(--neutral-300)); + --button-secondary-border-color: var(--neutral-200); + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-border-color-active: var(--button-secondary-border-color); + --button-secondary-text-color: var(--neutral-700); + --button-secondary-text-color-hover: var(--button-secondary-text-color); + --button-secondary-text-color-active: var(--neutral-800); + --button-shadow: var(--shadow-drop); + --button-shadow-active: var(--shadow-inset); + --button-shadow-hover: var(--shadow-drop-lg); + --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); + --button-small-radius: var(--radius-lg); + --button-small-text-size: var(--text-md); + --button-small-text-weight: 400; + --button-transition: none; } + .dark { - --color-accent-soft: var(--neutral-900); - --background-fill-primary: var(--neutral-950); - --background-fill-secondary: var(--neutral-900); - --border-color-accent: var(--neutral-600); - --border-color-primary: var(--neutral-700); - --text-color-code-background-fill: var(--neutral-800); - --link-text-color-active: var(--secondary-500); - --link-text-color: var(--secondary-500); - --link-text-color-hover: var(--secondary-400); - --link-text-color-visited: var(--secondary-600); - --body-text-color-subdued: var(--neutral-400); - --body-background-fill: var(--background-fill-primary); - --body-text-color: var(--neutral-100); - --shadow-spread: 1px; - --block-background-fill: var(--neutral-800); - --block-border-color: var(--border-color-primary); - --block-border-width: 1px; - --block-info-text-color: var(--body-text-color-subdued); - --block-label-background-fill: var(--background-fill-secondary); - --block-label-border-color: var(--border-color-primary); - --block-label-border-width: 1px; - --block-label-text-color: var(--neutral-200); - --block-shadow: none; - --block-title-background-fill: none; - --block-title-border-color: none; - --block-title-border-width: 0px; - --block-title-text-color: var(--neutral-200); - --panel-background-fill: var(--background-fill-secondary); - --panel-border-color: var(--border-color-primary); - --checkbox-background-color: var(--neutral-800); - --checkbox-background-color-focus: var(--checkbox-background-color); - --checkbox-background-color-hover: var(--checkbox-background-color); - --checkbox-background-color-selected: var(--secondary-600); - --checkbox-border-color: var(--neutral-700); - --checkbox-border-color-focus: var(--secondary-500); - --checkbox-border-color-hover: var(--neutral-600); - --checkbox-border-color-selected: var(--secondary-600); - --checkbox-label-background-fill: linear-gradient( - to top, - var(--neutral-900), - var(--neutral-800) - ); - --checkbox-label-background-fill-hover: linear-gradient( - to top, - var(--neutral-900), - var(--neutral-800) - ); - --checkbox-label-background-fill-selected: var( - --checkbox-label-background-fill - ); - --checkbox-label-border-color: var(--border-color-primary); - --checkbox-label-border-color-hover: var(--border-color-primary); - --checkbox-label-text-color: var(--body-text-color); - --checkbox-label-text-color-selected: var(--checkbox-label-text-color); - --error-background-fill: var(--background-fill-primary); - --error-border-color: var(--border-color-primary); - --error-border-width: var(--error-border-width); - --error-text-color: #ef4444; - --input-background-fill: var(--neutral-800); - --input-background-fill-focus: var(--secondary-600); - --input-background-fill-hover: var(--input-background-fill); - --input-border-color: var(--border-color-primary); - --input-border-color-focus: var(--neutral-700); - --input-border-color-hover: var(--border-color-primary); - --input-placeholder-color: var(--neutral-500); - --input-shadow: var(--input-shadow); - --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700), - var(--shadow-inset); - --loader-color: var(--loader-color); - --stat-background-fill: linear-gradient( - to right, - var(--primary-400), - var(--primary-600) - ); - --table-border-color: var(--neutral-700); - --table-even-background-fill: var(--neutral-950); - --table-odd-background-fill: var(--neutral-900); - --table-row-focus: var(--color-accent-soft); - --button-cancel-background-fill: linear-gradient( - to bottom right, - #dc2626, - #b91c1c - ); - --button-cancel-background-fill-hover: linear-gradient( - to bottom right, - #dc2626, - #dc2626 - ); - --button-cancel-border-color: #dc2626; - --button-cancel-border-color-hover: var(--button-cancel-border-color); - --button-cancel-text-color: white; - --button-cancel-text-color-hover: var(--button-cancel-text-color); - --button-primary-background-fill: linear-gradient( - to bottom right, - var(--primary-600), - var(--primary-700) - ); - --button-primary-background-fill-hover: linear-gradient( - to bottom right, - var(--primary-600), - var(--primary-600) - ); - --button-primary-border-color: var(--primary-600); - --button-primary-border-color-hover: var(--button-primary-border-color); - --button-primary-text-color: white; - --button-primary-text-color-hover: var(--button-primary-text-color); - --button-secondary-background-fill: linear-gradient( - to bottom right, - var(--neutral-600), - var(--neutral-700) - ); - --button-secondary-background-fill-hover: linear-gradient( - to bottom right, - var(--neutral-600), - var(--neutral-600) - ); - --button-secondary-border-color: var(--neutral-600); - --button-secondary-border-color-hover: var(--button-secondary-border-color); - --button-secondary-text-color: white; - --button-secondary-text-color-hover: var(--button-secondary-text-color); + --body-background-fill: var(--background-fill-primary); + --body-text-color: var(--neutral-100); + --color-accent-soft: var(--neutral-700); + --background-fill-primary: var(--neutral-950); + --background-fill-secondary: var(--neutral-900); + --border-color-accent: var(--neutral-600); + --border-color-primary: var(--neutral-700); + --link-text-color-active: var(--secondary-500); + --link-text-color: var(--secondary-500); + --link-text-color-hover: var(--secondary-400); + --link-text-color-visited: var(--secondary-600); + --body-text-color-subdued: var(--neutral-400); + --shadow-spread: 1px; + --block-background-fill: var(--neutral-800); + --block-border-color: var(--border-color-primary); + --block_border_width: None; + --block-info-text-color: var(--body-text-color-subdued); + --block-label-background-fill: var(--background-fill-secondary); + --block-label-border-color: var(--border-color-primary); + --block_label_border_width: None; + --block-label-text-color: var(--neutral-200); + --block_shadow: None; + --block_title_background_fill: None; + --block_title_border_color: None; + --block_title_border_width: None; + --block-title-text-color: var(--neutral-200); + --panel-background-fill: var(--background-fill-secondary); + --panel-border-color: var(--border-color-primary); + --panel_border_width: None; + --checkbox-background-color: var(--neutral-800); + --checkbox-background-color-focus: var(--checkbox-background-color); + --checkbox-background-color-hover: var(--checkbox-background-color); + --checkbox-background-color-selected: var(--secondary-600); + --checkbox-border-color: var(--neutral-700); + --checkbox-border-color-focus: var(--secondary-500); + --checkbox-border-color-hover: var(--neutral-600); + --checkbox-border-color-selected: var(--secondary-600); + --checkbox-border-width: var(--input-border-width); + --checkbox-label-background-fill: linear-gradient(to top, var(--neutral-900), var(--neutral-800)); + --checkbox-label-background-fill-hover: linear-gradient(to top, var(--neutral-900), var(--neutral-800)); + --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill); + --checkbox-label-border-color: var(--border-color-primary); + --checkbox-label-border-color-hover: var(--checkbox-label-border-color); + --checkbox-label-border-width: var(--input-border-width); + --checkbox-label-text-color: var(--body-text-color); + --checkbox-label-text-color-selected: var(--checkbox-label-text-color); + --error-background-fill: var(--background-fill-primary); + --error-border-color: var(--border-color-primary); + --error_border_width: None; + --error-text-color: #ef4444; + --input-background-fill: var(--neutral-800); + --input-background-fill-focus: var(--secondary-600); + --input-background-fill-hover: var(--input-background-fill); + --input-border-color: var(--border-color-primary); + --input-border-color-focus: var(--neutral-700); + --input-border-color-hover: var(--input-border-color); + --input_border_width: None; + --input-placeholder-color: var(--neutral-500); + --input_shadow: None; + --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700), var(--shadow-inset); + --loader_color: None; + --slider_color: None; + --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-600)); + --table-border-color: var(--neutral-700); + --table-even-background-fill: var(--neutral-950); + --table-odd-background-fill: var(--neutral-900); + --table-row-focus: var(--color-accent-soft); + --button-border-width: var(--input-border-width); + --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c); + --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626); + --button-cancel-border-color: #dc2626; + --button-cancel-border-color-hover: var(--button-cancel-border-color); + --button-cancel-text-color: white; + --button-cancel-text-color-hover: var(--button-cancel-text-color); + --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-600)); + --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-500)); + --button-primary-background-fill-active: linear-gradient(to bottom right, var(--primary-600), var(--primary-700)); + --button-primary-border-color: var(--primary-500); + --button-primary-border-color-hover: var(--button-primary-border-color); + --button-primary-border-color-active: var(--button-primary-border-color); + --button-primary-text-color: white; + --button-primary-text-color-hover: var(--button-primary-text-color); + --button-primary-text-color-active: var(--neutral-300); + --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-700)); + --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-600)); + --button-secondary-background-fill-active: linear-gradient(to bottom right, var(--neutral-700), var(--neutral-800)); + --button-secondary-border-color: var(--neutral-600); + --button-secondary-border-color-hover: var(--button-secondary-border-color); + --button-secondary-border-color-active: var(--button-secondary-border-color); + --button-secondary-text-color: white; + --button-secondary-text-color-hover: var(--button-secondary-text-color); + --button-secondary-text-color-active: var(--neutral-300); + --name: default; + --primary-50: #fff7ed; + --primary-100: #ffedd5; + --primary-200: #fed7aa; + --primary-300: #fdba74; + --primary-400: #fb923c; + --primary-500: #f97316; + --primary-600: #ea580c; + --primary-700: #c2410c; + --primary-800: #9a3412; + --primary-900: #7c2d12; + --primary-950: #6c2e12; + --secondary-50: #eff6ff; + --secondary-100: #dbeafe; + --secondary-200: #bfdbfe; + --secondary-300: #93c5fd; + --secondary-400: #60a5fa; + --secondary-500: #3b82f6; + --secondary-600: #2563eb; + --secondary-700: #1d4ed8; + --secondary-800: #1e40af; + --secondary-900: #1e3a8a; + --secondary-950: #1d3660; + --neutral-50: #f9fafb; + --neutral-100: #f3f4f6; + --neutral-200: #e5e7eb; + --neutral-300: #d1d5db; + --neutral-400: #9ca3af; + --neutral-500: #6b7280; + --neutral-600: #4b5563; + --neutral-700: #374151; + --neutral-800: #1f2937; + --neutral-900: #111827; + --neutral-950: #0b0f19; + --spacing-xxs: 1px; + --spacing-xs: 2px; + --spacing-sm: 4px; + --spacing-md: 6px; + --spacing-lg: 8px; + --spacing-xl: 10px; + --spacing-xxl: 16px; + --radius-xxs: 1px; + --radius-xs: 2px; + --radius-sm: 4px; + --radius-md: 6px; + --radius-lg: 8px; + --radius-xl: 12px; + --radius-xxl: 22px; + --text-xxs: 9px; + --text-xs: 10px; + --text-sm: 12px; + --text-md: 14px; + --text-lg: 16px; + --text-xl: 22px; + --text-xxl: 26px; + --font: 'Source Sans Pro', 'ui-sans-serif', 'system-ui', sans-serif; + --font-mono: 'IBM Plex Mono', 'ui-monospace', 'Consolas', monospace; + --body-text-size: var(--text-md); + --body-text-weight: 400; + --embed-radius: var(--radius-lg); + --color-accent: var(--primary-500); + --shadow-drop: rgba(0,0,0,0.05) 0px 1px 2px 0px; + --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset; + --block-border-width: 1px; + --block-info-text-size: var(--text-sm); + --block-info-text-weight: 400; + --block-label-border-width: 1px; + --block-label-shadow: var(--block-shadow); + --block-label-margin: 0; + --block-label-padding: var(--spacing-sm) var(--spacing-lg); + --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0; + --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px); + --block-label-text-size: var(--text-sm); + --block-label-text-weight: 400; + --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px); + --block-radius: var(--radius-lg); + --block-shadow: var(--shadow-drop); + --block-title-background-fill: none; + --block-title-border-color: none; + --block-title-border-width: 0px; + --block-title-padding: 0; + --block-title-radius: none; + --block-title-text-size: var(--text-md); + --block-title-text-weight: 400; + --container-radius: var(--radius-lg); + --form-gap-width: 1px; + --layout-gap: var(--spacing-xxl); + --panel-border-width: 0; + --section-header-text-size: var(--text-md); + --section-header-text-weight: 400; + --checkbox-border-radius: var(--radius-sm); + --checkbox-label-gap: var(--spacing-lg); + --checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md)); + --checkbox-label-shadow: var(--shadow-drop); + --checkbox-label-text-size: var(--text-md); + --checkbox-label-text-weight: 400; + --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); + --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); + --checkbox-shadow: var(--input-shadow); + --error-border-width: 1px; + --input-border-width: 1px; + --input-padding: var(--spacing-xl); + --input-radius: var(--radius-lg); + --input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset); + --input-text-size: var(--text-md); + --input-text-weight: 400; + --loader-color: var(--color-accent); + --prose-text-size: var(--text-md); + --prose-text-weight: 400; + --prose-header-text-weight: 600; + --slider-color: auto; + --table-radius: var(--radius-lg); + --button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg)); + --button-large-radius: var(--radius-lg); + --button-large-text-size: var(--text-lg); + --button-large-text-weight: 600; + --button-shadow: var(--shadow-drop); + --button-shadow-active: var(--shadow-inset); + --button-shadow-hover: var(--shadow-drop-lg); + --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm)); + --button-small-radius: var(--radius-lg); + --button-small-text-size: var(--text-md); + --button-small-text-weight: 400; + --button-transition: none; } diff --git a/static/screenshot.png b/static/screenshot.png index f675e63..8fbfd1b 100644 Binary files a/static/screenshot.png and b/static/screenshot.png differ diff --git a/static/screenshot2.png b/static/screenshot2.png index 7f0127b..4b3a255 100644 Binary files a/static/screenshot2.png and b/static/screenshot2.png differ diff --git a/vite.config.ts b/vite.config.ts index 6d0f9de..287947b 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -70,6 +70,6 @@ export default defineConfig({ // } }, test: { - include: ['src/**/*.{test,spec}.{js,ts}'] + include: ['litegraph/packages/tests/src/main.ts'] } });