separate upload-draw component #3
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -10,6 +10,9 @@
 | 
			
		|||
      "dependencies": {
 | 
			
		||||
        "@heroicons/react": "^2.0.18",
 | 
			
		||||
        "@hookform/resolvers": "^3.3.2",
 | 
			
		||||
        "@mantine/core": "^8.2.1",
 | 
			
		||||
        "@mantine/hooks": "^8.2.1",
 | 
			
		||||
        "@mantine/notifications": "^8.2.1",
 | 
			
		||||
        "@radix-ui/react-accordion": "^1.1.2",
 | 
			
		||||
        "@radix-ui/react-alert-dialog": "^1.0.5",
 | 
			
		||||
        "@radix-ui/react-context-menu": "^2.1.5",
 | 
			
		||||
| 
						 | 
				
			
			@ -1043,28 +1046,46 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@floating-ui/core": {
 | 
			
		||||
      "version": "1.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==",
 | 
			
		||||
      "version": "1.7.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
 | 
			
		||||
      "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@floating-ui/utils": "^0.1.3"
 | 
			
		||||
        "@floating-ui/utils": "^0.2.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@floating-ui/dom": {
 | 
			
		||||
      "version": "1.5.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz",
 | 
			
		||||
      "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==",
 | 
			
		||||
      "version": "1.7.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.3.tgz",
 | 
			
		||||
      "integrity": "sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@floating-ui/core": "^1.4.2",
 | 
			
		||||
        "@floating-ui/utils": "^0.1.3"
 | 
			
		||||
        "@floating-ui/core": "^1.7.3",
 | 
			
		||||
        "@floating-ui/utils": "^0.2.10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@floating-ui/react": {
 | 
			
		||||
      "version": "0.26.28",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
 | 
			
		||||
      "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@floating-ui/react-dom": "^2.1.2",
 | 
			
		||||
        "@floating-ui/utils": "^0.2.8",
 | 
			
		||||
        "tabbable": "^6.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": ">=16.8.0",
 | 
			
		||||
        "react-dom": ">=16.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@floating-ui/react-dom": {
 | 
			
		||||
      "version": "2.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==",
 | 
			
		||||
      "version": "2.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@floating-ui/dom": "^1.5.1"
 | 
			
		||||
        "@floating-ui/dom": "^1.7.3"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": ">=16.8.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -1072,9 +1093,10 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@floating-ui/utils": {
 | 
			
		||||
      "version": "0.1.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz",
 | 
			
		||||
      "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A=="
 | 
			
		||||
      "version": "0.2.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
 | 
			
		||||
      "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@heroicons/react": {
 | 
			
		||||
      "version": "2.0.18",
 | 
			
		||||
| 
						 | 
				
			
			@ -1168,6 +1190,105 @@
 | 
			
		|||
        "@jridgewell/sourcemap-codec": "^1.4.14"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/core": {
 | 
			
		||||
      "version": "8.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-+WnqII3zSD72F+7GLcRXZ/MyO4r7A4JM/yWkCSclxR4LeRQ5bd4HBRXkvXRMZP28UeL2b5X9Re2Sig3KVGDBeQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@floating-ui/react": "^0.26.28",
 | 
			
		||||
        "clsx": "^2.1.1",
 | 
			
		||||
        "react-number-format": "^5.4.3",
 | 
			
		||||
        "react-remove-scroll": "^2.6.2",
 | 
			
		||||
        "react-textarea-autosize": "8.5.9",
 | 
			
		||||
        "type-fest": "^4.27.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@mantine/hooks": "8.2.2",
 | 
			
		||||
        "react": "^18.x || ^19.x",
 | 
			
		||||
        "react-dom": "^18.x || ^19.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/core/node_modules/clsx": {
 | 
			
		||||
      "version": "2.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/core/node_modules/react-remove-scroll": {
 | 
			
		||||
      "version": "2.7.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
 | 
			
		||||
      "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "react-remove-scroll-bar": "^2.3.7",
 | 
			
		||||
        "react-style-singleton": "^2.2.3",
 | 
			
		||||
        "tslib": "^2.1.0",
 | 
			
		||||
        "use-callback-ref": "^1.3.3",
 | 
			
		||||
        "use-sidecar": "^1.1.3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/core/node_modules/type-fest": {
 | 
			
		||||
      "version": "4.41.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
 | 
			
		||||
      "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
 | 
			
		||||
      "license": "(MIT OR CC0-1.0)",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=16"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/sindresorhus"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/hooks": {
 | 
			
		||||
      "version": "8.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-fjt0+pc1UxJIIUswu4ur72qVH+/UoFxyYmqWexuHJTOvuB86M//KUvXpFyhJcTdEENBHg2k1fyMpWmgg1VOZ5w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^18.x || ^19.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/notifications": {
 | 
			
		||||
      "version": "8.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-8.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-XThhzNomF6vQtGqEnjCP0nidP34uSTmNLDkyo7ITqVEe9/KAMqf6PcWZ20xShokVtXGE/L9fq1jOhdCZ9eLVxg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@mantine/store": "8.2.2",
 | 
			
		||||
        "react-transition-group": "4.4.5"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@mantine/core": "8.2.2",
 | 
			
		||||
        "@mantine/hooks": "8.2.2",
 | 
			
		||||
        "react": "^18.x || ^19.x",
 | 
			
		||||
        "react-dom": "^18.x || ^19.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mantine/store": {
 | 
			
		||||
      "version": "8.2.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mantine/store/-/store-8.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-4uvXAuCxPCOLRBgyy0tuIhm8cWsX8odcxVSc6lNWT5K0rT04gvB96I27MWThyGGLqB/BfON3VcBZ1dIMzt7k7w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^18.x || ^19.x"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@next/env": {
 | 
			
		||||
      "version": "14.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -2643,9 +2764,10 @@
 | 
			
		|||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@socket.io/component-emitter": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
 | 
			
		||||
      "version": "3.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@swc/core": {
 | 
			
		||||
      "version": "1.3.96",
 | 
			
		||||
| 
						 | 
				
			
			@ -3929,6 +4051,16 @@
 | 
			
		|||
        "node": ">=6.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/dom-helpers": {
 | 
			
		||||
      "version": "5.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.8.7",
 | 
			
		||||
        "csstype": "^3.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/electron-to-chromium": {
 | 
			
		||||
      "version": "1.4.588",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.588.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -3936,21 +4068,23 @@
 | 
			
		|||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/engine.io-client": {
 | 
			
		||||
      "version": "6.5.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
 | 
			
		||||
      "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
 | 
			
		||||
      "version": "6.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@socket.io/component-emitter": "~3.1.0",
 | 
			
		||||
        "debug": "~4.3.1",
 | 
			
		||||
        "engine.io-parser": "~5.2.1",
 | 
			
		||||
        "ws": "~8.11.0",
 | 
			
		||||
        "xmlhttprequest-ssl": "~2.0.0"
 | 
			
		||||
        "ws": "~8.17.1",
 | 
			
		||||
        "xmlhttprequest-ssl": "~2.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/engine.io-parser": {
 | 
			
		||||
      "version": "5.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==",
 | 
			
		||||
      "version": "5.2.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
 | 
			
		||||
      "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10.0.0"
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -4492,7 +4626,8 @@
 | 
			
		|||
    "node_modules/hamt_plus": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA=="
 | 
			
		||||
      "integrity": "sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/has-flag": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -4593,14 +4728,6 @@
 | 
			
		|||
        "node": ">=20.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/invariant": {
 | 
			
		||||
      "version": "2.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
 | 
			
		||||
      "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "loose-envify": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/is-binary-path": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -5327,6 +5454,17 @@
 | 
			
		|||
        "node": ">= 0.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/prop-types": {
 | 
			
		||||
      "version": "15.8.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
 | 
			
		||||
      "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "loose-envify": "^1.4.0",
 | 
			
		||||
        "object-assign": "^4.1.1",
 | 
			
		||||
        "react-is": "^16.13.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/proxy-from-env": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -5407,6 +5545,22 @@
 | 
			
		|||
        "react-dom": ">=16.8.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-is": {
 | 
			
		||||
      "version": "16.13.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
 | 
			
		||||
      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-number-format": {
 | 
			
		||||
      "version": "5.4.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.4.tgz",
 | 
			
		||||
      "integrity": "sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
 | 
			
		||||
        "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-photo-album": {
 | 
			
		||||
      "version": "2.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-photo-album/-/react-photo-album-2.3.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -5452,19 +5606,20 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-remove-scroll-bar": {
 | 
			
		||||
      "version": "2.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz",
 | 
			
		||||
      "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==",
 | 
			
		||||
      "version": "2.3.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
 | 
			
		||||
      "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "react-style-singleton": "^2.2.1",
 | 
			
		||||
        "react-style-singleton": "^2.2.2",
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
| 
						 | 
				
			
			@ -5473,20 +5628,20 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-style-singleton": {
 | 
			
		||||
      "version": "2.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
 | 
			
		||||
      "version": "2.2.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
 | 
			
		||||
      "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "get-nonce": "^1.0.0",
 | 
			
		||||
        "invariant": "^2.2.4",
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
| 
						 | 
				
			
			@ -5494,6 +5649,39 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-textarea-autosize": {
 | 
			
		||||
      "version": "8.5.9",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz",
 | 
			
		||||
      "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.20.13",
 | 
			
		||||
        "use-composed-ref": "^1.3.0",
 | 
			
		||||
        "use-latest": "^1.2.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-transition-group": {
 | 
			
		||||
      "version": "4.4.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
 | 
			
		||||
      "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
 | 
			
		||||
      "license": "BSD-3-Clause",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.5.5",
 | 
			
		||||
        "dom-helpers": "^5.0.1",
 | 
			
		||||
        "loose-envify": "^1.4.0",
 | 
			
		||||
        "prop-types": "^15.6.2"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": ">=16.6.0",
 | 
			
		||||
        "react-dom": ">=16.6.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-universal-interface": {
 | 
			
		||||
      "version": "0.6.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -5564,6 +5752,7 @@
 | 
			
		|||
      "version": "0.7.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/recoil/-/recoil-0.7.7.tgz",
 | 
			
		||||
      "integrity": "sha512-8Og5KPQW9LwC577Vc7Ug2P0vQshkv1y3zG3tSSkWMqkWSwHmE+by06L8JtnGocjW6gcCvfwB3YtrJG6/tWivNQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "hamt_plus": "1.0.2"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -5769,13 +5958,14 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/socket.io-client": {
 | 
			
		||||
      "version": "4.7.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz",
 | 
			
		||||
      "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==",
 | 
			
		||||
      "version": "4.8.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz",
 | 
			
		||||
      "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@socket.io/component-emitter": "~3.1.0",
 | 
			
		||||
        "debug": "~4.3.2",
 | 
			
		||||
        "engine.io-client": "~6.5.2",
 | 
			
		||||
        "engine.io-client": "~6.6.1",
 | 
			
		||||
        "socket.io-parser": "~4.2.4"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
| 
						 | 
				
			
			@ -5786,6 +5976,7 @@
 | 
			
		|||
      "version": "4.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
 | 
			
		||||
      "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@socket.io/component-emitter": "~3.1.0",
 | 
			
		||||
        "debug": "~4.3.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -5974,6 +6165,12 @@
 | 
			
		|||
        "url": "https://github.com/sponsors/ljharb"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tabbable": {
 | 
			
		||||
      "version": "6.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/tailwind-merge": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.0.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -6219,9 +6416,10 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-callback-ref": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==",
 | 
			
		||||
      "version": "1.3.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
 | 
			
		||||
      "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -6229,8 +6427,53 @@
 | 
			
		|||
        "node": ">=10"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-composed-ref": {
 | 
			
		||||
      "version": "1.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-isomorphic-layout-effect": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-latest": {
 | 
			
		||||
      "version": "1.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "use-isomorphic-layout-effect": "^1.1.1"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
| 
						 | 
				
			
			@ -6239,9 +6482,10 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-sidecar": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
 | 
			
		||||
      "version": "1.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "detect-node-es": "^1.1.0",
 | 
			
		||||
        "tslib": "^2.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -6250,8 +6494,8 @@
 | 
			
		|||
        "node": ">=10"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
| 
						 | 
				
			
			@ -6361,15 +6605,16 @@
 | 
			
		|||
      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/ws": {
 | 
			
		||||
      "version": "8.11.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
 | 
			
		||||
      "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
 | 
			
		||||
      "version": "8.17.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
 | 
			
		||||
      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "bufferutil": "^4.0.1",
 | 
			
		||||
        "utf-8-validate": "^5.0.2"
 | 
			
		||||
        "utf-8-validate": ">=5.0.2"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "bufferutil": {
 | 
			
		||||
| 
						 | 
				
			
			@ -6381,9 +6626,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/xmlhttprequest-ssl": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
 | 
			
		||||
      "version": "2.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.4.0"
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -6415,23 +6660,30 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/zod": {
 | 
			
		||||
      "version": "3.22.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
 | 
			
		||||
      "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
 | 
			
		||||
      "version": "3.25.76",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
 | 
			
		||||
      "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/sponsors/colinhacks"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/zundo": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zundo/-/zundo-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-XzKDyunmyxvQHKDjgTmOClOQscJAm5NAa1iEazR0DilvV/uwCjnDwlHJuJ+GmG/oj5RMjzsD0ptghZzjEj1w4g==",
 | 
			
		||||
      "version": "2.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/zundo/-/zundo-2.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-4GXYxXA17SIKYhVbWHdSEU04P697IMyVGXrC2TnzoyohEAWytFNOKqOp5gTGvaW93F/PM5Y0evbGtOPF0PWQwQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "individual",
 | 
			
		||||
        "url": "https://github.com/sponsors/charkour"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "zustand": "^4.3.0"
 | 
			
		||||
        "zustand": "^4.3.0 || ^5.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "zustand": {
 | 
			
		||||
          "optional": false
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/zustand": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,9 @@
 | 
			
		|||
    "preview": "vite preview"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@mantine/core": "^8.2.1",
 | 
			
		||||
		"@mantine/hooks": "^8.2.1",
 | 
			
		||||
		"@mantine/notifications": "^8.2.1",
 | 
			
		||||
    "@heroicons/react": "^2.0.18",
 | 
			
		||||
    "@hookform/resolvers": "^3.3.2",
 | 
			
		||||
    "@radix-ui/react-accordion": "^1.1.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,168 +1,10 @@
 | 
			
		|||
import { useCallback, useEffect, useRef } from "react";
 | 
			
		||||
import { UploadDraw } from "./features/upload-draw";
 | 
			
		||||
 | 
			
		||||
import useInputImage from "@/hooks/useInputImage";
 | 
			
		||||
import { keepGUIAlive } from "@/lib/utils";
 | 
			
		||||
import { getServerConfig } from "@/lib/api";
 | 
			
		||||
import Header from "@/components/Header";
 | 
			
		||||
import Workspace from "@/components/Workspace";
 | 
			
		||||
import FileSelect from "@/components/FileSelect";
 | 
			
		||||
import { Toaster } from "./components/ui/toaster";
 | 
			
		||||
import { useStore } from "./lib/states";
 | 
			
		||||
import { useWindowSize } from "react-use";
 | 
			
		||||
 | 
			
		||||
const SUPPORTED_FILE_TYPE = [
 | 
			
		||||
	"image/jpeg",
 | 
			
		||||
	"image/png",
 | 
			
		||||
	"image/webp",
 | 
			
		||||
	"image/bmp",
 | 
			
		||||
	"image/tiff",
 | 
			
		||||
];
 | 
			
		||||
function Home() {
 | 
			
		||||
	const [file, updateAppState, setServerConfig, setFile] = useStore(
 | 
			
		||||
		(state) => [
 | 
			
		||||
			state.file,
 | 
			
		||||
			state.updateAppState,
 | 
			
		||||
			state.setServerConfig,
 | 
			
		||||
			state.setFile,
 | 
			
		||||
		],
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const userInputImage = useInputImage();
 | 
			
		||||
 | 
			
		||||
	const windowSize = useWindowSize();
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		if (userInputImage) {
 | 
			
		||||
			setFile(userInputImage);
 | 
			
		||||
		}
 | 
			
		||||
	}, [userInputImage, setFile]);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		updateAppState({ windowSize });
 | 
			
		||||
	}, [windowSize]);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const fetchServerConfig = async () => {
 | 
			
		||||
			const serverConfig = await getServerConfig();
 | 
			
		||||
			setServerConfig(serverConfig);
 | 
			
		||||
			if (serverConfig.isDesktop) {
 | 
			
		||||
				// Keeping GUI Window Open
 | 
			
		||||
				keepGUIAlive();
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
		fetchServerConfig();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const dragCounter = useRef(0);
 | 
			
		||||
 | 
			
		||||
	const handleDrag = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const handleDragIn = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		dragCounter.current += 1;
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const handleDragOut = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		dragCounter.current -= 1;
 | 
			
		||||
		if (dragCounter.current > 0) return;
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const handleDrop = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
 | 
			
		||||
			if (event.dataTransfer.files.length > 1) {
 | 
			
		||||
				// setToastState({
 | 
			
		||||
				//   open: true,
 | 
			
		||||
				//   desc: "Please drag and drop only one file",
 | 
			
		||||
				//   state: "error",
 | 
			
		||||
				//   duration: 3000,
 | 
			
		||||
				// })
 | 
			
		||||
			} else {
 | 
			
		||||
				const dragFile = event.dataTransfer.files[0];
 | 
			
		||||
				const fileType = dragFile.type;
 | 
			
		||||
				if (SUPPORTED_FILE_TYPE.includes(fileType)) {
 | 
			
		||||
					setFile(dragFile);
 | 
			
		||||
				} else {
 | 
			
		||||
					// setToastState({
 | 
			
		||||
					//   open: true,
 | 
			
		||||
					//   desc: "Please drag and drop an image file",
 | 
			
		||||
					//   state: "error",
 | 
			
		||||
					//   duration: 3000,
 | 
			
		||||
					// })
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			event.dataTransfer.clearData();
 | 
			
		||||
		}
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const onPaste = useCallback((event: any) => {
 | 
			
		||||
		// TODO: when sd side panel open, ctrl+v not work
 | 
			
		||||
		// https://htmldom.dev/paste-an-image-from-the-clipboard/
 | 
			
		||||
		if (!event.clipboardData) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		const clipboardItems = event.clipboardData.items;
 | 
			
		||||
		const items: DataTransferItem[] = [].slice
 | 
			
		||||
			.call(clipboardItems)
 | 
			
		||||
			.filter((item: DataTransferItem) => {
 | 
			
		||||
				// Filter the image items only
 | 
			
		||||
				return item.type.indexOf("image") !== -1;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		if (items.length === 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
		// TODO: add confirm dialog
 | 
			
		||||
 | 
			
		||||
		const item = items[0];
 | 
			
		||||
		// Get the blob of image
 | 
			
		||||
		const blob = item.getAsFile();
 | 
			
		||||
		if (blob) {
 | 
			
		||||
			setFile(blob);
 | 
			
		||||
		}
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		window.addEventListener("dragenter", handleDragIn);
 | 
			
		||||
		window.addEventListener("dragleave", handleDragOut);
 | 
			
		||||
		window.addEventListener("dragover", handleDrag);
 | 
			
		||||
		window.addEventListener("drop", handleDrop);
 | 
			
		||||
		window.addEventListener("paste", onPaste);
 | 
			
		||||
		return function cleanUp() {
 | 
			
		||||
			window.removeEventListener("dragenter", handleDragIn);
 | 
			
		||||
			window.removeEventListener("dragleave", handleDragOut);
 | 
			
		||||
			window.removeEventListener("dragover", handleDrag);
 | 
			
		||||
			window.removeEventListener("drop", handleDrop);
 | 
			
		||||
			window.removeEventListener("paste", onPaste);
 | 
			
		||||
		};
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
 | 
			
		||||
			<Toaster />
 | 
			
		||||
			<Header />
 | 
			
		||||
			<Workspace />
 | 
			
		||||
			{!file ? (
 | 
			
		||||
				<FileSelect
 | 
			
		||||
					onSelection={async (f) => {
 | 
			
		||||
						setFile(f);
 | 
			
		||||
					}}
 | 
			
		||||
				/>
 | 
			
		||||
			) : (
 | 
			
		||||
				<></>
 | 
			
		||||
			)}
 | 
			
		||||
		</main>
 | 
			
		||||
		<div>
 | 
			
		||||
			<UploadDraw />
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,175 @@
 | 
			
		|||
import { useCallback, useEffect, useRef } from "react";
 | 
			
		||||
import { useStore } from "./lib/states";
 | 
			
		||||
 | 
			
		||||
import useInputImage from "./hooks/useInputImage";
 | 
			
		||||
import { keepGUIAlive } from "./lib/utils";
 | 
			
		||||
import { getServerConfig } from "./lib/api";
 | 
			
		||||
 | 
			
		||||
import Header from "./components/Header";
 | 
			
		||||
import FileSelect from "./components/FileSelect";
 | 
			
		||||
import Editor from "./components/Editor";
 | 
			
		||||
import ImageSize from "./components/ImageSize";
 | 
			
		||||
 | 
			
		||||
import { useWindowSize } from "react-use";
 | 
			
		||||
 | 
			
		||||
const SUPPORTED_FILE_TYPE = [
 | 
			
		||||
	"image/jpeg",
 | 
			
		||||
	"image/png",
 | 
			
		||||
	"image/webp",
 | 
			
		||||
	"image/bmp",
 | 
			
		||||
	"image/tiff",
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const UploadDraw = () => {
 | 
			
		||||
	const [file, updateAppState, setServerConfig, setFile] = useStore(
 | 
			
		||||
		(state) => [
 | 
			
		||||
			state.file,
 | 
			
		||||
			state.updateAppState,
 | 
			
		||||
			state.setServerConfig,
 | 
			
		||||
			state.setFile,
 | 
			
		||||
		],
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const userInputImage = useInputImage();
 | 
			
		||||
 | 
			
		||||
	const windowSize = useWindowSize();
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		if (userInputImage) {
 | 
			
		||||
			setFile(userInputImage);
 | 
			
		||||
		}
 | 
			
		||||
	}, [userInputImage, setFile]);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		updateAppState({ windowSize });
 | 
			
		||||
	}, [windowSize]);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const fetchServerConfig = async () => {
 | 
			
		||||
			const serverConfig = await getServerConfig();
 | 
			
		||||
			setServerConfig(serverConfig);
 | 
			
		||||
			if (serverConfig.isDesktop) {
 | 
			
		||||
				// Keeping GUI Window Open
 | 
			
		||||
				keepGUIAlive();
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
		fetchServerConfig();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const dragCounter = useRef(0);
 | 
			
		||||
 | 
			
		||||
	const handleDrag = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const handleDragIn = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		dragCounter.current += 1;
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const handleDragOut = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		dragCounter.current -= 1;
 | 
			
		||||
		if (dragCounter.current > 0) return;
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const handleDrop = useCallback((event: any) => {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
		if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
 | 
			
		||||
			if (event.dataTransfer.files.length > 1) {
 | 
			
		||||
				// setToastState({
 | 
			
		||||
				//   open: true,
 | 
			
		||||
				//   desc: "Please drag and drop only one file",
 | 
			
		||||
				//   state: "error",
 | 
			
		||||
				//   duration: 3000,
 | 
			
		||||
				// })
 | 
			
		||||
			} else {
 | 
			
		||||
				const dragFile = event.dataTransfer.files[0];
 | 
			
		||||
				const fileType = dragFile.type;
 | 
			
		||||
				if (SUPPORTED_FILE_TYPE.includes(fileType)) {
 | 
			
		||||
					setFile(dragFile);
 | 
			
		||||
				} else {
 | 
			
		||||
					// setToastState({
 | 
			
		||||
					//   open: true,
 | 
			
		||||
					//   desc: "Please drag and drop an image file",
 | 
			
		||||
					//   state: "error",
 | 
			
		||||
					//   duration: 3000,
 | 
			
		||||
					// })
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			event.dataTransfer.clearData();
 | 
			
		||||
		}
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	const onPaste = useCallback((event: any) => {
 | 
			
		||||
		// TODO: when sd side panel open, ctrl+v not work
 | 
			
		||||
		// https://htmldom.dev/paste-an-image-from-the-clipboard/
 | 
			
		||||
		if (!event.clipboardData) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		const clipboardItems = event.clipboardData.items;
 | 
			
		||||
		const items: DataTransferItem[] = [].slice
 | 
			
		||||
			.call(clipboardItems)
 | 
			
		||||
			.filter((item: DataTransferItem) => {
 | 
			
		||||
				// Filter the image items only
 | 
			
		||||
				return item.type.indexOf("image") !== -1;
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
		if (items.length === 0) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		event.stopPropagation();
 | 
			
		||||
 | 
			
		||||
		// TODO: add confirm dialog
 | 
			
		||||
 | 
			
		||||
		const item = items[0];
 | 
			
		||||
		// Get the blob of image
 | 
			
		||||
		const blob = item.getAsFile();
 | 
			
		||||
		if (blob) {
 | 
			
		||||
			setFile(blob);
 | 
			
		||||
		}
 | 
			
		||||
	}, []);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		window.addEventListener("dragenter", handleDragIn);
 | 
			
		||||
		window.addEventListener("dragleave", handleDragOut);
 | 
			
		||||
		window.addEventListener("dragover", handleDrag);
 | 
			
		||||
		window.addEventListener("drop", handleDrop);
 | 
			
		||||
		window.addEventListener("paste", onPaste);
 | 
			
		||||
		return function cleanUp() {
 | 
			
		||||
			window.removeEventListener("dragenter", handleDragIn);
 | 
			
		||||
			window.removeEventListener("dragleave", handleDragOut);
 | 
			
		||||
			window.removeEventListener("dragover", handleDrag);
 | 
			
		||||
			window.removeEventListener("drop", handleDrop);
 | 
			
		||||
			window.removeEventListener("paste", onPaste);
 | 
			
		||||
		};
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<main className="flex min-h-screen flex-col items-center justify-between w-full bg-[radial-gradient(circle_at_1px_1px,_#8e8e8e8e_1px,_transparent_0)] [background-size:20px_20px] bg-repeat">
 | 
			
		||||
			<Header />
 | 
			
		||||
			<div className="flex gap-3 absolute top-[68px] left-[24px] items-center">
 | 
			
		||||
				<ImageSize />
 | 
			
		||||
			</div>
 | 
			
		||||
			{file ? <Editor file={file} /> : <></>}
 | 
			
		||||
 | 
			
		||||
			{!file ? (
 | 
			
		||||
				<FileSelect
 | 
			
		||||
					onSelection={async (f) => {
 | 
			
		||||
						setFile(f);
 | 
			
		||||
					}}
 | 
			
		||||
				/>
 | 
			
		||||
			) : (
 | 
			
		||||
				<></>
 | 
			
		||||
			)}
 | 
			
		||||
		</main>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default UploadDraw;
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
import { useState } from "react";
 | 
			
		||||
import useResolution from "../hooks/useResolution";
 | 
			
		||||
 | 
			
		||||
type FileSelectProps = {
 | 
			
		||||
	onSelection: (file: File) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function FileSelect(props: FileSelectProps) {
 | 
			
		||||
	const { onSelection } = props;
 | 
			
		||||
 | 
			
		||||
	const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`);
 | 
			
		||||
 | 
			
		||||
	const resolution = useResolution();
 | 
			
		||||
 | 
			
		||||
	function onFileSelected(file: File) {
 | 
			
		||||
		if (!file) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		// Skip non-image files
 | 
			
		||||
		const isImage = file.type.match("image.*");
 | 
			
		||||
		if (!isImage) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		try {
 | 
			
		||||
			// Check if file is larger than 20mb
 | 
			
		||||
			if (file.size > 20 * 1024 * 1024) {
 | 
			
		||||
				throw new Error("file too large");
 | 
			
		||||
			}
 | 
			
		||||
			onSelection(file);
 | 
			
		||||
		} catch (e) {
 | 
			
		||||
			// eslint-disable-next-line
 | 
			
		||||
			alert(`error: ${(e as any).message}`);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="absolute flex w-screen h-screen justify-center items-center pointer-events-none">
 | 
			
		||||
			<label
 | 
			
		||||
				htmlFor={uploadElemId}
 | 
			
		||||
				className="grid border-[2px] border-[dashed] bg-[var(--mantine-color-body)] rounded-lg min-w-[600px] hover:bg-[#1971c2] hover:text-[#1971c2]-foreground pointer-events-auto"
 | 
			
		||||
			>
 | 
			
		||||
				<div
 | 
			
		||||
					className="grid p-16 w-full h-full"
 | 
			
		||||
					onDragOver={(ev) => {
 | 
			
		||||
						ev.stopPropagation();
 | 
			
		||||
						ev.preventDefault();
 | 
			
		||||
					}}
 | 
			
		||||
				>
 | 
			
		||||
					<input
 | 
			
		||||
						className="hidden"
 | 
			
		||||
						id={uploadElemId}
 | 
			
		||||
						name={uploadElemId}
 | 
			
		||||
						type="file"
 | 
			
		||||
						onChange={(ev) => {
 | 
			
		||||
							const file = ev.currentTarget.files?.[0];
 | 
			
		||||
							if (file) {
 | 
			
		||||
								onFileSelected(file);
 | 
			
		||||
							}
 | 
			
		||||
						}}
 | 
			
		||||
						accept="image/png, image/jpeg"
 | 
			
		||||
					/>
 | 
			
		||||
					<p className="text-center">
 | 
			
		||||
						{resolution === "desktop"
 | 
			
		||||
							? "Click here or drag an image file"
 | 
			
		||||
							: "Tap here to load your picture"}
 | 
			
		||||
					</p>
 | 
			
		||||
				</div>
 | 
			
		||||
			</label>
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,87 @@
 | 
			
		|||
import { RotateCw, Image } from "lucide-react";
 | 
			
		||||
import { ActionIcon, Tooltip } from "@mantine/core";
 | 
			
		||||
import { useStore } from "../lib/states";
 | 
			
		||||
 | 
			
		||||
const Header = () => {
 | 
			
		||||
	const [
 | 
			
		||||
		file,
 | 
			
		||||
		isInpainting,
 | 
			
		||||
		model,
 | 
			
		||||
		setFile,
 | 
			
		||||
		runInpainting,
 | 
			
		||||
		showPrevMask,
 | 
			
		||||
		hidePrevMask,
 | 
			
		||||
	] = useStore((state) => [
 | 
			
		||||
		state.file,
 | 
			
		||||
		state.isInpainting,
 | 
			
		||||
		state.settings.model,
 | 
			
		||||
		state.setFile,
 | 
			
		||||
		state.runInpainting,
 | 
			
		||||
		state.showPrevMask,
 | 
			
		||||
		state.hidePrevMask,
 | 
			
		||||
	]);
 | 
			
		||||
 | 
			
		||||
	const handleRerunLastMask = () => {
 | 
			
		||||
		runInpainting();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const onRerunMouseEnter = () => {
 | 
			
		||||
		showPrevMask();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const onRerunMouseLeave = () => {
 | 
			
		||||
		hidePrevMask();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<header className="h-[60px] px-6 py-4 absolute top-[0] flex justify-between items-center w-full z-20 border-b backdrop-filter backdrop-blur-md">
 | 
			
		||||
			<div className="flex items-center gap-1">
 | 
			
		||||
				<Tooltip label="Upload image">
 | 
			
		||||
					<ActionIcon
 | 
			
		||||
						size={36}
 | 
			
		||||
						variant="default"
 | 
			
		||||
						disabled={isInpainting}
 | 
			
		||||
						component="label"
 | 
			
		||||
					>
 | 
			
		||||
						<Image />
 | 
			
		||||
 | 
			
		||||
						<input
 | 
			
		||||
							className="hidden"
 | 
			
		||||
							type="file"
 | 
			
		||||
							onChange={(ev) => {
 | 
			
		||||
								const newFile = ev.currentTarget.files?.[0];
 | 
			
		||||
								if (newFile) {
 | 
			
		||||
									setFile(newFile);
 | 
			
		||||
								}
 | 
			
		||||
							}}
 | 
			
		||||
							accept="image/png, image/jpeg"
 | 
			
		||||
						/>
 | 
			
		||||
					</ActionIcon>
 | 
			
		||||
				</Tooltip>
 | 
			
		||||
 | 
			
		||||
				{file && !model.need_prompt ? (
 | 
			
		||||
					<Tooltip label="Rerun previous mask">
 | 
			
		||||
						<ActionIcon
 | 
			
		||||
							size={36}
 | 
			
		||||
							variant="default"
 | 
			
		||||
							disabled={isInpainting}
 | 
			
		||||
							onClick={handleRerunLastMask}
 | 
			
		||||
							onMouseEnter={onRerunMouseEnter}
 | 
			
		||||
							onMouseLeave={onRerunMouseLeave}
 | 
			
		||||
						>
 | 
			
		||||
							<div className="icon-button-icon-wrapper">
 | 
			
		||||
								<RotateCw />
 | 
			
		||||
							</div>
 | 
			
		||||
						</ActionIcon>
 | 
			
		||||
					</Tooltip>
 | 
			
		||||
				) : (
 | 
			
		||||
					<></>
 | 
			
		||||
				)}
 | 
			
		||||
			</div>
 | 
			
		||||
 | 
			
		||||
			<div className="flex gap-1"></div>
 | 
			
		||||
		</header>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Header;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
import { useStore } from "../lib/states";
 | 
			
		||||
 | 
			
		||||
const ImageSize = () => {
 | 
			
		||||
	const [imageWidth, imageHeight] = useStore((state) => [
 | 
			
		||||
		state.imageWidth,
 | 
			
		||||
		state.imageHeight,
 | 
			
		||||
	]);
 | 
			
		||||
 | 
			
		||||
	if (!imageWidth || !imageHeight) {
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="border bg-[var(--mantine-color-body)] rounded-lg px-2 py-[6px] z-10">
 | 
			
		||||
			{imageWidth}x{imageHeight}
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ImageSize;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
import { useStore } from "../lib/states";
 | 
			
		||||
import { useHotkeys } from "react-hotkeys-hook";
 | 
			
		||||
 | 
			
		||||
const useHotKey = (keys: string, callback: any, deps?: any[]) => {
 | 
			
		||||
	const disableShortCuts = useStore((state) => state.disableShortCuts);
 | 
			
		||||
 | 
			
		||||
	const ref = useHotkeys(
 | 
			
		||||
		keys,
 | 
			
		||||
		callback,
 | 
			
		||||
		{ enabled: !disableShortCuts },
 | 
			
		||||
		deps,
 | 
			
		||||
	);
 | 
			
		||||
	return ref;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default useHotKey;
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,24 @@
 | 
			
		|||
import { useEffect, useState } from "react"
 | 
			
		||||
 | 
			
		||||
function useImage(file: File | null): [HTMLImageElement, boolean] {
 | 
			
		||||
  const [image] = useState(new Image())
 | 
			
		||||
  const [isLoaded, setIsLoaded] = useState(false)
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!file) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    image.onload = () => {
 | 
			
		||||
      setIsLoaded(true)
 | 
			
		||||
    }
 | 
			
		||||
    setIsLoaded(false)
 | 
			
		||||
    image.src = URL.createObjectURL(file)
 | 
			
		||||
    return () => {
 | 
			
		||||
      image.onload = null
 | 
			
		||||
    }
 | 
			
		||||
  }, [file, image])
 | 
			
		||||
 | 
			
		||||
  return [image, isLoaded]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { useImage }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,41 @@
 | 
			
		|||
import { API_ENDPOINT } from "../lib/api";
 | 
			
		||||
import { useCallback, useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
export default function useInputImage() {
 | 
			
		||||
	const [inputImage, setInputImage] = useState<File | null>(null);
 | 
			
		||||
 | 
			
		||||
	const fetchInputImage = useCallback(() => {
 | 
			
		||||
		const headers = new Headers();
 | 
			
		||||
		headers.append("pragma", "no-cache");
 | 
			
		||||
		headers.append("cache-control", "no-cache");
 | 
			
		||||
 | 
			
		||||
		fetch(`${API_ENDPOINT}/inputimage`, { headers })
 | 
			
		||||
			.then(async (res) => {
 | 
			
		||||
				if (!res.ok) {
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				const filename = res.headers
 | 
			
		||||
					.get("content-disposition")
 | 
			
		||||
					?.split("filename=")[1]
 | 
			
		||||
					.split(";")[0];
 | 
			
		||||
 | 
			
		||||
				const data = await res.blob();
 | 
			
		||||
				if (data && data.type.startsWith("image")) {
 | 
			
		||||
					const userInput = new File(
 | 
			
		||||
						[data],
 | 
			
		||||
						filename !== undefined ? filename : "inputImage",
 | 
			
		||||
					);
 | 
			
		||||
					setInputImage(userInput);
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
			.catch((err) => {
 | 
			
		||||
				console.log(err);
 | 
			
		||||
			});
 | 
			
		||||
	}, [setInputImage]);
 | 
			
		||||
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		fetchInputImage();
 | 
			
		||||
	}, [fetchInputImage]);
 | 
			
		||||
 | 
			
		||||
	return inputImage;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,31 @@
 | 
			
		|||
import { useCallback, useEffect, useState } from 'react'
 | 
			
		||||
 | 
			
		||||
const useResolution = () => {
 | 
			
		||||
  const [width, setWidth] = useState(window.innerWidth)
 | 
			
		||||
 | 
			
		||||
  const windowSizeHandler = useCallback(() => {
 | 
			
		||||
    setWidth(window.innerWidth)
 | 
			
		||||
  }, [])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    window.addEventListener('resize', windowSizeHandler)
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      window.removeEventListener('resize', windowSizeHandler)
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  if (width < 768) {
 | 
			
		||||
    return 'mobile'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (width >= 768 && width < 1224) {
 | 
			
		||||
    return 'tablet'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (width >= 1224) {
 | 
			
		||||
    return 'desktop'
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default useResolution
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
export { default as UploadDraw } from "./UploadDraw";
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,248 @@
 | 
			
		|||
import {
 | 
			
		||||
	Filename,
 | 
			
		||||
	ModelInfo,
 | 
			
		||||
	PowerPaintTask,
 | 
			
		||||
	Rect,
 | 
			
		||||
	ServerConfig,
 | 
			
		||||
} from "./types";
 | 
			
		||||
import { Settings } from "./states";
 | 
			
		||||
import { convertToBase64, srcToFile } from "./utils";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
export const API_ENDPOINT = import.meta.env.DEV
 | 
			
		||||
	? import.meta.env.VITE_BACKEND + "/api/v1"
 | 
			
		||||
	: "/api/v1";
 | 
			
		||||
 | 
			
		||||
const api = axios.create({
 | 
			
		||||
	baseURL: API_ENDPOINT,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export default async function inpaint(
 | 
			
		||||
	imageFile: File,
 | 
			
		||||
	settings: Settings,
 | 
			
		||||
	croperRect: Rect,
 | 
			
		||||
	extenderState: Rect,
 | 
			
		||||
	mask: File | Blob,
 | 
			
		||||
	paintByExampleImage: File | null = null,
 | 
			
		||||
) {
 | 
			
		||||
	const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
	const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
	const exampleImageBase64 = paintByExampleImage
 | 
			
		||||
		? await convertToBase64(paintByExampleImage)
 | 
			
		||||
		: null;
 | 
			
		||||
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/inpaint`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			image: imageBase64,
 | 
			
		||||
			mask: maskBase64,
 | 
			
		||||
			ldm_steps: settings.ldmSteps,
 | 
			
		||||
			ldm_sampler: settings.ldmSampler,
 | 
			
		||||
			zits_wireframe: settings.zitsWireframe,
 | 
			
		||||
			cv2_flag: settings.cv2Flag,
 | 
			
		||||
			cv2_radius: settings.cv2Radius,
 | 
			
		||||
			hd_strategy: "Crop",
 | 
			
		||||
			hd_strategy_crop_triger_size: 640,
 | 
			
		||||
			hd_strategy_crop_margin: 128,
 | 
			
		||||
			hd_trategy_resize_imit: 2048,
 | 
			
		||||
			prompt: settings.prompt,
 | 
			
		||||
			negative_prompt: settings.negativePrompt,
 | 
			
		||||
			use_croper: settings.showCropper,
 | 
			
		||||
			croper_x: croperRect.x,
 | 
			
		||||
			croper_y: croperRect.y,
 | 
			
		||||
			croper_height: croperRect.height,
 | 
			
		||||
			croper_width: croperRect.width,
 | 
			
		||||
			use_extender: settings.showExtender,
 | 
			
		||||
			extender_x: extenderState.x,
 | 
			
		||||
			extender_y: extenderState.y,
 | 
			
		||||
			extender_height: extenderState.height,
 | 
			
		||||
			extender_width: extenderState.width,
 | 
			
		||||
			sd_mask_blur: settings.sdMaskBlur,
 | 
			
		||||
			sd_strength: settings.sdStrength,
 | 
			
		||||
			sd_steps: settings.sdSteps,
 | 
			
		||||
			sd_guidance_scale: settings.sdGuidanceScale,
 | 
			
		||||
			sd_sampler: settings.sdSampler,
 | 
			
		||||
			sd_seed: settings.seedFixed ? settings.seed : -1,
 | 
			
		||||
			sd_match_histograms: settings.sdMatchHistograms,
 | 
			
		||||
			sd_freeu: settings.enableFreeu,
 | 
			
		||||
			sd_freeu_config: settings.freeuConfig,
 | 
			
		||||
			sd_lcm_lora: settings.enableLCMLora,
 | 
			
		||||
			paint_by_example_example_image: exampleImageBase64,
 | 
			
		||||
			p2p_image_guidance_scale: settings.p2pImageGuidanceScale,
 | 
			
		||||
			enable_controlnet: settings.enableControlnet,
 | 
			
		||||
			controlnet_conditioning_scale: settings.controlnetConditioningScale,
 | 
			
		||||
			controlnet_method: settings.controlnetMethod
 | 
			
		||||
				? settings.controlnetMethod
 | 
			
		||||
				: "",
 | 
			
		||||
			powerpaint_task: settings.showExtender
 | 
			
		||||
				? PowerPaintTask.outpainting
 | 
			
		||||
				: settings.powerpaintTask,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return {
 | 
			
		||||
			blob: URL.createObjectURL(blob),
 | 
			
		||||
			seed: res.headers.get("X-Seed"),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
	const errors = await res.json();
 | 
			
		||||
	throw new Error(`Something went wrong: ${errors.errors}`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getServerConfig(): Promise<ServerConfig> {
 | 
			
		||||
	const res = await api.get(`/server-config`);
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function switchModel(name: string): Promise<ModelInfo> {
 | 
			
		||||
	const res = await api.post(`/model`, { name });
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function switchPluginModel(
 | 
			
		||||
	plugin_name: string,
 | 
			
		||||
	model_name: string,
 | 
			
		||||
) {
 | 
			
		||||
	return api.post(`/switch_plugin_model`, { plugin_name, model_name });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function currentModel(): Promise<ModelInfo> {
 | 
			
		||||
	const res = await api.get("/model");
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function runPlugin(
 | 
			
		||||
	genMask: boolean,
 | 
			
		||||
	name: string,
 | 
			
		||||
	imageFile: File,
 | 
			
		||||
	upscale?: number,
 | 
			
		||||
	clicks?: number[][],
 | 
			
		||||
) {
 | 
			
		||||
	const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
	const p = genMask ? "run_plugin_gen_mask" : "run_plugin_gen_image";
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/${p}`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			name,
 | 
			
		||||
			image: imageBase64,
 | 
			
		||||
			upscale,
 | 
			
		||||
			clicks,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return { blob: URL.createObjectURL(blob) };
 | 
			
		||||
	}
 | 
			
		||||
	const errMsg = await res.json();
 | 
			
		||||
	throw new Error(errMsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getMediaFile(tab: string, filename: string) {
 | 
			
		||||
	const res = await fetch(
 | 
			
		||||
		`${API_ENDPOINT}/media_file?tab=${tab}&filename=${encodeURIComponent(
 | 
			
		||||
			filename,
 | 
			
		||||
		)}`,
 | 
			
		||||
		{
 | 
			
		||||
			method: "GET",
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		const file = new File([blob], filename, {
 | 
			
		||||
			type: res.headers.get("Content-Type") ?? "image/png",
 | 
			
		||||
		});
 | 
			
		||||
		return file;
 | 
			
		||||
	}
 | 
			
		||||
	const errMsg = await res.json();
 | 
			
		||||
	throw new Error(errMsg.errors);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getMedias(tab: string): Promise<Filename[]> {
 | 
			
		||||
	const res = await api.get(`medias`, { params: { tab } });
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function downloadToOutput(
 | 
			
		||||
	image: HTMLImageElement,
 | 
			
		||||
	filename: string,
 | 
			
		||||
	mimeType: string,
 | 
			
		||||
) {
 | 
			
		||||
	const file = await srcToFile(image.src, filename, mimeType);
 | 
			
		||||
	const fd = new FormData();
 | 
			
		||||
	fd.append("file", file);
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		const res = await fetch(`${API_ENDPOINT}/save_image`, {
 | 
			
		||||
			method: "POST",
 | 
			
		||||
			body: fd,
 | 
			
		||||
		});
 | 
			
		||||
		if (!res.ok) {
 | 
			
		||||
			const errMsg = await res.text();
 | 
			
		||||
			throw new Error(errMsg);
 | 
			
		||||
		}
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		throw new Error(`Something went wrong: ${error}`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getSamplers(): Promise<string[]> {
 | 
			
		||||
	const res = await api.post("/samplers");
 | 
			
		||||
	return res.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function postAdjustMask(
 | 
			
		||||
	mask: File | Blob,
 | 
			
		||||
	operate: "expand" | "shrink" | "reverse",
 | 
			
		||||
	kernel_size: number,
 | 
			
		||||
) {
 | 
			
		||||
	const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/adjust_mask`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			mask: maskBase64,
 | 
			
		||||
			operate: operate,
 | 
			
		||||
			kernel_size: kernel_size,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return blob;
 | 
			
		||||
	}
 | 
			
		||||
	const errMsg = await res.json();
 | 
			
		||||
	throw new Error(errMsg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function submitMask(imageFile: File, mask: File | Blob) {
 | 
			
		||||
	const imageBase64 = await convertToBase64(imageFile);
 | 
			
		||||
	const maskBase64 = await convertToBase64(mask);
 | 
			
		||||
 | 
			
		||||
	const res = await fetch(`${API_ENDPOINT}/submit-mask`, {
 | 
			
		||||
		method: "POST",
 | 
			
		||||
		headers: {
 | 
			
		||||
			"Content-Type": "application/json",
 | 
			
		||||
		},
 | 
			
		||||
		body: JSON.stringify({
 | 
			
		||||
			image: imageBase64,
 | 
			
		||||
			mask: maskBase64,
 | 
			
		||||
		}),
 | 
			
		||||
	});
 | 
			
		||||
	if (res.ok) {
 | 
			
		||||
		const blob = await res.blob();
 | 
			
		||||
		return {
 | 
			
		||||
			blob: URL.createObjectURL(blob),
 | 
			
		||||
			seed: res.headers.get("X-Seed"),
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
	// const errors = await res.json();
 | 
			
		||||
	throw new Error(`Submit successfull.`);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
export const ACCENT_COLOR = "#ffcc00bb"
 | 
			
		||||
export const DEFAULT_BRUSH_SIZE = 40
 | 
			
		||||
export const MIN_BRUSH_SIZE = 3
 | 
			
		||||
export const MAX_BRUSH_SIZE = 200
 | 
			
		||||
export const MODEL_TYPE_INPAINT = "inpaint"
 | 
			
		||||
export const MODEL_TYPE_DIFFUSERS_SD = "diffusers_sd"
 | 
			
		||||
export const MODEL_TYPE_DIFFUSERS_SDXL = "diffusers_sdxl"
 | 
			
		||||
export const MODEL_TYPE_DIFFUSERS_SD_INPAINT = "diffusers_sd_inpaint"
 | 
			
		||||
export const MODEL_TYPE_DIFFUSERS_SDXL_INPAINT = "diffusers_sdxl_inpaint"
 | 
			
		||||
export const MODEL_TYPE_OTHER = "diffusers_other"
 | 
			
		||||
export const BRUSH_COLOR = "#ffcc00bb"
 | 
			
		||||
 | 
			
		||||
export const LDM = "ldm"
 | 
			
		||||
export const CV2 = "cv2"
 | 
			
		||||
 | 
			
		||||
export const PAINT_BY_EXAMPLE = "Fantasy-Studio/Paint-by-Example"
 | 
			
		||||
export const INSTRUCT_PIX2PIX = "timbrooks/instruct-pix2pix"
 | 
			
		||||
export const KANDINSKY_2_2 = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
 | 
			
		||||
export const POWERPAINT = "Sanster/PowerPaint-V1-stable-diffusion-inpainting"
 | 
			
		||||
export const ANYTEXT = "Sanster/AnyText"
 | 
			
		||||
 | 
			
		||||
export const DEFAULT_NEGATIVE_PROMPT =
 | 
			
		||||
  "out of frame, lowres, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, disfigured, gross proportions, malformed limbs, watermark, signature"
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -0,0 +1,136 @@
 | 
			
		|||
export interface Filename {
 | 
			
		||||
  name: string
 | 
			
		||||
  height: number
 | 
			
		||||
  width: number
 | 
			
		||||
  ctime: number
 | 
			
		||||
  mtime: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PluginInfo {
 | 
			
		||||
  name: string
 | 
			
		||||
  support_gen_image: boolean
 | 
			
		||||
  support_gen_mask: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ServerConfig {
 | 
			
		||||
  plugins: PluginInfo[]
 | 
			
		||||
  modelInfos: ModelInfo[]
 | 
			
		||||
  removeBGModel: string
 | 
			
		||||
  removeBGModels: string[]
 | 
			
		||||
  realesrganModel: string
 | 
			
		||||
  realesrganModels: string[]
 | 
			
		||||
  interactiveSegModel: string
 | 
			
		||||
  interactiveSegModels: string[]
 | 
			
		||||
  enableFileManager: boolean
 | 
			
		||||
  enableAutoSaving: boolean
 | 
			
		||||
  enableControlnet: boolean
 | 
			
		||||
  controlnetMethod: string
 | 
			
		||||
  disableModelSwitch: boolean
 | 
			
		||||
  isDesktop: boolean
 | 
			
		||||
  samplers: string[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface GenInfo {
 | 
			
		||||
  prompt: string
 | 
			
		||||
  negative_prompt: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ModelInfo {
 | 
			
		||||
  name: string
 | 
			
		||||
  path: string
 | 
			
		||||
  model_type:
 | 
			
		||||
    | "inpaint"
 | 
			
		||||
    | "diffusers_sd"
 | 
			
		||||
    | "diffusers_sdxl"
 | 
			
		||||
    | "diffusers_sd_inpaint"
 | 
			
		||||
    | "diffusers_sdxl_inpaint"
 | 
			
		||||
    | "diffusers_other"
 | 
			
		||||
  support_strength: boolean
 | 
			
		||||
  support_outpainting: boolean
 | 
			
		||||
  support_controlnet: boolean
 | 
			
		||||
  controlnets: string[]
 | 
			
		||||
  support_freeu: boolean
 | 
			
		||||
  support_lcm_lora: boolean
 | 
			
		||||
  need_prompt: boolean
 | 
			
		||||
  is_single_file_diffusers: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum PluginName {
 | 
			
		||||
  RemoveBG = "RemoveBG",
 | 
			
		||||
  AnimeSeg = "AnimeSeg",
 | 
			
		||||
  RealESRGAN = "RealESRGAN",
 | 
			
		||||
  GFPGAN = "GFPGAN",
 | 
			
		||||
  RestoreFormer = "RestoreFormer",
 | 
			
		||||
  InteractiveSeg = "InteractiveSeg",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PluginParams {
 | 
			
		||||
  upscale: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum SortBy {
 | 
			
		||||
  NAME = "name",
 | 
			
		||||
  CTIME = "ctime",
 | 
			
		||||
  MTIME = "mtime",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum SortOrder {
 | 
			
		||||
  DESCENDING = "desc",
 | 
			
		||||
  ASCENDING = "asc",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum LDMSampler {
 | 
			
		||||
  ddim = "ddim",
 | 
			
		||||
  plms = "plms",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum CV2Flag {
 | 
			
		||||
  INPAINT_NS = "INPAINT_NS",
 | 
			
		||||
  INPAINT_TELEA = "INPAINT_TELEA",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Rect {
 | 
			
		||||
  x: number
 | 
			
		||||
  y: number
 | 
			
		||||
  width: number
 | 
			
		||||
  height: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface FreeuConfig {
 | 
			
		||||
  s1: number
 | 
			
		||||
  s2: number
 | 
			
		||||
  b1: number
 | 
			
		||||
  b2: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Point {
 | 
			
		||||
  x: number
 | 
			
		||||
  y: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Line {
 | 
			
		||||
  size?: number
 | 
			
		||||
  pts: Point[]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type LineGroup = Array<Line>
 | 
			
		||||
 | 
			
		||||
export interface Size {
 | 
			
		||||
  width: number
 | 
			
		||||
  height: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ExtenderDirection {
 | 
			
		||||
  x = "x",
 | 
			
		||||
  y = "y",
 | 
			
		||||
  xy = "xy",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum PowerPaintTask {
 | 
			
		||||
  text_guided = "text-guided",
 | 
			
		||||
  shape_guided = "shape-guided",
 | 
			
		||||
  object_remove = "object-remove",
 | 
			
		||||
  outpainting = "outpainting",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type AdjustMaskOperate = "expand" | "shrink" | "reverse"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,246 @@
 | 
			
		|||
import { type ClassValue, clsx } from "clsx";
 | 
			
		||||
import { SyntheticEvent } from "react";
 | 
			
		||||
import { twMerge } from "tailwind-merge";
 | 
			
		||||
import { LineGroup } from "./types";
 | 
			
		||||
import { BRUSH_COLOR } from "./const";
 | 
			
		||||
 | 
			
		||||
export function cn(...inputs: ClassValue[]) {
 | 
			
		||||
	return twMerge(clsx(inputs));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function keepGUIAlive() {
 | 
			
		||||
	async function getRequest(url = "") {
 | 
			
		||||
		const response = await fetch(url, {
 | 
			
		||||
			method: "GET",
 | 
			
		||||
			cache: "no-cache",
 | 
			
		||||
		});
 | 
			
		||||
		return response.json();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const keepAliveServer = () => {
 | 
			
		||||
		const url = document.location;
 | 
			
		||||
		const route = "/flaskwebgui-keep-server-alive";
 | 
			
		||||
		getRequest(url + route).then((data) => {
 | 
			
		||||
			return data;
 | 
			
		||||
		});
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const intervalRequest = 3 * 1000;
 | 
			
		||||
	keepAliveServer();
 | 
			
		||||
	setInterval(keepAliveServer, intervalRequest);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function dataURItoBlob(dataURI: string) {
 | 
			
		||||
	const mime = dataURI.split(",")[0].split(":")[1].split(";")[0];
 | 
			
		||||
	const binary = atob(dataURI.split(",")[1]);
 | 
			
		||||
	const array = [];
 | 
			
		||||
	for (let i = 0; i < binary.length; i += 1) {
 | 
			
		||||
		array.push(binary.charCodeAt(i));
 | 
			
		||||
	}
 | 
			
		||||
	return new Blob([new Uint8Array(array)], { type: mime });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function loadImage(image: HTMLImageElement, src: string) {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		const initSRC = image.src;
 | 
			
		||||
		const img = image;
 | 
			
		||||
		img.onload = resolve;
 | 
			
		||||
		img.onerror = (err) => {
 | 
			
		||||
			img.src = initSRC;
 | 
			
		||||
			reject(err);
 | 
			
		||||
		};
 | 
			
		||||
		img.src = src;
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function blobToImage(blob: Blob) {
 | 
			
		||||
	const dataURL = URL.createObjectURL(blob);
 | 
			
		||||
	const newImage = new Image();
 | 
			
		||||
	await loadImage(newImage, dataURL);
 | 
			
		||||
	return newImage;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function canvasToImage(
 | 
			
		||||
	canvas: HTMLCanvasElement,
 | 
			
		||||
): Promise<HTMLImageElement> {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		const image = new Image();
 | 
			
		||||
 | 
			
		||||
		image.addEventListener("load", () => {
 | 
			
		||||
			resolve(image);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		image.addEventListener("error", (error) => {
 | 
			
		||||
			reject(error);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		image.src = canvas.toDataURL();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function fileToImage(file: File): Promise<HTMLImageElement> {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		const reader = new FileReader();
 | 
			
		||||
		reader.onload = () => {
 | 
			
		||||
			const image = new Image();
 | 
			
		||||
			image.onload = () => {
 | 
			
		||||
				resolve(image);
 | 
			
		||||
			};
 | 
			
		||||
			image.onerror = () => {
 | 
			
		||||
				reject("无法加载图像。");
 | 
			
		||||
			};
 | 
			
		||||
			image.src = reader.result as string;
 | 
			
		||||
		};
 | 
			
		||||
		reader.onerror = () => {
 | 
			
		||||
			reject("无法读取文件。");
 | 
			
		||||
		};
 | 
			
		||||
		reader.readAsDataURL(file);
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function srcToFile(src: string, fileName: string, mimeType: string) {
 | 
			
		||||
	return fetch(src)
 | 
			
		||||
		.then(function (res) {
 | 
			
		||||
			return res.arrayBuffer();
 | 
			
		||||
		})
 | 
			
		||||
		.then(function (buf) {
 | 
			
		||||
			return new File([buf], fileName, { type: mimeType });
 | 
			
		||||
		});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function askWritePermission() {
 | 
			
		||||
	try {
 | 
			
		||||
		// The clipboard-write permission is granted automatically to pages
 | 
			
		||||
		// when they are the active tab. So it's not required, but it's more safe.
 | 
			
		||||
		const { state } = await navigator.permissions.query({
 | 
			
		||||
			name: "clipboard-write" as PermissionName,
 | 
			
		||||
		});
 | 
			
		||||
		return state === "granted";
 | 
			
		||||
	} catch (error) {
 | 
			
		||||
		// Browser compatibility / Security error (ONLY HTTPS) ...
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function canvasToBlob(canvas: HTMLCanvasElement, mime: string): Promise<any> {
 | 
			
		||||
	return new Promise((resolve, reject) =>
 | 
			
		||||
		canvas.toBlob(async (d) => {
 | 
			
		||||
			if (d) {
 | 
			
		||||
				resolve(d);
 | 
			
		||||
			} else {
 | 
			
		||||
				reject(new Error("Expected toBlob() to be defined"));
 | 
			
		||||
			}
 | 
			
		||||
		}, mime),
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const setToClipboard = async (blob: any) => {
 | 
			
		||||
	const data = [new ClipboardItem({ [blob.type]: blob })];
 | 
			
		||||
	await navigator.clipboard.write(data);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function isRightClick(ev: SyntheticEvent) {
 | 
			
		||||
	const mouseEvent = ev.nativeEvent as MouseEvent;
 | 
			
		||||
	return mouseEvent.button === 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isMidClick(ev: SyntheticEvent) {
 | 
			
		||||
	const mouseEvent = ev.nativeEvent as MouseEvent;
 | 
			
		||||
	return mouseEvent.button === 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function copyCanvasImage(canvas: HTMLCanvasElement) {
 | 
			
		||||
	const blob = await canvasToBlob(canvas, "image/png");
 | 
			
		||||
	try {
 | 
			
		||||
		await setToClipboard(blob);
 | 
			
		||||
	} catch {
 | 
			
		||||
		console.log("Copy image failed!");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function downloadImage(uri: string, name: string) {
 | 
			
		||||
	const link = document.createElement("a");
 | 
			
		||||
	link.href = uri;
 | 
			
		||||
	link.download = name;
 | 
			
		||||
 | 
			
		||||
	// this is necessary as link.click() does not work on the latest firefox
 | 
			
		||||
	link.dispatchEvent(
 | 
			
		||||
		new MouseEvent("click", {
 | 
			
		||||
			bubbles: true,
 | 
			
		||||
			cancelable: true,
 | 
			
		||||
			view: window,
 | 
			
		||||
		}),
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	setTimeout(() => {
 | 
			
		||||
		// For Firefox it is necessary to delay revoking the ObjectURL
 | 
			
		||||
		// window.URL.revokeObjectURL(base64)
 | 
			
		||||
		link.remove();
 | 
			
		||||
	}, 100);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function mouseXY(ev: SyntheticEvent) {
 | 
			
		||||
	const mouseEvent = ev.nativeEvent as MouseEvent;
 | 
			
		||||
	return { x: mouseEvent.offsetX, y: mouseEvent.offsetY };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function drawLines(
 | 
			
		||||
	ctx: CanvasRenderingContext2D,
 | 
			
		||||
	lines: LineGroup,
 | 
			
		||||
	color = BRUSH_COLOR,
 | 
			
		||||
) {
 | 
			
		||||
	ctx.strokeStyle = color;
 | 
			
		||||
	ctx.lineCap = "round";
 | 
			
		||||
	ctx.lineJoin = "round";
 | 
			
		||||
 | 
			
		||||
	lines.forEach((line) => {
 | 
			
		||||
		if (!line?.pts.length || !line.size) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		ctx.lineWidth = line.size;
 | 
			
		||||
		ctx.beginPath();
 | 
			
		||||
		ctx.moveTo(line.pts[0].x, line.pts[0].y);
 | 
			
		||||
		line.pts.forEach((pt) => ctx.lineTo(pt.x, pt.y));
 | 
			
		||||
		ctx.stroke();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const generateMask = (
 | 
			
		||||
	imageWidth: number,
 | 
			
		||||
	imageHeight: number,
 | 
			
		||||
	lineGroups: LineGroup[],
 | 
			
		||||
	maskImages: HTMLImageElement[] = [],
 | 
			
		||||
	lineGroupsColor: string = "white",
 | 
			
		||||
): HTMLCanvasElement => {
 | 
			
		||||
	const maskCanvas = document.createElement("canvas");
 | 
			
		||||
	maskCanvas.width = imageWidth;
 | 
			
		||||
	maskCanvas.height = imageHeight;
 | 
			
		||||
	const ctx = maskCanvas.getContext("2d");
 | 
			
		||||
	if (!ctx) {
 | 
			
		||||
		throw new Error("could not retrieve mask canvas");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	maskImages.forEach((maskImage) => {
 | 
			
		||||
		ctx.drawImage(maskImage, 0, 0, imageWidth, imageHeight);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	lineGroups.forEach((lineGroup) => {
 | 
			
		||||
		drawLines(ctx, lineGroup, lineGroupsColor);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return maskCanvas;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const convertToBase64 = (fileOrBlob: File | Blob): Promise<string> => {
 | 
			
		||||
	return new Promise((resolve, reject) => {
 | 
			
		||||
		const reader = new FileReader();
 | 
			
		||||
		reader.onload = (event) => {
 | 
			
		||||
			const base64String = event.target?.result as string;
 | 
			
		||||
			resolve(base64String);
 | 
			
		||||
		};
 | 
			
		||||
		reader.onerror = (error) => {
 | 
			
		||||
			reject(error);
 | 
			
		||||
		};
 | 
			
		||||
		reader.readAsDataURL(fileOrBlob);
 | 
			
		||||
	});
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,22 +1,18 @@
 | 
			
		|||
import React from "react"
 | 
			
		||||
import ReactDOM from "react-dom/client"
 | 
			
		||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
 | 
			
		||||
import "inter-ui/inter.css"
 | 
			
		||||
import App from "./App.tsx"
 | 
			
		||||
import "./globals.css"
 | 
			
		||||
import { ThemeProvider } from "next-themes"
 | 
			
		||||
import { TooltipProvider } from "./components/ui/tooltip.tsx"
 | 
			
		||||
import React from "react";
 | 
			
		||||
import ReactDOM from "react-dom/client";
 | 
			
		||||
import App from "./App.tsx";
 | 
			
		||||
import "./globals.css";
 | 
			
		||||
 | 
			
		||||
const queryClient = new QueryClient()
 | 
			
		||||
import { MantineProvider } from "@mantine/core";
 | 
			
		||||
import { Notifications } from "@mantine/notifications";
 | 
			
		||||
import "@mantine/core/styles.css";
 | 
			
		||||
import "@mantine/notifications/styles.css";
 | 
			
		||||
 | 
			
		||||
ReactDOM.createRoot(document.getElementById("root")!).render(
 | 
			
		||||
	<React.StrictMode>
 | 
			
		||||
    <QueryClientProvider client={queryClient}>
 | 
			
		||||
      <ThemeProvider defaultTheme="dark" disableTransitionOnChange>
 | 
			
		||||
        <TooltipProvider>
 | 
			
		||||
		<MantineProvider defaultColorScheme="dark">
 | 
			
		||||
			<Notifications />
 | 
			
		||||
			<App />
 | 
			
		||||
        </TooltipProvider>
 | 
			
		||||
      </ThemeProvider>
 | 
			
		||||
    </QueryClientProvider>
 | 
			
		||||
  </React.StrictMode>
 | 
			
		||||
)
 | 
			
		||||
		</MantineProvider>
 | 
			
		||||
	</React.StrictMode>,
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue