diff --git a/package-lock.json b/package-lock.json index 19e99ae..de794fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,22 @@ "workspaces": [ "packages/*" ], + "dependencies": { + "immer": "^10.0.2" + }, "devDependencies": { "lerna": "^7.0.2" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -1349,33 +1361,33 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", - "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz", + "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.8.tgz", + "integrity": "sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/generator": "^7.22.7", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-module-transforms": "^7.22.5", - "@babel/helpers": "^7.22.5", - "@babel/parser": "^7.22.5", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", + "@babel/traverse": "^7.22.8", "@babel/types": "^7.22.5", + "@nicolo-ribaudo/semver-v6": "^6.3.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.2" }, "engines": { "node": ">=6.9.0" @@ -1385,18 +1397,10 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/generator": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.7.tgz", + "integrity": "sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==", "dependencies": { "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", @@ -1454,15 +1458,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", - "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz", + "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==", "dependencies": { - "@babel/compat-data": "^7.22.5", + "@babel/compat-data": "^7.22.6", "@babel/helper-validator-option": "^7.22.5", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1" }, "engines": { "node": ">=6.9.0" @@ -1479,23 +1483,15 @@ "yallist": "^3.0.2" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.5.tgz", - "integrity": "sha512-xkb58MyOYIslxu3gKmVXmjTtUPvBU4odYzbiIQbWwLKIHCsx6UGZGX6F1IznMFVnDdirseUZopzN+ZRt8Xb33Q==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz", + "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1505,8 +1501,8 @@ "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-replace-supers": "^7.22.5", "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "semver": "^6.3.0" + "@babel/helper-split-export-declaration": "^7.22.6", + "@nicolo-ribaudo/semver-v6": "^6.3.3" }, "engines": { "node": ">=6.9.0" @@ -1515,24 +1511,15 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.5.tgz", - "integrity": "sha512-1VpEFOIbMRaXyDeUwUfmTIxExLwQ+zkW+Bh5zXpApA3oQedBx9v/updixWxnx/bZpKw7u8VxWjb/qWpIcmPq8A==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz", + "integrity": "sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "regexpu-core": "^5.3.1", - "semver": "^6.3.0" + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "regexpu-core": "^5.3.1" }, "engines": { "node": ">=6.9.0" @@ -1541,41 +1528,22 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz", - "integrity": "sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.1.tgz", + "integrity": "sha512-kX4oXixDxG197yhX+J3Wp+NpL2wuCFjWQAr6yX2jtCnflK9ulMI51ULFGIrWiX1jGfvAxdHp+XQCcP2bZGPs9A==", "dev": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", "debug": "^4.1.1", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" + "resolve": "^1.14.2" }, "peerDependencies": { "@babel/core": "^7.4.0-0" } }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", @@ -1727,9 +1695,9 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { "@babel/types": "^7.22.5" }, @@ -1777,12 +1745,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", "dependencies": { "@babel/template": "^7.22.5", - "@babel/traverse": "^7.22.5", + "@babel/traverse": "^7.22.6", "@babel/types": "^7.22.5" }, "engines": { @@ -1867,9 +1835,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2201,9 +2169,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz", - "integrity": "sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.7.tgz", + "integrity": "sha512-7HmE7pk/Fmke45TODvxvkxRMV9RazV+ZZzhOL9AG8G29TLrr3jkjwF7uJfxZ30EoXpO+LJkq4oA8NjO2DTnEDg==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", @@ -2299,19 +2267,19 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.5.tgz", - "integrity": "sha512-2edQhLfibpWpsVBx2n/GKOz6JdGQvLruZQfGr9l1qes2KQaWswjBzhQF7UDUZMNaMMQeYnQzxwOMPsbYF7wqPQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz", + "integrity": "sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-optimise-call-expression": "^7.22.5", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-replace-supers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", "globals": "^11.1.0" }, "engines": { @@ -2715,9 +2683,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.5.tgz", - "integrity": "sha512-AconbMKOMkyG+xCng2JogMCDcqW8wedQAqpVIL4cOSescZ7+iW8utC6YDZLMCSUIReEA733gzRSaOSXMAt/4WQ==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz", + "integrity": "sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -2966,13 +2934,13 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.5.tgz", - "integrity": "sha512-fj06hw89dpiZzGZtxn+QybifF07nNiZjZ7sazs2aVDcysAZVGjW7+7iFYxg6GLNM47R/thYfLdrXc+2f11Vi9A==", + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.7.tgz", + "integrity": "sha512-1whfDtW+CzhETuzYXfcgZAh8/GFMeEbz0V5dVgya8YeJyCU6Y/P2Gnx4Qb3MylK68Zu9UiwUvbPMPTpFAOJ+sQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.22.5", - "@babel/helper-compilation-targets": "^7.22.5", + "@babel/compat-data": "^7.22.6", + "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", @@ -2997,13 +2965,13 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.22.5", - "@babel/plugin-transform-async-generator-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.7", "@babel/plugin-transform-async-to-generator": "^7.22.5", "@babel/plugin-transform-block-scoped-functions": "^7.22.5", "@babel/plugin-transform-block-scoping": "^7.22.5", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-class-static-block": "^7.22.5", - "@babel/plugin-transform-classes": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", "@babel/plugin-transform-computed-properties": "^7.22.5", "@babel/plugin-transform-destructuring": "^7.22.5", "@babel/plugin-transform-dotall-regex": "^7.22.5", @@ -3028,7 +2996,7 @@ "@babel/plugin-transform-object-rest-spread": "^7.22.5", "@babel/plugin-transform-object-super": "^7.22.5", "@babel/plugin-transform-optional-catch-binding": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", "@babel/plugin-transform-parameters": "^7.22.5", "@babel/plugin-transform-private-methods": "^7.22.5", "@babel/plugin-transform-private-property-in-object": "^7.22.5", @@ -3046,11 +3014,11 @@ "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", "@babel/preset-modules": "^0.1.5", "@babel/types": "^7.22.5", - "babel-plugin-polyfill-corejs2": "^0.4.3", - "babel-plugin-polyfill-corejs3": "^0.8.1", - "babel-plugin-polyfill-regenerator": "^0.5.0", - "core-js-compat": "^3.30.2", - "semver": "^6.3.0" + "@nicolo-ribaudo/semver-v6": "^6.3.3", + "babel-plugin-polyfill-corejs2": "^0.4.4", + "babel-plugin-polyfill-corejs3": "^0.8.2", + "babel-plugin-polyfill-regenerator": "^0.5.1", + "core-js-compat": "^3.31.0" }, "engines": { "node": ">=6.9.0" @@ -3059,15 +3027,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/preset-modules": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", @@ -3115,17 +3074,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", "dependencies": { "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.5", + "@babel/generator": "^7.22.7", "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-function-name": "^7.22.5", "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.22.5", - "@babel/parser": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", "@babel/types": "^7.22.5", "debug": "^4.1.0", "globals": "^11.1.0" @@ -4575,6 +4534,14 @@ } } }, + "node_modules/@nicolo-ribaudo/semver-v6": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz", + "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7311,48 +7278,39 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz", - "integrity": "sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.4.tgz", + "integrity": "sha512-9WeK9snM1BfxB38goUEv2FLnA6ja07UMfazFHzCXUb3NyDZAwfXvQiURQ6guTTMeHcOsdknULm1PDhs4uWtKyA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.4.0", - "semver": "^6.1.1" + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.1", + "@nicolo-ribaudo/semver-v6": "^6.3.3" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz", - "integrity": "sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.2.tgz", + "integrity": "sha512-Cid+Jv1BrY9ReW9lIfNlNpsI53N+FN7gE+f73zLAUbr9C52W4gKLWSByx47pfDJsEysojKArqOtOKZSVIIUTuQ==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.0", - "core-js-compat": "^3.30.1" + "@babel/helper-define-polyfill-provider": "^0.4.1", + "core-js-compat": "^3.31.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz", - "integrity": "sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.1.tgz", + "integrity": "sha512-L8OyySuI6OSQ5hFy9O+7zFjyr4WhAfRjLIOkhQGYl+emwJkd/S4XXT1JpfrgR1jrQ1NcGiOh+yAdGlF8pnC3Jw==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.0" + "@babel/helper-define-polyfill-provider": "^0.4.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -8393,12 +8351,12 @@ } }, "node_modules/core-js-compat": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.0.tgz", - "integrity": "sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==", + "version": "3.31.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.31.1.tgz", + "integrity": "sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==", "dev": true, "dependencies": { - "browserslist": "^4.21.5" + "browserslist": "^4.21.9" }, "funding": { "type": "opencollective", @@ -10829,6 +10787,15 @@ "node": ">=10" } }, + "node_modules/immer": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", + "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -14383,17 +14350,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -18422,6 +18389,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -18894,15 +18869,6 @@ "node": ">=8.12.0" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -19638,6 +19604,29 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zustand": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.9.tgz", + "integrity": "sha512-Tat5r8jOMG1Vcsj8uldMyqYKC5IZvQif8zetmLHs9WoZlntTHmIoNM8TpLRY31ExncuUvUOXehd0kvahkuHjDw==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "packages/cli": { "name": "gpt-turbo-cli", "version": "4.3.2", @@ -19900,7 +19889,8 @@ "react-icons": "^4.8.0", "react-router-dom": "^6.13.0", "uuid": "^9.0.0", - "zod": "^3.21.4" + "zod": "^3.21.4", + "zustand": "^4.3.9" }, "devDependencies": { "@types/react": "^18.0.26", diff --git a/package.json b/package.json index 4460994..36d42eb 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,8 @@ ], "devDependencies": { "lerna": "^7.0.2" + }, + "dependencies": { + "immer": "^10.0.2" } } diff --git a/packages/lib/src/classes/Conversation.ts b/packages/lib/src/classes/Conversation.ts index 3375cfa..4bc8a26 100644 --- a/packages/lib/src/classes/Conversation.ts +++ b/packages/lib/src/classes/Conversation.ts @@ -289,7 +289,7 @@ export class Conversation { */ public addFunction(fn: CallableFunction | CallableFunctionModel) { this.functions = this.functions - .filter((f) => f.name === fn.name || f.id === fn.id) + .filter((f) => f.name !== fn.name && f.id !== fn.id) .concat( fn instanceof CallableFunction ? fn diff --git a/packages/web/package.json b/packages/web/package.json index 243d426..90244c0 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -73,7 +73,8 @@ "react-icons": "^4.8.0", "react-router-dom": "^6.13.0", "uuid": "^9.0.0", - "zod": "^3.21.4" + "zod": "^3.21.4", + "zustand": "^4.3.9" }, "devDependencies": { "@types/react": "^18.0.26", diff --git a/packages/web/src/changelog/index.ts b/packages/web/src/changelog/index.ts new file mode 100644 index 0000000..5e74305 --- /dev/null +++ b/packages/web/src/changelog/index.ts @@ -0,0 +1,16 @@ +import { ReactNode } from "react"; +import v4_4_0 from "./v4-4-0"; + +export type ChangelogEntrySection = { + label: ReactNode; + items: ReactNode[]; +}; + +export type ChangelogEntry = { + version: string; + date: Date; + sections: ChangelogEntrySection[]; +}; + +// First entry is the latest version +export const changelog = [v4_4_0]; diff --git a/packages/web/src/changelog/v4-4-0.ts b/packages/web/src/changelog/v4-4-0.ts new file mode 100644 index 0000000..31d5b22 --- /dev/null +++ b/packages/web/src/changelog/v4-4-0.ts @@ -0,0 +1,41 @@ +import { ChangelogEntry } from "."; +import { CHANGELOG_SECTION } from "../config/constants"; + +const v4_4_0: ChangelogEntry = { + version: "4.4.0 - Zustand", + date: new Date("july 12 2023"), + sections: [ + { + label: CHANGELOG_SECTION.FEATURES, + items: [ + "Added the ability to remove all callable functions.", + "Added the ability to remove all saved contexts.", + "Added the ability to remove all saved prompts.", + "Added the ability to reset default settings.", + "Added the ability to reset function warnings.", + "Added this changelog!", + ], + }, + { + label: CHANGELOG_SECTION.IMPROVEMENTS, + items: [ + "Revamped state management from React Context API to Zustand.", + "With Zustand comes a new persistence system, which will (hopefully) help with invalid saved data in the future, thanks to migrations.", + ], + }, + { + label: CHANGELOG_SECTION.FIXES, + items: [ + "Fixed an issue preventing from having more than 1 callable function in a conversation.", + ], + }, + { + label: CHANGELOG_SECTION.REMOVALS, + items: [ + "Removed the API key from saved conversations. It will be taken from your default settings instead.", + ], + }, + ], +}; + +export default v4_4_0; diff --git a/packages/web/src/components/About.tsx b/packages/web/src/components/About.tsx index 11d8169..1c57efa 100644 --- a/packages/web/src/components/About.tsx +++ b/packages/web/src/components/About.tsx @@ -1,10 +1,9 @@ -import { Anchor, Stack, Text, Title } from "@mantine/core"; +import { Anchor, Stack, Text } from "@mantine/core"; import { DISCORD_SERVER_INVITE } from "../config/constants"; const About = () => { return ( - About GPT Turbo Web This web app was created in order to showcase what the underlying{" "} diff --git a/packages/web/src/components/AddConversationForm.tsx b/packages/web/src/components/AddConversationForm.tsx index ad69a18..624756f 100644 --- a/packages/web/src/components/AddConversationForm.tsx +++ b/packages/web/src/components/AddConversationForm.tsx @@ -1,14 +1,13 @@ -import useConversationManager from "../hooks/useConversationManager"; import React from "react"; -import usePersistence from "../hooks/usePersistence"; import ConversationForm from "./ConversationForm"; import { ConversationFormValues } from "../contexts/ConversationFormContext"; import useCallableFunctions from "../hooks/useCallableFunctions"; +import { addConversation } from "../store/actions/conversations/addConversation"; +import { setActiveConversation } from "../store/actions/conversations/setActiveConversation"; +import { addPersistedConversationId } from "../store/actions/persistence/addPersistedConversationId"; const AddConversationForm = () => { - const { addConversation, setActiveConversation } = useConversationManager(); const { getCallableFunction } = useCallableFunctions(); - const { addPersistedConversationId } = usePersistence(); const onSubmit = React.useCallback( ({ @@ -21,21 +20,16 @@ const AddConversationForm = () => { const newConversation = addConversation(values, { headers, proxy }); setActiveConversation(newConversation.id, true); - functionIds.forEach((id) => { - const callableFunction = getCallableFunction(id); + for (const functionId of functionIds) { + const callableFunction = getCallableFunction(functionId); newConversation.addFunction(callableFunction); - }); + } if (save) { addPersistedConversationId(newConversation.id); } }, - [ - addConversation, - addPersistedConversationId, - getCallableFunction, - setActiveConversation, - ] + [getCallableFunction] ); return ; diff --git a/packages/web/src/components/AppSettings.tsx b/packages/web/src/components/AppSettings.tsx index 51cbb0f..802ef5a 100644 --- a/packages/web/src/components/AppSettings.tsx +++ b/packages/web/src/components/AppSettings.tsx @@ -1,5 +1,4 @@ import { - Button, ColorScheme, Divider, Group, @@ -9,24 +8,14 @@ import { Text, useMantineColorScheme, } from "@mantine/core"; -import useConversationManager from "../hooks/useConversationManager"; -import React from "react"; import AppStorageUsage from "./AppStorageUsage"; +import { useAppStore } from "../store"; +import { toggleShowUsage } from "../store/actions/appSettings/toggleShowUsage"; +import AppSettingsDangerZone from "./AppSettingsDangerZone"; const AppSettings = () => { const { colorScheme, toggleColorScheme } = useMantineColorScheme(); - const { showUsage, setShowUsage, removeAllConversations } = - useConversationManager(); - const [clearConfirm, setClearConfirm] = React.useState(false); - - const handleClearConversations = React.useCallback(() => { - if (!clearConfirm) { - setClearConfirm(true); - return; - } - removeAllConversations(); - setClearConfirm(false); - }, [clearConfirm, removeAllConversations]); + const showUsage = useAppStore((state) => state.showUsage); return ( @@ -48,7 +37,7 @@ const AppSettings = () => { Show Usage setShowUsage(!showUsage)} + onChange={() => toggleShowUsage()} /> @@ -56,16 +45,7 @@ const AppSettings = () => { - - Delete all conversations - - + ); }; diff --git a/packages/web/src/components/AppSettingsDangerZone.tsx b/packages/web/src/components/AppSettingsDangerZone.tsx new file mode 100644 index 0000000..92b4e01 --- /dev/null +++ b/packages/web/src/components/AppSettingsDangerZone.tsx @@ -0,0 +1,68 @@ +import { Group, SimpleGrid, Stack, Text } from "@mantine/core"; +import { removeAllCallableFunctions } from "../store/actions/callableFunctions/removeAllCallableFunctions"; +import { removeAllConversations } from "../store/actions/conversations/removeAllConversations"; +import InlineConfirmButton from "./InlineConfirmButton"; +import React from "react"; +import { resetCallableFunctionWarnings } from "../store/actions/callableFunctions/resetCallableFunctionWarnings"; +import { resetDefaultSettings } from "../store/actions/defaultConversationSettings/resetDefaultSettings"; +import { removeAllSavedContexts } from "../store/actions/savedContexts/removeAllSavedContexts"; +import { removeAllSavedPrompts } from "../store/actions/savedPrompts/removeAllSavedPrompts"; + +const AppSettingsDangerZone = () => { + const actions = React.useMemo( + () => [ + { + label: "Delete conversations", + unconfirmedLabel: "Delete", + action: removeAllConversations, + }, + { + label: "Delete Functions", + unconfirmedLabel: "Delete", + action: removeAllCallableFunctions, + }, + { + label: "Delete Saved Contexts", + unconfirmedLabel: "Delete", + action: removeAllSavedContexts, + }, + { + label: "Delete Saved Prompts", + unconfirmedLabel: "Delete", + action: removeAllSavedPrompts, + }, + { + label: "Reset Function Warnings", + unconfirmedLabel: "Reset", + action: resetCallableFunctionWarnings, + }, + { + label: "Reset Default Settings", + unconfirmedLabel: "Reset", + action: resetDefaultSettings, + }, + ], + [] + ); + + return ( + + + {actions.map(({ label, unconfirmedLabel, action }) => ( + + {label} + + {unconfirmedLabel} + + + ))} + + + ); +}; + +export default AppSettingsDangerZone; diff --git a/packages/web/src/components/AppStorageUsage.tsx b/packages/web/src/components/AppStorageUsage.tsx index 03c278d..aeba139 100644 --- a/packages/web/src/components/AppStorageUsage.tsx +++ b/packages/web/src/components/AppStorageUsage.tsx @@ -1,11 +1,3 @@ -import React from "react"; -import { - STORAGEKEY_PERSISTENCE, - STORAGEKEY_SETTINGS, - STORAGEKEY_COLORSCHEME, - STORAGEKEY_SHOWUSAGE, -} from "../config/constants"; -import { useLocalStorage } from "@mantine/hooks"; import { Box, ColorSwatch, @@ -15,6 +7,20 @@ import { Tooltip, useMantineTheme, } from "@mantine/core"; +import React from "react"; +import { STORAGE_PERSISTENCE_KEY } from "../config/constants"; + +interface StorageUsage { + label: string; + size: number; +} + +const MIN_SIZE = 100; + +const getUsageLabel = (key: string) => { + const words = key.split(/(?=[A-Z])/); + return words.map((word) => word[0].toUpperCase() + word.slice(1)).join(" "); +}; const getSizeLabel = (size: number) => { if (size < 1024) return `${size.toFixed(2)}B`; @@ -22,56 +28,60 @@ const getSizeLabel = (size: number) => { return `${(size / (1024 * 1024)).toFixed(2)}MB`; }; -const useLocalStorageSize = (key: string) => { - const [value] = useLocalStorage({ key, deserialize: (v) => v }); - return value ? key.length + value.length : 0; -}; - const AppStorageUsage = () => { + const storageValue = localStorage.getItem(STORAGE_PERSISTENCE_KEY); + const theme = useMantineTheme(); - const persistenceSize = useLocalStorageSize(STORAGEKEY_PERSISTENCE); - const settingsSize = useLocalStorageSize(STORAGEKEY_SETTINGS); - const colorSchemeSize = useLocalStorageSize(STORAGEKEY_COLORSCHEME); - const showUsageSize = useLocalStorageSize(STORAGEKEY_SHOWUSAGE); - - const usage = React.useMemo( - () => - [ - { - key: STORAGEKEY_PERSISTENCE, - label: "Persistence", - size: persistenceSize, - }, - { - key: STORAGEKEY_SETTINGS, - label: "Settings", - size: settingsSize, - }, - { - key: STORAGEKEY_COLORSCHEME, - label: "Color Scheme", - size: colorSchemeSize, - }, - { - key: STORAGEKEY_SHOWUSAGE, - label: "Show Usage", - size: showUsageSize, - }, - ].filter(({ size }) => size > 0), - [colorSchemeSize, persistenceSize, settingsSize, showUsageSize] - ); + const isDark = theme.colorScheme === "dark"; + const colorShade = isDark ? 4 : 6; - const colors: Record = React.useMemo( - () => ({ - [STORAGEKEY_PERSISTENCE]: theme.colors.pink[5], - [STORAGEKEY_SETTINGS]: theme.colors.blue[5], - [STORAGEKEY_COLORSCHEME]: theme.colors.green[5], - [STORAGEKEY_SHOWUSAGE]: theme.colors.yellow[5], - }), + const usage = React.useMemo(() => { + if (!storageValue) return []; + + const json = JSON.parse(storageValue); + if (!json.state) return []; + + const { state } = json; + + return Object.entries(state) + .map(([key, value]) => { + const label = getUsageLabel(key); + const size = JSON.stringify(value).length; + + return { + label, + size, + } satisfies StorageUsage; + }) + .filter((usage) => usage.size > MIN_SIZE); + }, [storageValue]); + + const colors = React.useMemo( + () => [ + theme.colors.blue[colorShade], + theme.colors.grape[colorShade], + theme.colors.teal[colorShade], + theme.colors.orange[colorShade], + theme.colors.indigo[colorShade], + theme.colors.red[colorShade], + theme.colors.yellow[colorShade], + theme.colors.cyan[colorShade], + theme.colors.lime[colorShade], + theme.colors.violet[colorShade], + theme.colors.pink[colorShade], + ], [ + colorShade, theme.colors.blue, - theme.colors.green, + theme.colors.cyan, + theme.colors.grape, + theme.colors.indigo, + theme.colors.lime, + theme.colors.orange, theme.colors.pink, + theme.colors.red, + theme.colors.teal, + theme.colors.violet, theme.colors.yellow, ] ); @@ -81,8 +91,8 @@ const AppStorageUsage = () => { const total = usage.reduce((acc, { size }) => acc + size, 0); return usage - .map(({ key, label, size }) => { - const color = colors[key] ?? theme.colors.dark[7]; + .map(({ label, size }, i) => { + const color = colors[i % colors.length]; return { value: (size / quota) * 100, @@ -102,12 +112,15 @@ const AppStorageUsage = () => { return ( Storage Usage - + This is an estimate, assuming a 5MB quota. (default for most - browsers) + browsers). + + + Categories under {getSizeLabel(MIN_SIZE)} are not shown. - + {sections.map(({ color, label, tooltip, value }) => ( & { fn: CallableFunction; @@ -32,13 +35,9 @@ const CallableFunctionCard = ({ ...cardProps }: CallableFunctionCardProps) => { const navigate = useNavigate(); - const { - getCallableFunctionDisplayName, - deleteCallableFunction, - addCallableFunction, - getCallableFunctionCode, - callableFunctions, - } = useCallableFunctions(); + const callableFunctions = useAppStore((state) => state.callableFunctions); + const { getCallableFunctionDisplayName, getCallableFunctionCode } = + useCallableFunctions(); const signature = React.useMemo(() => { const parameters = [ @@ -120,7 +119,6 @@ const CallableFunctionCard = ({ }, }); }, [ - addCallableFunction, callableFunctions, fn, getCallableFunctionCode, @@ -144,7 +142,7 @@ const CallableFunctionCard = ({ deleteCallableFunction(fn.id); }, }); - }, [deleteCallableFunction, fn.id, getCallableFunctionDisplayName]); + }, [fn.id, getCallableFunctionDisplayName]); const onExport = React.useCallback(() => { const data = JSON.stringify( diff --git a/packages/web/src/components/CallableFunctionForm.tsx b/packages/web/src/components/CallableFunctionForm.tsx index 8008c33..d04a809 100644 --- a/packages/web/src/components/CallableFunctionForm.tsx +++ b/packages/web/src/components/CallableFunctionForm.tsx @@ -16,7 +16,7 @@ import CallableFunctionFormParameters from "./CallableFunctionFormParameters"; import OptionalTextInput from "./OptionalTextInput"; import { modals } from "@mantine/modals"; import React, { Suspense } from "react"; -import useCallableFunctions from "../hooks/useCallableFunctions"; +import { deleteCallableFunction } from "../store/actions/callableFunctions/deleteCallableFunction"; const CallableFunctionFormCode = React.lazy( () => import("./CallableFunctionFormCode") @@ -35,7 +35,6 @@ const CallableFunctionFormProvided = ({ isNew = false, }: CallableFunctionFormProvidedProps) => { const form = useCallableFunctionForm(); - const { deleteCallableFunction } = useCallableFunctions(); const navigate = useNavigate(); const openDeleteModal = React.useCallback(() => { @@ -57,12 +56,7 @@ const CallableFunctionFormProvided = ({ navigate("/functions"); }, }); - }, [ - deleteCallableFunction, - form.values.displayName, - form.values.id, - navigate, - ]); + }, [form.values.displayName, form.values.id, navigate]); return ( diff --git a/packages/web/src/components/CallableFunctionImportButton.tsx b/packages/web/src/components/CallableFunctionImportButton.tsx index fbbb5da..d820747 100644 --- a/packages/web/src/components/CallableFunctionImportButton.tsx +++ b/packages/web/src/components/CallableFunctionImportButton.tsx @@ -8,13 +8,12 @@ import { CallableFunctionExport } from "../entities/callableFunctionExport"; import useCallableFunctions from "../hooks/useCallableFunctions"; import { notifications } from "@mantine/notifications"; import getErrorInfo from "../utils/getErrorInfo"; +import { useAppStore } from "../store"; +import { addCallableFunction } from "../store/actions/callableFunctions/addCallableFunction"; const CallableFunctionImportButton = () => { - const { - callableFunctions, - addCallableFunction, - getCallableFunctionDisplayName, - } = useCallableFunctions(); + const callableFunctions = useAppStore((state) => state.callableFunctions); + const { getCallableFunctionDisplayName } = useCallableFunctions(); const [ showImportModal, { open: openImportModal, close: closeImportModal }, @@ -77,12 +76,7 @@ const CallableFunctionImportButton = () => { closeImportModal(); }, - [ - addCallableFunction, - callableFunctions, - closeImportModal, - getCallableFunctionDisplayName, - ] + [callableFunctions, closeImportModal, getCallableFunctionDisplayName] ); return ( diff --git a/packages/web/src/components/Changelog.tsx b/packages/web/src/components/Changelog.tsx new file mode 100644 index 0000000..494aded --- /dev/null +++ b/packages/web/src/components/Changelog.tsx @@ -0,0 +1,46 @@ +import { Box, List, Stack, Text, Timeline, Title } from "@mantine/core"; +import { changelog } from "../changelog"; + +const dateStr = (date: Date) => { + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +}; + +const Changelog = () => { + return ( + + + {changelog.map(({ version, date, sections }, i) => ( + + + {dateStr(date)} + + + {sections.map(({ label, items }, j) => ( + + {label} + + {items.map((item, k) => ( + + {item} + + ))} + + + ))} + + + ))} + + + ); +}; + +export default Changelog; diff --git a/packages/web/src/components/ChangelogButton.tsx b/packages/web/src/components/ChangelogButton.tsx new file mode 100644 index 0000000..7359f13 --- /dev/null +++ b/packages/web/src/components/ChangelogButton.tsx @@ -0,0 +1,57 @@ +import { Modal, ScrollArea, Center, Loader, Text } from "@mantine/core"; +import { useDisclosure } from "@mantine/hooks"; +import React, { Suspense } from "react"; +import { BiCodeAlt } from "react-icons/bi"; +import TippedActionIcon from "./TippedActionIcon"; +import { useAppStore } from "../store"; +import { changelog } from "../changelog"; +import { setLastChangelog } from "../store/actions/appSettings/setLastChangelog"; + +const Changelog = React.lazy(() => import("./Changelog")); + +const latestChangelogVersion = changelog[0].version; + +const ChangelogButton = () => { + const lastChangelog = useAppStore((state) => state.lastChangelog); + const [changelogOpened, { open: openChangelog, close: closeChangelog }] = + useDisclosure(); + + React.useEffect(() => { + if (lastChangelog !== latestChangelogVersion) { + openChangelog(); + setLastChangelog(latestChangelogVersion); + } + }, [lastChangelog, openChangelog]); + + return ( + <> + + + + + Changelog + + } + scrollAreaComponent={ScrollArea.Autosize} + > + + + + } + > + + + + + ); +}; + +export default ChangelogButton; diff --git a/packages/web/src/components/ContextInput.tsx b/packages/web/src/components/ContextInput.tsx index a90497d..f11d901 100644 --- a/packages/web/src/components/ContextInput.tsx +++ b/packages/web/src/components/ContextInput.tsx @@ -4,7 +4,7 @@ import TippedActionIcon from "./TippedActionIcon"; import { useDisclosure } from "@mantine/hooks"; import SaveContextModalBody from "./SavePromptModalBody"; import SavedContextsModalBody from "./SavedPromptsModalBody"; -import usePersistence from "../hooks/usePersistence"; +import { useAppStore } from "../store"; interface ContextInputProps { value: string; @@ -12,9 +12,7 @@ interface ContextInputProps { } const ContextInput = ({ value, onChange }: ContextInputProps) => { - const { - persistence: { contexts }, - } = usePersistence(); + const savedContexts = useAppStore((state) => state.savedContexts); const [ showSaveContextModal, { open: openSaveContextModal, close: closeSaveContextModal }, @@ -35,7 +33,7 @@ const ContextInput = ({ value, onChange }: ContextInputProps) => { label="Context" rightSection={ - {contexts.length > 0 && ( + {savedContexts.length > 0 && ( setCurrentTab( diff --git a/packages/web/src/components/ConversationFormFunctionsTab.tsx b/packages/web/src/components/ConversationFormFunctionsTab.tsx index 6afcde9..a1dcab3 100644 --- a/packages/web/src/components/ConversationFormFunctionsTab.tsx +++ b/packages/web/src/components/ConversationFormFunctionsTab.tsx @@ -4,10 +4,11 @@ import useConversationForm from "../hooks/useConversationForm"; import { AiOutlineFunction } from "react-icons/ai"; import { Link } from "react-router-dom"; import useCallableFunctions from "../hooks/useCallableFunctions"; +import { useAppStore } from "../store"; const ConversationFormFunctionsTab = () => { - const { callableFunctions, getCallableFunctionDisplayName } = - useCallableFunctions(); + const callableFunctions = useAppStore((state) => state.callableFunctions); + const { getCallableFunctionDisplayName } = useCallableFunctions(); const form = useConversationForm(); return ( diff --git a/packages/web/src/components/ConversationNavbar.tsx b/packages/web/src/components/ConversationNavbar.tsx index 061fe88..d78fcbc 100644 --- a/packages/web/src/components/ConversationNavbar.tsx +++ b/packages/web/src/components/ConversationNavbar.tsx @@ -24,6 +24,9 @@ import SettingsFormModal from "./SettingsFormModal"; import { FaRegQuestionCircle } from "react-icons/fa"; import About from "./About"; import { DISCORD_SERVER_INVITE } from "../config/constants"; +import { useAppStore } from "../store"; +import { setActiveConversation } from "../store/actions/conversations/setActiveConversation"; +import ChangelogButton from "./ChangelogButton"; const useStyles = createStyles(() => ({ burger: { @@ -35,8 +38,8 @@ const useStyles = createStyles(() => ({ })); const AppNavbar = () => { - const { activeConversation, setActiveConversation, showUsage } = - useConversationManager(); + const showUsage = useAppStore((state) => state.showUsage); + const { activeConversation } = useConversationManager(); const { classes } = useStyles(); const theme = useMantineTheme(); const [navbarOpened, { close: closeNavbar, toggle: toggleNavbar }] = @@ -139,6 +142,7 @@ const AppNavbar = () => { > + GPT Turbo Web v{APP_VERSION} by{" "} @@ -158,7 +162,7 @@ const AppNavbar = () => { onClose={closeAbout} size="lg" centered - withCloseButton={false} + title={About GPT Turbo Web} > diff --git a/packages/web/src/components/FunctionsImportWarning.tsx b/packages/web/src/components/FunctionsImportWarning.tsx index 3966d0d..0a2963b 100644 --- a/packages/web/src/components/FunctionsImportWarning.tsx +++ b/packages/web/src/components/FunctionsImportWarning.tsx @@ -1,14 +1,16 @@ import { Alert, List, Center, Button, Text } from "@mantine/core"; import { FaExclamationTriangle } from "react-icons/fa"; -import useCallableFunctions from "../hooks/useCallableFunctions"; +import { useAppStore } from "../store"; +import { dismissFunctionsImportWarning } from "../store/actions/callableFunctions/dismissFunctionsImportWarning"; interface FunctionsImportWarningProps { children?: React.ReactNode; } const FunctionsImportWarning = ({ children }: FunctionsImportWarningProps) => { - const { dismissFunctionsImportWarning, showFunctionsImportWarning } = - useCallableFunctions(); + const showFunctionsImportWarning = useAppStore( + (state) => state.showFunctionsImportWarning + ); if (!showFunctionsImportWarning) return <>{children}; diff --git a/packages/web/src/components/FunctionsWarning.tsx b/packages/web/src/components/FunctionsWarning.tsx index 5495fc7..d73f432 100644 --- a/packages/web/src/components/FunctionsWarning.tsx +++ b/packages/web/src/components/FunctionsWarning.tsx @@ -1,14 +1,16 @@ import { Alert, List, Anchor, Center, Button } from "@mantine/core"; import { FaExclamationTriangle } from "react-icons/fa"; -import useCallableFunctions from "../hooks/useCallableFunctions"; +import { useAppStore } from "../store"; +import { dismissFunctionsWarning } from "../store/actions/callableFunctions/dismissFunctionsWarning"; interface FunctionsWarningProps { children?: React.ReactNode; } const FunctionsWarning = ({ children }: FunctionsWarningProps) => { - const { dismissFunctionsWarning, showFunctionsWarning } = - useCallableFunctions(); + const showFunctionsWarning = useAppStore( + (state) => state.showFunctionsWarning + ); if (!showFunctionsWarning) return <>{children}; diff --git a/packages/web/src/components/InlineConfirmButton.tsx b/packages/web/src/components/InlineConfirmButton.tsx new file mode 100644 index 0000000..5047094 --- /dev/null +++ b/packages/web/src/components/InlineConfirmButton.tsx @@ -0,0 +1,61 @@ +import { Button, ButtonProps } from "@mantine/core"; +import { useDisclosure, useTimeout } from "@mantine/hooks"; +import React from "react"; + +interface InlineConfirmButtonBaseProps { + onConfirm: () => void; + confirm?: React.ReactNode; + confirmVariant?: ButtonProps["variant"]; + timeout?: number; + onClick?: () => void; +} + +type InlineConfirmButtonButtonProps = + import("@mantine/utils").PolymorphicComponentProps; + +type InlineConfirmButtonProps = InlineConfirmButtonBaseProps & + InlineConfirmButtonButtonProps; + +const InlineConfirmButton = ({ + onConfirm, + confirm, + confirmVariant = "filled", + children, + variant = "outline", + onClick, + ...buttonProps +}: InlineConfirmButtonProps) => { + const [isConfirm, { open: showConfirm, close: hideConfirm }] = + useDisclosure(); + const { start: startConfirmTimeout, clear: clearConfirmTimeout } = + useTimeout(() => hideConfirm(), 3000); + + const startConfirm = React.useCallback(() => { + showConfirm(); + startConfirmTimeout(); + }, [showConfirm, startConfirmTimeout]); + + const stopConfirm = React.useCallback(() => { + hideConfirm(); + clearConfirmTimeout(); + }, [clearConfirmTimeout, hideConfirm]); + + const handleClick = React.useCallback(() => { + onClick?.(); + if (!isConfirm) return startConfirm(); + onConfirm(); + stopConfirm(); + }, [onClick, isConfirm, startConfirm, onConfirm, stopConfirm]); + + return ( + + ); +}; + +export default InlineConfirmButton; diff --git a/packages/web/src/components/NavbarConversation.tsx b/packages/web/src/components/NavbarConversation.tsx index 2c3cbbd..848c0d3 100644 --- a/packages/web/src/components/NavbarConversation.tsx +++ b/packages/web/src/components/NavbarConversation.tsx @@ -13,6 +13,9 @@ import React from "react"; import { useForm } from "@mantine/form"; import TippedActionIcon from "./TippedActionIcon"; import NavbarConversationInfo from "./NavbarConversationInfo"; +import { setConversationName } from "../store/actions/conversations/setConversationName"; +import { removeConversation } from "../store/actions/conversations/removeConversation"; +import { setActiveConversation } from "../store/actions/conversations/setActiveConversation"; interface NavbarConversationProps { conversation: Conversation; @@ -65,13 +68,8 @@ const NavbarConversation = ({ conversation, onClick, }: NavbarConversationProps) => { - const { - activeConversation, - setActiveConversation, - removeConversation, - getConversationName, - setConversationName, - } = useConversationManager(); + const { activeConversation, getConversationName } = + useConversationManager(); const [isDeleting, setIsDeleting] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false); const isActive = conversation.id === activeConversation?.id; @@ -98,7 +96,7 @@ const NavbarConversation = ({ const onDelete = React.useCallback(() => { removeConversation(conversation.id); setIsDeleting(false); - }, [conversation.id, removeConversation]); + }, [conversation.id]); const onCancel = React.useCallback(() => { setIsDeleting(false); diff --git a/packages/web/src/components/NavbarConversationInfo.tsx b/packages/web/src/components/NavbarConversationInfo.tsx index a36e429..de427c2 100644 --- a/packages/web/src/components/NavbarConversationInfo.tsx +++ b/packages/web/src/components/NavbarConversationInfo.tsx @@ -16,8 +16,8 @@ import { BsShieldSlash, } from "react-icons/bs"; import { TbCircleDashed } from "react-icons/tb"; -import usePersistence from "../hooks/usePersistence"; import NavbarConverationInfoIcon from "./NavbarConverationInfoIcon"; +import { useAppStore } from "../store"; interface NavbarConversationInfoProps { conversation: Conversation; @@ -29,7 +29,9 @@ const NavbarConversationInfo = ({ conversation, }: NavbarConversationInfoProps) => { const { model, dry, disableModeration, stream } = conversation.getConfig(); - const { persistedConversationIds } = usePersistence(); + const persistedConversationIds = useAppStore( + (state) => state.persistedConversationIds + ); const persisted = persistedConversationIds.includes(conversation.id); diff --git a/packages/web/src/components/NavbarConversations.tsx b/packages/web/src/components/NavbarConversations.tsx index 8d812d5..e68064e 100644 --- a/packages/web/src/components/NavbarConversations.tsx +++ b/packages/web/src/components/NavbarConversations.tsx @@ -12,6 +12,8 @@ import React from "react"; import TippedActionIcon from "./TippedActionIcon"; import { BiTrash } from "react-icons/bi"; import { useTimeout } from "@mantine/hooks"; +import { useAppStore } from "../store"; +import { removeConversation } from "../store/actions/conversations/removeConversation"; interface NavbarConversationsProps { onConversationSelect?: () => void; @@ -74,11 +76,8 @@ const NavbarConversations = ({ onConversationSelect = () => {}, }: NavbarConversationsProps) => { const { classes } = useStyles(); - const { - conversations: allConversations, - getConversationLastEdit, - removeConversation, - } = useConversationManager(); + const conversations = useAppStore((state) => state.conversations); + const { getConversationLastEdit } = useConversationManager(); const [deleteConfirmation, setdeleteConfirmation] = React.useState< string | null >(null); @@ -86,7 +85,7 @@ const NavbarConversations = ({ useTimeout(() => setdeleteConfirmation(null), 3000); const conversationGroups = React.useMemo(() => { - return allConversations + return conversations .map((c) => ({ conversation: c, lastEdit: getConversationLastEdit(c.id), @@ -99,8 +98,8 @@ const NavbarConversations = ({ } acc[relativeDate].push(conversation); return acc; - }, {} as Record); - }, [allConversations, getConversationLastEdit]); + }, {} as Record); + }, [conversations, getConversationLastEdit]); const makeDeleteGroup = React.useCallback( (group: string) => () => { @@ -118,7 +117,6 @@ const NavbarConversations = ({ clearUnconfirmDelete, conversationGroups, deleteConfirmation, - removeConversation, startUnconfirmDelete, ] ); diff --git a/packages/web/src/components/Prompt.tsx b/packages/web/src/components/Prompt.tsx index 9a02a03..b53de0d 100644 --- a/packages/web/src/components/Prompt.tsx +++ b/packages/web/src/components/Prompt.tsx @@ -22,15 +22,13 @@ import { import { BiFolder, BiPaperPlane } from "react-icons/bi"; import TippedActionIcon from "./TippedActionIcon"; import SavedPromptsModalBody from "./SavedPromptsModalBody"; -import usePersistence from "../hooks/usePersistence"; import getErrorInfo from "../utils/getErrorInfo"; +import { useAppStore } from "../store"; +import { setConversationLastEdit } from "../store/actions/conversations/setConversationLastEdit"; const Prompt = () => { - const { - activeConversation: conversation, - showUsage, - setConversationLastEdit, - } = useConversationManager(); + const showUsage = useAppStore((state) => state.showUsage); + const { activeConversation: conversation } = useConversationManager(); const form = useForm({ initialValues: { prompt: "", @@ -38,9 +36,7 @@ const Prompt = () => { }); const [isStreaming, setIsStreaming] = React.useState(false); - const { - persistence: { prompts }, - } = usePersistence(); + const savedPrompts = useAppStore((state) => state.savedPrompts); const [ showSavedPromptsModal, { open: openSavedPromptsModal, close: closeSavedPromptsModal }, @@ -128,7 +124,7 @@ const Prompt = () => { spacing="xs" pr="xs" > - {prompts.length > 0 && ( + {savedPrompts.length > 0 && ( { - const { - saveContext, - savePrompt, - persistence: { contexts, prompts }, - } = usePersistence(); + const savedContexts = useAppStore((state) => state.savedContexts); + const savedPrompts = useAppStore((state) => state.savedPrompts); const save = mode === "context" ? saveContext : savePrompt; - const items = mode === "context" ? contexts : prompts; + const items = mode === "context" ? savedContexts : savedPrompts; const schema = - mode === "context" ? persistenceContextSchema : persistencePromptSchema; + mode === "context" + ? persistenceSavedContextSchema + : persistenceSavedPromptSchema; const form = useForm({ initialValues: { @@ -52,10 +53,7 @@ const SavePromptModalBody = ({ }); const onSubmit = form.onSubmit((values) => { - save({ - name: values.name, - value, - }); + save(values.name, value); close(); }); diff --git a/packages/web/src/components/SavedPromptsModalBody.tsx b/packages/web/src/components/SavedPromptsModalBody.tsx index d0d8687..4803475 100644 --- a/packages/web/src/components/SavedPromptsModalBody.tsx +++ b/packages/web/src/components/SavedPromptsModalBody.tsx @@ -1,8 +1,10 @@ import { Accordion, Box, Stack } from "@mantine/core"; -import usePersistence from "../hooks/usePersistence"; import TippedActionIcon from "./TippedActionIcon"; import { BiImport, BiTrash } from "react-icons/bi"; import React from "react"; +import { useAppStore } from "../store"; +import { removeSavedContext } from "../store/actions/savedContexts/removeSavedContext"; +import { removeSavedPrompt } from "../store/actions/savedPrompts/removeSavedPrompt"; interface SavedContextsModalBodyProps { onSelect: (value: string) => void; @@ -15,14 +17,11 @@ const SavedPromptsModalBody = ({ onSelect, mode, }: SavedContextsModalBodyProps) => { - const { - removeContext, - removePrompt, - persistence: { contexts, prompts }, - } = usePersistence(); + const savedContexts = useAppStore((state) => state.savedContexts); + const savedPrompts = useAppStore((state) => state.savedPrompts); - const remove = mode === "context" ? removeContext : removePrompt; - const items = mode === "context" ? contexts : prompts; + const remove = mode === "context" ? removeSavedContext : removeSavedPrompt; + const items = mode === "context" ? savedContexts : savedPrompts; React.useEffect(() => { if (items.length === 0) { diff --git a/packages/web/src/components/SettingsFormModal.tsx b/packages/web/src/components/SettingsFormModal.tsx index ebc06af..621a08d 100644 --- a/packages/web/src/components/SettingsFormModal.tsx +++ b/packages/web/src/components/SettingsFormModal.tsx @@ -1,9 +1,10 @@ import { Container, Modal, ModalProps, useMantineTheme } from "@mantine/core"; import { useMediaQuery } from "@mantine/hooks"; -import useSettings from "../hooks/useSettings"; import React from "react"; import { ConversationFormValues } from "../contexts/ConversationFormContext"; import ConversationForm from "./ConversationForm"; +import { useAppStore } from "../store"; +import { setDefaultSettings } from "../store/actions/defaultConversationSettings/setDefaultSettings"; type SettingsFormModalProps = ModalProps; @@ -13,18 +14,17 @@ const SettingsFormModal = ({ }: SettingsFormModalProps) => { const theme = useMantineTheme(); const isSm = useMediaQuery(`(max-width: ${theme.breakpoints.md})`); - - const { settings, setSettings } = useSettings(); + const settings = useAppStore((state) => state.defaultSettings); const onSubmit = React.useCallback( (values: ConversationFormValues) => { - setSettings({ + setDefaultSettings({ ...settings, ...values, }); onClose(); }, - [onClose, setSettings, settings] + [onClose, settings] ); return ( diff --git a/packages/web/src/components/StorageLoadError.tsx b/packages/web/src/components/StorageLoadError.tsx new file mode 100644 index 0000000..166b196 --- /dev/null +++ b/packages/web/src/components/StorageLoadError.tsx @@ -0,0 +1,133 @@ +import { + Accordion, + Button, + Group, + JsonInput, + Stack, + Text, + Textarea, +} from "@mantine/core"; +import React from "react"; +import getErrorInfo from "../utils/getErrorInfo"; +import { STORAGE_PERSISTENCE_KEY } from "../config/constants"; +import { BiBug } from "react-icons/bi"; +import { BsFire } from "react-icons/bs"; +import { storeVersion } from "../store/persist/migrations"; + +interface StorageLoadErrorProps { + isMigrationError: boolean; + error: Error; + currentData: string; +} + +const StorageLoadError = ({ + isMigrationError, + error, + currentData, +}: StorageLoadErrorProps) => { + const reportLink = React.useMemo(() => { + const link = "https://github.com/maxijonson/gpt-turbo/issues/new"; + + const title = `[Bug]: Web Storage ${ + isMigrationError ? "Migration" : "Load" + } Error`; + + const info = getErrorInfo(error); + let body = `**App Version**:\n${APP_VERSION}\n\n`; + body += `**Store Version**:\n${storeVersion}\n\n`; + body += `**Is Migration Error**:\n${isMigrationError}\n\n`; + body += `**Error Info**:\n\`\`\`\n${info.title}\n${info.message}\n\`\`\`\n\n`; + body += `**Stack Trace**:\n\`\`\`\n${error.stack}\n\`\`\`\n\n`; + body += `**Current Data**:\n\`\`\`json\n${currentData}\n\`\`\``; + + const params = new URLSearchParams({ + title, + body, + }); + + return `${link}?${params.toString()}`; + }, [currentData, error, isMigrationError]); + + return ( + + + {isMigrationError + ? "There was an error migrating your saved data to the latest version. " + : "There was an error loading your saved data. "} + + Right now, your saved data is still stored in your browser's + local storage, has been logged to the console and is visible + below to ensure you have a temporary copy. However, the + application cannot be used with invalid/corrupted data. + + + + Unfortunately, local storage is a pretty "primitive" storage + mechanism, so it is not easy to ensure data integrity as the + application evolves and there are no fixes other than editing + the value manually... If you're comfortable working with JSON, + you can try to fix the data yourself. + + + Ultimately, it's recommended to erase your saved data and start + fresh. You can do this by clicking the "Reset" button below. + + + If you think this is an unexpected bug, you can report it on + GitHub. Press the "Report on GitHub" button below to open a new + issue with the error information pre-filled. (Please check for + any sensitive information before submitting!) + + Sorry for the inconvenience! + + + Current Data + + + + + + Error + +