From 9ac64de81199ca238ef280c780b5f1dd12ca3a74 Mon Sep 17 00:00:00 2001 From: space-nuko <24979496+space-nuko@users.noreply.github.com> Date: Fri, 12 May 2023 20:40:55 -0500 Subject: [PATCH] Show prompt details & thumbnail in queue --- litegraph | 2 +- package.json | 1 + pnpm-lock.yaml | 343 +++++++++++++++++- src/lib/ImageViewer.ts | 2 - src/lib/api.ts | 5 +- src/lib/components/ComfyApp.ts | 30 +- src/lib/components/ComfyProperties.svelte | 7 +- src/lib/components/ComfyQueue.svelte | 183 +++++++--- src/lib/components/Modal.svelte | 61 ++++ src/lib/components/PromptDisplay.svelte | 165 +++++++++ .../components/gradio/app/Accordion.svelte | 2 +- src/lib/nodes/ComfyActionNodes.ts | 52 +++ src/lib/nodes/ComfyGraphNode.ts | 9 +- src/lib/stores/queueState.ts | 8 +- src/lib/widgets/TextWidget.svelte | 7 +- src/scss/global.scss | 35 +- 16 files changed, 817 insertions(+), 95 deletions(-) create mode 100644 src/lib/components/Modal.svelte create mode 100644 src/lib/components/PromptDisplay.svelte diff --git a/litegraph b/litegraph index a3ebe89..11970cc 160000 --- a/litegraph +++ b/litegraph @@ -1 +1 @@ -Subproject commit a3ebe89b9d636df059f9c505f45381b55ad4f370 +Subproject commit 11970cc85c183bbd3c48fec6f1ee83088575e72f diff --git a/package.json b/package.json index 34d7b65..ee0e8d0 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@gradio/gallery": "workspace:*", "@gradio/icons": "workspace:*", "@gradio/image": "workspace:*", + "@gradio/json": "workspace:*", "@gradio/tabs": "workspace:*", "@gradio/theme": "workspace:*", "@gradio/upload": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 42c00ea..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 @@ -875,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: @@ -2209,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==} @@ -2406,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: @@ -2425,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==} @@ -2451,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==} @@ -2470,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==} @@ -2516,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==} @@ -2658,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: @@ -2715,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: @@ -2765,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==} @@ -2791,7 +2856,6 @@ packages: /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} - dev: true /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} @@ -2968,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 @@ -3192,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'} @@ -3212,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==} @@ -3734,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==} @@ -3822,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'} @@ -4008,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==} @@ -4825,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 @@ -4883,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: @@ -4946,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==} @@ -4990,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==} @@ -5047,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: @@ -5144,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'} @@ -5271,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'} @@ -5341,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==} @@ -5371,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 @@ -5480,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} @@ -5542,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 @@ -5786,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==} @@ -5812,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 @@ -5912,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: @@ -5920,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'} @@ -5984,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==} @@ -6409,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: @@ -6422,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 @@ -6532,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==} @@ -6565,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'} @@ -7019,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'} @@ -7260,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} @@ -7391,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'} @@ -7445,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: @@ -7460,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'} @@ -7555,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/src/lib/ImageViewer.ts b/src/lib/ImageViewer.ts index 4d6837c..ab5d955 100644 --- a/src/lib/ImageViewer.ts +++ b/src/lib/ImageViewer.ts @@ -65,8 +65,6 @@ export class ImageViewer { setTimeout(() => { this.modalImage.focus() }, 200) - - event.stopPropagation() } static get_gallery_urls(galleryElem: HTMLDivElement): string[] { diff --git a/src/lib/api.ts b/src/lib/api.ts index 0780443..b4c4ab1 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,7 +1,7 @@ import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutput, SerializedPromptOutputs } from "./components/ComfyApp"; import type TypedEmitter from "typed-emitter"; import EventEmitter from "events"; -import type { GalleryOutput } from "./nodes/ComfyWidgetNodes"; +import type { GalleryOutput, GalleryOutputEntry } from "./nodes/ComfyWidgetNodes"; import type { SerializedLGraph } from "@litegraph-ts/core"; export type ComfyPromptRequest = { @@ -62,7 +62,8 @@ export type ComfyPromptPNGInfo = { export type ComfyPromptExtraData = { extra_pnginfo?: ComfyPromptPNGInfo, client_id?: string, // UUID - subgraphs: string[] + subgraphs: string[], + thumbnails?: GalleryOutputEntry[] } type ComfyAPIEvents = { diff --git a/src/lib/components/ComfyApp.ts b/src/lib/components/ComfyApp.ts index b01fced..b6e7ac0 100644 --- a/src/lib/components/ComfyApp.ts +++ b/src/lib/components/ComfyApp.ts @@ -59,7 +59,8 @@ export type SerializedAppState = { export type SerializedPromptInput = [NodeID, number] | any export type SerializedPromptInputs = { - inputs: Record, + /* property name -> value or link */ + inputs: Record, class_type: string } @@ -611,7 +612,7 @@ export default class ComfyApp { // The reasoning behind this check: // We only want to serialize inputs to nodes with backend equivalents. - // And in ComfyBox, the nodes in litegraph *never* have widgets, instead they're all inputs. + // And in ComfyBox, the backend nodes in litegraph *never* have widgets, instead they're all inputs. // All values are passed by separate frontend-only nodes, // either UI-bound or something like ConstantInteger. // So we know that any value passed into a backend node *must* come from @@ -728,8 +729,26 @@ export default class ComfyApp { ({ num, batchCount } = this.queueItems.pop()); console.debug(`Queue get! ${num} ${batchCount} ${tag}`); + const thumbnails = [] + for (const node of this.lGraph.iterateNodesInOrderRecursive()) { + if (node.mode !== NodeMode.ALWAYS + || (tag != null + && Array.isArray(node.properties.tags) + && node.properties.tags.indexOf(tag) === -1)) + continue; + + if ("getPromptThumbnails" in node) { + const thumbsToAdd = (node as ComfyGraphNode).getPromptThumbnails(); + if (thumbsToAdd) + thumbnails.push(...thumbsToAdd) + } + } + for (let i = 0; i < batchCount; i++) { - for (const node of this.lGraph._nodes_in_order) { + for (const node of this.lGraph.iterateNodesInOrderRecursive()) { + if (node.mode !== NodeMode.ALWAYS) + continue; + if ("beforeQueued" in node) { (node as ComfyGraphNode).beforeQueued(tag); } @@ -742,7 +761,8 @@ export default class ComfyApp { extra_pnginfo: { workflow: p.workflow }, - subgraphs: [tag] + subgraphs: [tag], + thumbnails } let error = null; @@ -871,7 +891,7 @@ export default class ComfyApp { const isComfyInput = isComfyComboInput(input) const isComfyCombo = isComfyComboNode(inputNode) - console.debug("[refreshComboInNodes] CHECK", backendNode.type, input.name, "isComfyCombo", isComfyCombo, "isComfyInput", isComfyInput) + // console.debug("[refreshComboInNodes] CHECK", backendNode.type, input.name, "isComfyCombo", isComfyCombo, "isComfyInput", isComfyInput) return isComfyCombo && isComfyInput }); diff --git a/src/lib/components/ComfyProperties.svelte b/src/lib/components/ComfyProperties.svelte index 164f37c..7e432a3 100644 --- a/src/lib/components/ComfyProperties.svelte +++ b/src/lib/components/ComfyProperties.svelte @@ -480,10 +480,5 @@ padding: 0.5em; */ } - :global(input[type=text]:disabled) { - @include disable-input; - } - :global(textarea:disabled) { - @include disable-input; - } + @include disable-inputs; diff --git a/src/lib/components/ComfyQueue.svelte b/src/lib/components/ComfyQueue.svelte index 54d7625..9e5c30c 100644 --- a/src/lib/components/ComfyQueue.svelte +++ b/src/lib/components/ComfyQueue.svelte @@ -2,6 +2,7 @@ import queueState, { type CompletedQueueEntry, type QueueEntry, type QueueEntryStatus } from "$lib/stores/queueState"; import ProgressBar from "./ProgressBar.svelte"; import Spinner from "./Spinner.svelte"; + import PromptDisplay from "./PromptDisplay.svelte"; import { ListIcon as List } from "svelte-feather-icons"; import { convertComfyOutputToComfyURL, convertFilenameToComfyURL, getNodeInfo } from "$lib/utils" import type { Writable } from "svelte/store"; @@ -10,6 +11,7 @@ import { Button } from "@gradio/button"; import type ComfyApp from "./ComfyApp"; import { tick } from "svelte"; + import Modal from "./Modal.svelte"; let queuePending: Writable | null = null; let queueRunning: Writable | null = null; @@ -17,6 +19,7 @@ let queueList: HTMLDivElement | null = null; type QueueUIEntry = { + entry: QueueEntry, message: string, submessage: string, date?: string, @@ -49,21 +52,17 @@ updateFromHistory(); } - function convertEntry(entry: QueueEntry): QueueUIEntry { - const images = Object.values(entry.outputs).flatMap(o => o.images) - .map(convertComfyOutputToComfyURL); + function formatDate(date: Date): string { + const time = date.toLocaleString('en-US', { hour: 'numeric', minute: 'numeric', hour12: true }); + const day = date.toLocaleString('en-US', { month: '2-digit', day: '2-digit', year: 'numeric' }).replace(',', ''); + return [time, day].join(", ") + } - let date = null; - if (entry.queuedAt) { - const options: Intl.DateTimeFormatOptions = { - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: 'numeric', - minute: 'numeric' - }; - const dateTimeFormat = new Intl.DateTimeFormat('en-US', options); - date = dateTimeFormat.format(entry.queuedAt); + function convertEntry(entry: QueueEntry): QueueUIEntry { + let date = entry.finishedAt || entry.queuedAt; + let dateStr = null; + if (date) { + dateStr = formatDate(date); } let message = "Prompt"; @@ -72,24 +71,43 @@ let submessage = `Nodes: ${Object.keys(entry.prompt).length}` if (Object.keys(entry.outputs).length > 0) { - submessage = `Images: ${Object.keys(entry.outputs).length}` + const imageCount = Object.values(entry.outputs).flatMap(o => o.images).length + submessage = `Images: ${imageCount}` } return { + entry, message, submessage, - date, + dateStr, status: "pending", - images, + images: [] } } + function convertPendingEntry(entry: QueueEntry): QueueUIEntry { + const result = convertEntry(entry); + + const thumbnails = entry.extraData?.thumbnails + if (thumbnails) { + result.images = thumbnails.map(convertComfyOutputToComfyURL); + } + + return result; + } + function convertCompletedEntry(entry: CompletedQueueEntry): QueueUIEntry { const result = convertEntry(entry.entry); result.status = entry.status; + const images = Object.values(entry.entry.outputs).flatMap(o => o.images) + .map(convertComfyOutputToComfyURL); + result.images = images + if (entry.message) result.submessage = entry.message + else if (entry.status === "interrupted" || entry.status === "all_cached") + result.submessage = "Prompt was interrupted." if (entry.error) result.details = entry.error @@ -97,7 +115,7 @@ } async function updateFromQueue() { - _entries = $queuePending.map(convertEntry).reverse(); // newest entries appear at the top + _entries = $queuePending.map(convertPendingEntry).reverse(); // newest entries appear at the top if (queueList) { await tick(); // Wait for list size to be recalculated queueList.scroll({ top: queueList.scrollHeight }) @@ -108,28 +126,45 @@ async function updateFromHistory() { _entries = $queueCompleted.map(convertCompletedEntry).reverse(); if (queueList) { - await tick(); // Wait for list size to be recalculated queueList.scrollTo(0, 0); } console.warn("[ComfyQueue] BUILDHISTORY", _entries, $queueCompleted) } - function showLightbox(entry: QueueUIEntry, e: Event) { + function showLightbox(entry: QueueUIEntry, index: number, e: Event) { e.preventDefault() if (!entry.images) return - ImageViewer.instance.showModal(entry.images, 0) + ImageViewer.instance.showModal(entry.images, index); + + e.stopPropagation() } async function interrupt() { + if ($queueState.isInterrupting) + return + const app = (window as any).app as ComfyApp; if (!app || !app.api) return; - await app.api.interrupt(); + await app.api.interrupt() + .then(() => { + queueState.update(s => { s.isInterrupting = true; return s }) + }); } + let showModal = false; + let selectedPrompt = null; + function showPrompt(entry: QueueUIEntry, e: MouseEvent) { + selectedPrompt = entry.entry.prompt; + showModal = true; + } + + $: if(!showModal) + selectedPrompt = null; + let queued = false $: queued = Boolean($queueState.runningNodeID || $queueState.progress); @@ -137,14 +172,32 @@ $: inProgress = typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0; + + +
+

Prompt Details

+
+ {#if selectedPrompt} + + {/if} +
+
{#if _entries.length > 0} {#each _entries as entry} -
+
showPrompt(entry, e)}> {#if entry.images.length > 0} -
showLightbox(entry, e)}> - thumbnail +
+ {#each entry.images.slice(0, 4) as image, i} +
+ showLightbox(entry, i, e)} + src={image} + alt="thumbnail" /> +
+ {/each}
{:else} @@ -158,7 +211,7 @@
-
+
{#if entry.date != null} {entry.date} @@ -212,7 +265,10 @@
-
@@ -226,6 +282,13 @@ $mode-buttons-height: 30px; $queue-height: calc(100vh - #{$pending-height} - #{$mode-buttons-height} - #{$bottom-bar-height}); + .prompt-modal-header { + padding-left: 0.2rem; + + h1 { + font-size: large; + } +} .queue { color: var(--body-text-color); } @@ -275,16 +338,20 @@ border-top: 1px solid var(--table-border-color); background: var(--panel-background-fill); + &:hover:not(:has(img:hover)) { + background: var(--block-background-fill); + } + &.success { /* background: green; */ } &.error { background: red; } - &.all_cached { + &.all_cached, &.interrupted { filter: brightness(80%); - background: var(--neutral-600); - color: var(--neutral-300); + background: var(--comfy-disabled-textbox-background-fill); + color: var(--comfy-disable-textbox-text-color); } &.running { /* background: lightblue; */ @@ -294,29 +361,40 @@ } } - .queue-entry-images { - height: 100%; - aspect-ratio: 1/1; - margin: auto; + .queue-entry-rest { + width: 100%; + position: relative; + + &.all_cached, &.interrupted { + filter: brightness(80%); + color: var(--neutral-300); + } + } + + $thumbnails-size: 12rem; + + .queue-entry-images { + --cols: 1; + margin: auto; + width: calc($thumbnails-size * 2); + display: grid; + display: inline-grid; + grid-template-columns: repeat(var(--cols), 1fr); + grid-template-rows: repeat(var(--cols), 1fr); + column-gap: 1px; + row-gap: 1px; + vertical-align: top; + + img { + aspect-ratio: 1 / 1; + object-fit: cover; - > .queue-entry-image { - filter: none; &:hover { filter: brightness(120%) contrast(120%); } } } - .queue-entry-image-placeholder { - width: var(--size-20); - background: grey; - } - - .queue-entry-rest { - width: 100%; - position: relative; - } - .queue-entry-details { position: relative; padding: 1rem; @@ -378,6 +456,19 @@ } } + &:hover { + filter: brightness(85%); + } + &:active { + filter: brightness(50%) + } + &.mode-selected { + filter: brightness(80%) + } + } + + :global(.dark) .mode-button { + filter: none; &:hover { filter: brightness(120%); } diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte new file mode 100644 index 0000000..f0064a1 --- /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/PromptDisplay.svelte b/src/lib/components/PromptDisplay.svelte new file mode 100644 index 0000000..e76082c --- /dev/null +++ b/src/lib/components/PromptDisplay.svelte @@ -0,0 +1,165 @@ + + +
+ + {#each Object.entries(prompt) as [nodeID, inputs], i} + {@const classType = inputs.class_type} + {@const filtered = Object.entries(inputs.inputs).filter((i) => !isInputLink(i[1]))} + {#if filtered.length > 0} +
+ + + {#each filtered as [inputName, input]} + + Input: {inputName} + +
+ {#if isInputLink(input)} + Link {input[0]} -> {input[1]} + {:else if typeof input === "object"} + + + + + {:else if isMultiline(input)} + {@const lines = Math.max(countNewLines(input), input.length / splitLength)} + + {:else} + + {/if} +
+
+ {/each} +
+
+
+ {/if} + {/each} +
+
+ + 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/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts index 1250735..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, GalleryOutput } 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"; @@ -50,6 +50,13 @@ export default class ComfyGraphNode extends LGraphNode { */ 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 * indices, so that when a ComfyWidgetNode is connected to the input slot it diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts index 4e2c9a6..13ee1a1 100644 --- a/src/lib/stores/queueState.ts +++ b/src/lib/stores/queueState.ts @@ -54,7 +54,8 @@ export type QueueState = { queueCompleted: Writable, queueRemaining: number | "X" | null; runningNodeID: number | null; - progress: Progress | null + progress: Progress | null, + isInterrupting: boolean } type WritableQueueStateStore = Writable & QueueStateOps; @@ -64,7 +65,8 @@ const store: Writable = writable({ queueCompleted: writable([]), queueRemaining: null, runningNodeID: null, - progress: null + progress: null, + isInterrupting: false }) function toQueueEntry(resp: ComfyAPIHistoryItem): QueueEntry { @@ -209,6 +211,7 @@ function executionCached(promptID: PromptID, nodes: 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 @@ -248,6 +251,7 @@ function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromp } s.queuePending.update(qp => { qp.push(entry); return qp }) console.debug("[queueState] ADD PROMPT", promptID) + s.isInterrupting = false; return s }) } 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/scss/global.scss b/src/scss/global.scss index 976873a..18fd3e8 100644 --- a/src/scss/global.scss +++ b/src/scss/global.scss @@ -14,7 +14,11 @@ body { :root { --color-blue-500: #3985f5; - --comfy-accent-soft: var(--neutral-400); + --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); @@ -34,6 +38,10 @@ body { 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); @@ -56,13 +64,32 @@ body { } @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);