This commit is contained in:
2025-11-13 10:18:36 +08:00
parent 787906c566
commit 7b955de2f0
6 changed files with 1301 additions and 427 deletions
+380 -26
View File
@@ -11,9 +11,12 @@
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"antd": "^5.28.0", "antd": "^5.28.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
"framer-motion": "^12.23.24",
"playwright": "^1.56.1" "playwright": "^1.56.1"
}, },
"devDependencies": { "devDependencies": {
@@ -139,7 +142,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-validator-identifier": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1",
@@ -195,7 +197,6 @@
"version": "7.28.5", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
"integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.5", "@babel/parser": "^7.28.5",
@@ -229,7 +230,6 @@
"version": "7.28.0", "version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -239,7 +239,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
"integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/traverse": "^7.27.1", "@babel/traverse": "^7.27.1",
@@ -281,7 +280,6 @@
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -291,7 +289,6 @@
"version": "7.28.5", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -325,7 +322,6 @@
"version": "7.28.5", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.28.5" "@babel/types": "^7.28.5"
@@ -398,7 +394,6 @@
"version": "7.27.2", "version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
"integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
@@ -413,7 +408,6 @@
"version": "7.28.5", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
"integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
@@ -432,7 +426,6 @@
"version": "7.28.5", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.27.1",
@@ -866,18 +859,197 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/@emotion/babel-plugin": {
"version": "11.13.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
"integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.16.7",
"@babel/runtime": "^7.18.3",
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/serialize": "^1.3.3",
"babel-plugin-macros": "^3.1.0",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
"license": "MIT"
},
"node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
"license": "MIT"
},
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@emotion/babel-plugin/node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
"license": "MIT"
},
"node_modules/@emotion/cache": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
"integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
"license": "MIT",
"dependencies": {
"@emotion/memoize": "^0.9.0",
"@emotion/sheet": "^1.4.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"stylis": "4.2.0"
}
},
"node_modules/@emotion/cache/node_modules/stylis": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
"integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
"license": "MIT"
},
"node_modules/@emotion/hash": { "node_modules/@emotion/hash": {
"version": "0.8.0", "version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@emotion/is-prop-valid": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
"integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
"license": "MIT",
"dependencies": {
"@emotion/memoize": "^0.9.0"
}
},
"node_modules/@emotion/memoize": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
"integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
"license": "MIT"
},
"node_modules/@emotion/react": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/cache": "^11.14.0",
"@emotion/serialize": "^1.3.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
"@emotion/utils": "^1.4.2",
"@emotion/weak-memoize": "^0.4.0",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/serialize": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
"integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
"license": "MIT",
"dependencies": {
"@emotion/hash": "^0.9.2",
"@emotion/memoize": "^0.9.0",
"@emotion/unitless": "^0.10.0",
"@emotion/utils": "^1.4.2",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/serialize/node_modules/@emotion/hash": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
"license": "MIT"
},
"node_modules/@emotion/serialize/node_modules/@emotion/unitless": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
"integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
"license": "MIT"
},
"node_modules/@emotion/sheet": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
"integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
"license": "MIT"
},
"node_modules/@emotion/styled": {
"version": "11.14.1",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5",
"@emotion/is-prop-valid": "^1.3.0",
"@emotion/serialize": "^1.3.3",
"@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
"@emotion/utils": "^1.4.2"
},
"peerDependencies": {
"@emotion/react": "^11.0.0-rc.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/unitless": { "node_modules/@emotion/unitless": {
"version": "0.7.5", "version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@emotion/use-insertion-effect-with-fallbacks": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
"integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8.0"
}
},
"node_modules/@emotion/utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
"integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
"license": "MIT"
},
"node_modules/@emotion/weak-memoize": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12", "version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
@@ -1692,7 +1864,6 @@
"version": "0.3.13", "version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -1714,7 +1885,6 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@@ -1724,14 +1894,12 @@
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31", "version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
@@ -2539,6 +2707,12 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
"license": "MIT"
},
"node_modules/@types/plist": { "node_modules/@types/plist": {
"version": "3.0.5", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz",
@@ -3543,6 +3717,41 @@
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/babel-plugin-macros": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.12.5",
"cosmiconfig": "^7.0.0",
"resolve": "^1.19.0"
},
"engines": {
"node": ">=10",
"npm": ">=6"
}
},
"node_modules/babel-plugin-macros/node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -3968,7 +4177,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@@ -4332,6 +4540,31 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cosmiconfig": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
"license": "MIT",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.2.1",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.10.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/cosmiconfig/node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"license": "ISC",
"engines": {
"node": ">= 6"
}
},
"node_modules/crc": { "node_modules/crc": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz",
@@ -5128,6 +5361,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/error-ex": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.24.0", "version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
@@ -5364,7 +5606,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -5830,6 +6071,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
"license": "MIT"
},
"node_modules/find-up": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -5950,6 +6197,33 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/framer-motion": {
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fs-constants": { "node_modules/fs-constants": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -6425,6 +6699,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hosted-git-info": { "node_modules/hosted-git-info": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -6581,7 +6864,6 @@
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"parent-module": "^1.0.0", "parent-module": "^1.0.0",
@@ -6683,6 +6965,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-arrayish": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT"
},
"node_modules/is-async-function": { "node_modules/is-async-function": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -6766,7 +7054,6 @@
"version": "2.16.1", "version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"hasown": "^2.0.2" "hasown": "^2.0.2"
@@ -7193,7 +7480,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
@@ -7212,7 +7498,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
@@ -7227,6 +7512,12 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"license": "MIT"
},
"node_modules/json-schema-traverse": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -7382,6 +7673,12 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/locate-path": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -7853,6 +8150,21 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -8315,7 +8627,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"callsites": "^3.0.0" "callsites": "^3.0.0"
@@ -8324,6 +8635,24 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.0.0",
"error-ex": "^1.3.1",
"json-parse-even-better-errors": "^2.3.0",
"lines-and-columns": "^1.1.6"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -8358,7 +8687,6 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-scurry": { "node_modules/path-scurry": {
@@ -8395,6 +8723,15 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/pe-library": { "node_modules/pe-library": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz",
@@ -8420,7 +8757,6 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@@ -9319,7 +9655,6 @@
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-refresh": { "node_modules/react-refresh": {
@@ -9502,7 +9837,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "node": ">=4"
@@ -10355,7 +10689,6 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -10607,6 +10940,12 @@
"typescript": ">=4.8.4" "typescript": ">=4.8.4"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -11191,6 +11530,21 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
}
},
"node_modules/yargs": { "node_modules/yargs": {
"version": "17.7.2", "version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+3
View File
@@ -23,9 +23,12 @@
"dependencies": { "dependencies": {
"@electron-toolkit/preload": "^3.0.2", "@electron-toolkit/preload": "^3.0.2",
"@electron-toolkit/utils": "^4.0.0", "@electron-toolkit/utils": "^4.0.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"antd": "^5.28.0", "antd": "^5.28.0",
"axios": "^1.13.2", "axios": "^1.13.2",
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
"framer-motion": "^12.23.24",
"playwright": "^1.56.1" "playwright": "^1.56.1"
}, },
"devDependencies": { "devDependencies": {
+28 -11
View File
@@ -29,8 +29,8 @@ function createFloatingWindow(): void {
const { width } = screen.getPrimaryDisplay().workAreaSize const { width } = screen.getPrimaryDisplay().workAreaSize
floatingWindow = new BrowserWindow({ floatingWindow = new BrowserWindow({
width: 240, width: 260,
height: 210, height: 160,
x: width - 100, x: width - 100,
y: 20, y: 20,
frame: false, frame: false,
@@ -92,15 +92,23 @@ function createSettingsWindow(): void {
} }
function createChatWindow(initialText?: string): void { function createChatWindow(initialText?: string): void {
console.log('createChatWindow called with initialText:', initialText)
// If chat window already exists, focus it and send new text if provided // If chat window already exists, focus it and send new text if provided
if (chatWindow && !chatWindow.isDestroyed()) { if (chatWindow && !chatWindow.isDestroyed()) {
console.log('Chat window already exists, focusing and sending text')
chatWindow.focus() chatWindow.focus()
if (initialText) { if (initialText) {
chatWindow.webContents.send('set-initial-text', initialText) // Add a small delay to ensure the renderer is ready
setTimeout(() => {
chatWindow?.webContents.send('set-initial-text', initialText)
console.log('Sent initial text to existing window:', initialText)
}, 100)
} }
return return
} }
console.log('Creating new chat window')
chatWindow = new BrowserWindow({ chatWindow = new BrowserWindow({
width: 800, width: 800,
height: 600, height: 600,
@@ -122,8 +130,16 @@ function createChatWindow(initialText?: string): void {
// Send initial text after page loads // Send initial text after page loads
if (initialText) { if (initialText) {
console.log('Setting up did-finish-load listener for initial text')
chatWindow.webContents.once('did-finish-load', () => { chatWindow.webContents.once('did-finish-load', () => {
chatWindow?.webContents.send('set-initial-text', initialText) console.log('Chat window did-finish-load event fired')
// Add a small delay to ensure React components are mounted
setTimeout(() => {
if (chatWindow && !chatWindow.isDestroyed()) {
console.log('Sending initial text to new window:', initialText)
chatWindow.webContents.send('set-initial-text', initialText)
}
}, 200)
}) })
} }
@@ -338,16 +354,17 @@ function registerGlobalShortcuts(): void {
const registered = globalShortcut.register(shortcut, () => { const registered = globalShortcut.register(shortcut, () => {
if (floatingWindow && !floatingWindow.isDestroyed()) { if (floatingWindow && !floatingWindow.isDestroyed()) {
// Get clipboard text for selected text // Read clipboard content (user should copy text with Command+C first)
const selectedText = clipboard.readText('selection') // We only read the main clipboard, not the selection clipboard
const text = selectedText || clipboard.readText() const text = clipboard.readText()
if (text && text.trim()) { console.log('Command+K pressed, clipboard content:', text?.substring(0, 50))
// Send event to renderer to show prompt
floatingWindow.webContents.send('show-text-prompt', text.trim()) // Always send the event to toggle action menu
// Pass clipboard text (empty string if clipboard is empty)
floatingWindow.webContents.send('show-text-prompt', text || '')
floatingWindow.focus() floatingWindow.focus()
} }
}
}) })
if (!registered) { if (!registered) {
+340 -112
View File
@@ -1,6 +1,10 @@
import React, { useState, useEffect, useRef } from 'react' import React, { useState, useEffect, useRef } from 'react'
import { Input, Button, Typography, Space, Modal, message, Drawer, Skeleton } from 'antd' import { Input, Button, Typography, Modal, message, Drawer, Skeleton } from 'antd'
import type { TextAreaRef } from 'antd/es/input/TextArea'
import { SendOutlined, CommentOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons' import { SendOutlined, CommentOutlined, ReloadOutlined, SettingOutlined } from '@ant-design/icons'
import { motion, AnimatePresence } from 'framer-motion'
import styled from '@emotion/styled'
import { lightTheme, darkTheme, Theme } from '../theme'
const { TextArea } = Input const { TextArea } = Input
const { Text } = Typography const { Text } = Typography
@@ -35,10 +39,178 @@ interface CommentData {
replies?: CommentData[] replies?: CommentData[]
} }
// Styled Components
const ChatContainer = styled.div<{ theme: Theme }>`
display: flex;
flex-direction: column;
height: 100vh;
background: ${(props) => props.theme.colors.background};
font-family: ${(props) => props.theme.typography.fontFamily};
transition: background ${(props) => props.theme.animation.normal} ease;
`
const Header = styled.div<{ theme: Theme }>`
display: flex;
align-items: center;
justify-content: space-between;
padding: ${(props) => props.theme.spacing.md} ${(props) => props.theme.spacing.lg};
background: ${(props) => props.theme.colors.glassBackground};
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-bottom: 1px solid ${(props) => props.theme.colors.glassBorder};
position: sticky;
top: 0;
z-index: 10;
`
const Title = styled.h1<{ theme: Theme }>`
margin: 0;
font-size: ${(props) => props.theme.typography.fontSize.xl};
font-weight: ${(props) => props.theme.typography.fontWeight.semibold};
color: ${(props) => props.theme.colors.textPrimary};
`
const HeaderActions = styled.div`
display: flex;
gap: 8px;
`
const MessagesContainer = styled.div<{ theme: Theme }>`
flex: 1;
overflow-y: auto;
padding: ${(props) => props.theme.spacing.lg};
display: flex;
flex-direction: column;
gap: ${(props) => props.theme.spacing.md};
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: ${(props) => props.theme.colors.border};
border-radius: ${(props) => props.theme.borderRadius.full};
}
&::-webkit-scrollbar-thumb:hover {
background: ${(props) => props.theme.colors.textTertiary};
}
`
const MessageBubble = styled(motion.div)<{ role: 'user' | 'assistant'; theme: Theme }>`
max-width: 70%;
padding: ${(props) => props.theme.spacing.md} ${(props) => props.theme.spacing.lg};
border-radius: ${(props) => props.theme.borderRadius.md};
align-self: ${(props) => (props.role === 'user' ? 'flex-end' : 'flex-start')};
background: ${(props) =>
props.role === 'user' ? props.theme.colors.userBubble : props.theme.colors.aiBubble};
color: ${(props) =>
props.role === 'user' ? props.theme.colors.userBubbleText : props.theme.colors.aiBubbleText};
box-shadow: ${(props) => props.theme.shadows.md};
word-wrap: break-word;
white-space: pre-wrap;
font-size: ${(props) => props.theme.typography.fontSize.base};
line-height: 1.5;
backdrop-filter: ${(props) => (props.role === 'assistant' ? 'blur(20px)' : 'none')};
-webkit-backdrop-filter: ${(props) => (props.role === 'assistant' ? 'blur(20px)' : 'none')};
border: ${(props) =>
props.role === 'assistant' ? `1px solid ${props.theme.colors.glassBorder}` : 'none'};
transition: transform ${(props) => props.theme.animation.fast} ease;
&:hover {
transform: scale(1.01);
}
`
const MessageActions = styled(motion.div)<{ theme: Theme }>`
display: flex;
gap: ${(props) => props.theme.spacing.sm};
margin-top: ${(props) => props.theme.spacing.sm};
`
const InputContainer = styled.div<{ theme: Theme }>`
padding: ${(props) => props.theme.spacing.lg};
background: ${(props) => props.theme.colors.glassBackground};
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-top: 1px solid ${(props) => props.theme.colors.glassBorder};
`
const InputWrapper = styled(motion.div)<{ theme: Theme; isMultiLine: boolean }>`
background: ${(props) => props.theme.colors.surface};
border-radius: ${(props) => props.theme.borderRadius.lg};
box-shadow: ${(props) => props.theme.shadows.lg};
overflow: hidden;
display: flex;
flex-direction: ${(props) => (props.isMultiLine ? 'column' : 'row')};
align-items: ${(props) => (props.isMultiLine ? 'flex-end' : 'flex-end')};
gap: ${(props) => props.theme.spacing.sm};
padding: ${(props) => props.theme.spacing.sm};
transition: box-shadow ${(props) => props.theme.animation.fast} ease;
&:focus-within {
box-shadow: ${(props) => props.theme.shadows.xl};
}
`
const SendButton = styled(motion.button)<{ theme: Theme; disabled: boolean }>`
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: ${(props) =>
props.disabled
? props.theme.colors.border
: `linear-gradient(135deg, ${props.theme.colors.primary} 0%, #0051d5 100%)`};
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
box-shadow: ${(props) =>
props.disabled ? 'none' : '0 2px 8px rgba(0, 122, 255, 0.3)'};
transition: all ${(props) => props.theme.animation.fast} ease;
flex-shrink: 0;
padding: 0;
outline: none;
&:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.4);
}
&:active:not(:disabled) {
transform: scale(0.95);
}
svg {
width: 18px;
height: 18px;
transform: translateX(1px);
}
`
const EmptyState = styled.div<{ theme: Theme }>`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: ${(props) => props.theme.colors.textSecondary};
font-size: ${(props) => props.theme.typography.fontSize.lg};
`
const Chat: React.FC = () => { const Chat: React.FC = () => {
const [isDarkMode, setIsDarkMode] = useState(false)
const theme = isDarkMode ? darkTheme : lightTheme
const [messages, setMessages] = useState<Message[]>([]) const [messages, setMessages] = useState<Message[]>([])
const [inputValue, setInputValue] = useState('') const [inputValue, setInputValue] = useState('')
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [isMultiLine, setIsMultiLine] = useState(false)
const [currentArticleUrl, setCurrentArticleUrl] = useState<string>('') const [currentArticleUrl, setCurrentArticleUrl] = useState<string>('')
const [lastAiResponse, setLastAiResponse] = useState<string>('') const [lastAiResponse, setLastAiResponse] = useState<string>('')
const [isQrModalVisible, setIsQrModalVisible] = useState(false) const [isQrModalVisible, setIsQrModalVisible] = useState(false)
@@ -46,6 +218,7 @@ const Chat: React.FC = () => {
const [qrCodeError, setQrCodeError] = useState<string>('') const [qrCodeError, setQrCodeError] = useState<string>('')
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false) const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false)
const [confirmUsername, setConfirmUsername] = useState<string>('') const [confirmUsername, setConfirmUsername] = useState<string>('')
const [editableComment, setEditableComment] = useState<string>('')
const [regeneratingMessageId, setRegeneratingMessageId] = useState<string | null>(null) const [regeneratingMessageId, setRegeneratingMessageId] = useState<string | null>(null)
const [isAccountDrawerVisible, setIsAccountDrawerVisible] = useState(false) const [isAccountDrawerVisible, setIsAccountDrawerVisible] = useState(false)
const [isCheckingLoginStatus, setIsCheckingLoginStatus] = useState(false) const [isCheckingLoginStatus, setIsCheckingLoginStatus] = useState(false)
@@ -58,13 +231,28 @@ const Chat: React.FC = () => {
const messagesContainerRef = useRef<HTMLDivElement>(null) const messagesContainerRef = useRef<HTMLDivElement>(null)
const shouldAutoScrollRef = useRef(true) const shouldAutoScrollRef = useRef(true)
const isArticleRequestRef = useRef(false) // 使用 ref 而不是 state 以避免异步问题 const isArticleRequestRef = useRef(false) // 使用 ref 而不是 state 以避免异步问题
const inputRef = useRef<TextAreaRef>(null) // Ant Design TextArea ref
// Listen for initial text from main process // Listen for initial text from main process
useEffect(() => { useEffect(() => {
const unsubscribe = window.electron.ipcRenderer.on( const unsubscribe = window.electron.ipcRenderer.on(
'set-initial-text', 'set-initial-text',
(_: unknown, text: string) => { (_: unknown, text: string) => {
console.log('Chat: Received initial text:', text?.substring(0, 50))
if (text && text.trim()) {
setInputValue(text) setInputValue(text)
// Focus the input area after setting the value
setTimeout(() => {
if (inputRef.current) {
inputRef.current.focus()
// Move cursor to the end
const textarea = inputRef.current.resizableTextArea?.textArea
if (textarea) {
textarea.setSelectionRange(textarea.value.length, textarea.value.length)
}
}
}, 100)
}
} }
) )
@@ -417,9 +605,7 @@ const Chat: React.FC = () => {
setRegeneratingMessageId(messageId) setRegeneratingMessageId(messageId)
// 清空当前消息内容,显示加载状态 // 清空当前消息内容,显示加载状态
setMessages((prev) => setMessages((prev) => prev.map((msg) => (msg.id === messageId ? { ...msg, content: '' } : msg)))
prev.map((msg) => (msg.id === messageId ? { ...msg, content: '' } : msg))
)
try { try {
// 获取模型配置 // 获取模型配置
@@ -590,6 +776,7 @@ const Chat: React.FC = () => {
const showConfirmDialog = (username?: string): void => { const showConfirmDialog = (username?: string): void => {
console.log('showConfirmDialog called with username:', username) console.log('showConfirmDialog called with username:', username)
setConfirmUsername(username || '当前用户') setConfirmUsername(username || '当前用户')
setEditableComment(lastAiResponse) // 初始化可编辑内容
setIsConfirmModalVisible(true) setIsConfirmModalVisible(true)
} }
@@ -597,7 +784,7 @@ const Chat: React.FC = () => {
const handleConfirmOk = async (): Promise<void> => { const handleConfirmOk = async (): Promise<void> => {
console.log('handleConfirmOk called') console.log('handleConfirmOk called')
console.log('currentArticleUrl:', currentArticleUrl) console.log('currentArticleUrl:', currentArticleUrl)
console.log('lastAiResponse length:', lastAiResponse?.length) console.log('editableComment length:', editableComment?.length)
setIsConfirmModalVisible(false) setIsConfirmModalVisible(false)
message.loading({ content: '正在发送评论...', key: 'posting', duration: 0 }) message.loading({ content: '正在发送评论...', key: 'posting', duration: 0 })
@@ -606,7 +793,7 @@ const Chat: React.FC = () => {
console.log('Invoking post-comment...') console.log('Invoking post-comment...')
const result = await window.electron.ipcRenderer.invoke('post-comment', { const result = await window.electron.ipcRenderer.invoke('post-comment', {
url: currentArticleUrl, url: currentArticleUrl,
comment: lastAiResponse comment: editableComment // 使用编辑后的内容
}) })
console.log('post-comment result:', result) console.log('post-comment result:', result)
@@ -629,7 +816,9 @@ const Chat: React.FC = () => {
} }
const handleConfirmCancel = (): void => { const handleConfirmCancel = (): void => {
console.log('handleConfirmCancel called')
setIsConfirmModalVisible(false) setIsConfirmModalVisible(false)
setEditableComment('')
} }
const handleQrModalCancel = (): void => { const handleQrModalCancel = (): void => {
@@ -728,86 +917,89 @@ const Chat: React.FC = () => {
opacity: 0; opacity: 0;
} }
} }
/* 自定义 TextArea 滚动条样式 - WebKit 浏览器 */
.ant-input::-webkit-scrollbar {
width: 6px;
}
.ant-input::-webkit-scrollbar-track {
background: transparent;
}
.ant-input::-webkit-scrollbar-thumb {
background: ${theme.colors.border};
border-radius: 3px;
}
.ant-input::-webkit-scrollbar-thumb:hover {
background: ${theme.colors.textTertiary};
}
/* Firefox 滚动条样式 */
.ant-input {
scrollbar-width: thin;
scrollbar-color: ${theme.colors.border} transparent;
}
`}</style> `}</style>
<div <ChatContainer theme={theme}>
style={{
display: 'flex',
flexDirection: 'column',
height: '100vh',
background: '#f5f5f5'
}}
>
{/* Header */} {/* Header */}
<div <Header theme={theme}>
style={{ <Title theme={theme}>AI </Title>
padding: '16px 24px', <HeaderActions>
background: '#fff',
borderBottom: '1px solid #e8e8e8',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center'
}}
>
<Text strong style={{ fontSize: '16px' }}>
AI
</Text>
<Button <Button
type="text"
icon={<SettingOutlined />}
onClick={() => setIsDarkMode(!isDarkMode)}
style={{
color: theme.colors.textPrimary,
transition: `all ${theme.animation.fast} ease`
}}
/>
<Button
type="primary"
icon={<SettingOutlined />} icon={<SettingOutlined />}
onClick={() => { onClick={() => {
setIsAccountDrawerVisible(true) setIsAccountDrawerVisible(true)
// 打开 Drawer 时检查登录状态
checkXiaoheiheLoginStatus() checkXiaoheiheLoginStatus()
}} }}
> >
</Button> </Button>
</div> </HeaderActions>
</Header>
{/* Messages List */} {/* Messages List */}
<div <MessagesContainer ref={messagesContainerRef} theme={theme}>
ref={messagesContainerRef}
style={{
flex: 1,
overflowY: 'auto',
padding: '24px'
}}
>
{messages.length === 0 ? ( {messages.length === 0 ? (
<div <EmptyState theme={theme}>
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '100%',
color: '#999'
}}
>
<Text type="secondary">...</Text> <Text type="secondary">...</Text>
</div> </EmptyState>
) : ( ) : (
<Space direction="vertical" style={{ width: '100%' }} size="large"> <AnimatePresence mode="popLayout">
{messages.map((message) => ( {messages.map((message, index) => (
<div <MessageBubble
key={message.id} key={message.id}
role={message.role}
theme={theme}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{
duration: 0.15,
delay: index * 0.03,
ease: [0.25, 0.1, 0.25, 1]
}}
whileHover={{ scale: 1.01 }}
>
<Text
style={{ style={{
display: 'flex', color:
justifyContent: message.role === 'user' ? 'flex-end' : 'flex-start' message.role === 'user'
? theme.colors.userBubbleText
: theme.colors.aiBubbleText
}} }}
> >
<div
style={{
maxWidth: '70%',
padding: '12px 16px',
borderRadius: '12px',
background: message.role === 'user' ? '#1890ff' : '#fff',
color: message.role === 'user' ? '#fff' : '#000',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
wordBreak: 'break-word',
whiteSpace: 'pre-wrap'
}}
>
<Text style={{ color: message.role === 'user' ? '#fff' : '#000' }}>
{message.content} {message.content}
{message.role === 'assistant' && !message.content && isLoading && ( {message.role === 'assistant' && !message.content && isLoading && (
<span <span
@@ -815,7 +1007,7 @@ const Chat: React.FC = () => {
display: 'inline-block', display: 'inline-block',
width: '8px', width: '8px',
height: '16px', height: '16px',
background: '#1890ff', background: theme.colors.primary,
marginLeft: '2px', marginLeft: '2px',
animation: 'blink 1s infinite' animation: 'blink 1s infinite'
}} }}
@@ -825,7 +1017,7 @@ const Chat: React.FC = () => {
<div <div
style={{ style={{
marginTop: '4px', marginTop: '4px',
fontSize: '11px', fontSize: theme.typography.fontSize.xs,
opacity: 0.7 opacity: 0.7
}} }}
> >
@@ -837,7 +1029,12 @@ const Chat: React.FC = () => {
{/* AI 消息的操作按钮 */} {/* AI 消息的操作按钮 */}
{message.role === 'assistant' && message.content && ( {message.role === 'assistant' && message.content && (
<div style={{ marginTop: '12px', display: 'flex', gap: '8px' }}> <MessageActions
theme={theme}
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }}
>
<Button <Button
size="small" size="small"
icon={<ReloadOutlined />} icon={<ReloadOutlined />}
@@ -853,7 +1050,6 @@ const Chat: React.FC = () => {
type="primary" type="primary"
icon={<CommentOutlined />} icon={<CommentOutlined />}
onClick={() => { onClick={() => {
// 更新当前文章 URL 和 AI 回复
setCurrentArticleUrl(message.metadata!.articleUrl!) setCurrentArticleUrl(message.metadata!.articleUrl!)
setLastAiResponse(message.content) setLastAiResponse(message.content)
handlePostComment() handlePostComment()
@@ -862,49 +1058,64 @@ const Chat: React.FC = () => {
</Button> </Button>
)} )}
</div> </MessageActions>
)} )}
</div> </MessageBubble>
</div>
))} ))}
</Space> </AnimatePresence>
)} )}
<div ref={messagesEndRef} /> <div ref={messagesEndRef} />
</div> </MessagesContainer>
{/* Input Area */} {/* Input Area */}
<div <InputContainer theme={theme}>
style={{ <InputWrapper
padding: '16px 24px', theme={theme}
background: '#fff', isMultiLine={isMultiLine}
borderTop: '1px solid #e8e8e8', initial={{ y: 20, opacity: 0 }}
boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.06)' animate={{ y: 0, opacity: 1 }}
}} transition={{ duration: 0.2 }}
> >
<Space direction="vertical" style={{ width: '100%' }} size="middle">
<Space.Compact style={{ width: '100%' }}>
<TextArea <TextArea
rows={4}
ref={inputRef}
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => {
const value = e.target.value
setInputValue(value)
// Check if text contains newline or has multiple lines
setIsMultiLine(value.includes('\n') || value.split('\n').length > 1)
}}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
placeholder="输入消息... (Enter 发送, Shift+Enter 换行)" placeholder="输入消息... (Enter 发送, Shift+Enter 换行)"
autoSize={{ minRows: 1, maxRows: 4 }}
disabled={isLoading} disabled={isLoading}
style={{ flex: 1 }} style={{
flex: 1,
border: 'none',
boxShadow: 'none',
resize: 'none',
outline: 'none'
}}
styles={{
textarea: {
scrollbarWidth: 'thin',
scrollbarColor: `${theme.colors.border} transparent`
}
}}
/> />
<Button <SendButton
type="primary" theme={theme}
icon={<SendOutlined />}
onClick={handleSend}
disabled={!inputValue.trim() || isLoading} disabled={!inputValue.trim() || isLoading}
style={{ height: 'auto' }} onClick={handleSend}
whileTap={{ scale: 0.9 }}
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.15 }}
> >
<SendOutlined />
</Button> </SendButton>
</Space.Compact> </InputWrapper>
</Space> </InputContainer>
</div> </ChatContainer>
</div>
{/* QR Code Login Modal */} {/* QR Code Login Modal */}
<Modal <Modal
@@ -915,6 +1126,11 @@ const Chat: React.FC = () => {
okText="已完成登录" okText="已完成登录"
cancelText="取消" cancelText="取消"
width={400} width={400}
styles={{
mask: { backdropFilter: 'blur(10px)', background: 'rgba(0, 0, 0, 0.45)' }
}}
transitionName="zoom"
maskTransitionName="fade"
> >
<div style={{ textAlign: 'center', padding: '20px 0' }}> <div style={{ textAlign: 'center', padding: '20px 0' }}>
<p style={{ marginBottom: 16, color: '#666' }}>使 APP </p> <p style={{ marginBottom: 16, color: '#666' }}>使 APP </p>
@@ -952,27 +1168,35 @@ const Chat: React.FC = () => {
onCancel={handleConfirmCancel} onCancel={handleConfirmCancel}
okText="确认发送" okText="确认发送"
cancelText="取消" cancelText="取消"
width={500} width={600}
maskClosable={true}
keyboard={true}
styles={{
mask: { backdropFilter: 'blur(10px)', background: 'rgba(0, 0, 0, 0.45)' }
}}
> >
<div> <div>
<p> <p>
<strong>{confirmUsername}</strong> <strong>{confirmUsername}</strong>
</p> </p>
<p style={{ wordBreak: 'break-all', color: '#666' }}>{currentArticleUrl}</p> <p style={{ wordBreak: 'break-all', color: '#666', marginBottom: 16 }}>
<p style={{ marginTop: 16 }}></p> {currentArticleUrl}
<div </p>
<p style={{ marginTop: 16, marginBottom: 8, fontWeight: 500 }}></p>
<TextArea
value={editableComment}
onChange={(e) => setEditableComment(e.target.value)}
placeholder="请输入评论内容"
autoSize={{ minRows: 4, maxRows: 12 }}
style={{ style={{
maxHeight: 200, fontSize: 14,
overflow: 'auto', borderRadius: 8,
padding: 8, border: '1px solid #d9d9d9'
background: '#f5f5f5',
borderRadius: 4,
whiteSpace: 'pre-wrap',
fontSize: 12
}} }}
> />
{lastAiResponse} <p style={{ marginTop: 8, fontSize: 12, color: '#999' }}>
</div> {editableComment?.length || 0}
</p>
</div> </div>
</Modal> </Modal>
@@ -983,6 +1207,10 @@ const Chat: React.FC = () => {
onClose={() => setIsAccountDrawerVisible(false)} onClose={() => setIsAccountDrawerVisible(false)}
open={isAccountDrawerVisible} open={isAccountDrawerVisible}
width={400} width={400}
styles={{
mask: { backdropFilter: 'blur(10px)', background: 'rgba(0, 0, 0, 0.45)' },
body: { padding: '24px' }
}}
> >
<div> <div>
<div style={{ marginBottom: 24 }}> <div style={{ marginBottom: 24 }}>
+219 -209
View File
@@ -1,14 +1,12 @@
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
import ContextMenu from './ContextMenu' import { AnimatePresence, motion } from 'framer-motion'
const FloatingBall: React.FC = () => { const FloatingBall: React.FC = () => {
const [isBlinking, setIsBlinking] = useState(false) const [isBlinking, setIsBlinking] = useState(false)
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false)
const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 })
const [isMouseOverBall, setIsMouseOverBall] = useState(false) const [isMouseOverBall, setIsMouseOverBall] = useState(false)
const [isActionMenuOpen, setIsActionMenuOpen] = useState(false) const [isActionMenuOpen, setIsActionMenuOpen] = useState(false)
const [showTextPrompt, setShowTextPrompt] = useState(false)
const [selectedText, setSelectedText] = useState('') const [selectedText, setSelectedText] = useState('')
const [selectedButtonIndex, setSelectedButtonIndex] = useState(0) // 0: 对话, 1: 设置, 2: 退出
const isDraggingRef = useRef(false) const isDraggingRef = useRef(false)
const startPosRef = useRef({ x: 0, y: 0 }) const startPosRef = useRef({ x: 0, y: 0 })
const windowStartRef = useRef({ x: 0, y: 0 }) const windowStartRef = useRef({ x: 0, y: 0 })
@@ -42,8 +40,15 @@ const FloatingBall: React.FC = () => {
'show-text-prompt', 'show-text-prompt',
(_: unknown, text: string) => { (_: unknown, text: string) => {
setSelectedText(text) setSelectedText(text)
setShowTextPrompt(true) // 切换按钮显示状态
setIsActionMenuOpen(true) setIsActionMenuOpen((prev) => {
const newState = !prev
if (newState) {
// When opening menu, reset to first button
setSelectedButtonIndex(0)
}
return newState
})
} }
) )
@@ -54,6 +59,61 @@ const FloatingBall: React.FC = () => {
} }
}, []) }, [])
// Handle keyboard navigation when menu is open
useEffect(() => {
if (!isActionMenuOpen) return
const handleKeyDown = (e: KeyboardEvent): void => {
switch (e.key) {
case 'ArrowUp':
e.preventDefault()
setSelectedButtonIndex((prev) => (prev === 0 ? 2 : prev - 1))
break
case 'ArrowDown':
e.preventDefault()
setSelectedButtonIndex((prev) => (prev === 2 ? 0 : prev + 1))
break
case 'Tab':
e.preventDefault()
// Tab cycles through options
setSelectedButtonIndex((prev) => (prev === 2 ? 0 : prev + 1))
break
case 'Enter':
e.preventDefault()
executeSelectedAction()
break
case 'Escape':
e.preventDefault()
setIsActionMenuOpen(false)
setSelectedText('')
break
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [isActionMenuOpen, selectedButtonIndex, selectedText])
// Execute the action for the currently selected button
const executeSelectedAction = (): void => {
switch (selectedButtonIndex) {
case 0: // 对话
console.log('对话按钮选中 - 打开聊天窗口')
window.electron.ipcRenderer.send('open-chat', selectedText || undefined)
break
case 1: // 设置
console.log('设置按钮选中 - 打开设置窗口')
window.electron.ipcRenderer.send('open-settings')
break
case 2: // 退出
console.log('退出按钮选中 - 退出应用')
window.electron.ipcRenderer.send('quit-app')
break
}
setIsActionMenuOpen(false)
setSelectedText('')
}
const handleMouseEnterBall = (): void => { const handleMouseEnterBall = (): void => {
setIsMouseOverBall(true) setIsMouseOverBall(true)
// When mouse enters the ball area, stop ignoring mouse events // When mouse enters the ball area, stop ignoring mouse events
@@ -63,45 +123,14 @@ const FloatingBall: React.FC = () => {
const handleMouseLeaveBall = (): void => { const handleMouseLeaveBall = (): void => {
setIsMouseOverBall(false) setIsMouseOverBall(false)
// When mouse leaves the ball area, always restore click-through // When mouse leaves the ball area, always restore click-through
if (!isContextMenuOpen) {
window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true }) window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
} }
}
const handleContextMenu = (e: React.MouseEvent): void => {
e.preventDefault()
// Show custom context menu at cursor position
setContextMenuPosition({ x: e.clientX, y: e.clientY })
setIsContextMenuOpen(true)
// Disable mouse events pass-through when menu is open
window.electron.ipcRenderer.send('set-ignore-mouse-events', false)
}
const handleCloseContextMenu = (): void => {
setIsContextMenuOpen(false)
// Re-enable mouse events pass-through when menu closes, but only if mouse is not over the ball
setTimeout(() => {
if (!isMouseOverBall) {
window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
}
}, 50)
}
const handleSettingsClick = (): void => {
// Send message to main process to open settings
window.electron.ipcRenderer.send('open-settings')
}
const handleQuitClick = (): void => {
// Send message to main process to quit app
window.electron.ipcRenderer.send('quit-app')
}
const handleMouseDown = async (e: React.MouseEvent): Promise<void> => { const handleMouseDown = async (e: React.MouseEvent): Promise<void> => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
// Ignore right click for context menu // Ignore right click
if (e.button === 2) { if (e.button === 2) {
return return
} }
@@ -196,23 +225,6 @@ const FloatingBall: React.FC = () => {
} }
} }
`}</style> `}</style>
<ContextMenu
isOpen={isContextMenuOpen}
position={contextMenuPosition}
onClose={handleCloseContextMenu}
onSettings={handleSettingsClick}
onQuit={handleQuitClick}
onMouseEnter={() => {
// Keep mouse events enabled when hovering over menu
window.electron.ipcRenderer.send('set-ignore-mouse-events', false)
}}
onMouseLeave={() => {
// When mouse leaves menu, restore click-through
if (!isMouseOverBall) {
window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
}
}}
/>
<div <div
style={{ style={{
width: '100%', width: '100%',
@@ -224,202 +236,201 @@ const FloatingBall: React.FC = () => {
pointerEvents: 'none' pointerEvents: 'none'
}} }}
> >
{/* Text Prompt */}
{showTextPrompt && selectedText && (
<div
onMouseEnter={() => {
window.electron.ipcRenderer.send('set-ignore-mouse-events', false)
}}
onMouseDown={(e) => {
e.stopPropagation()
}}
style={{
position: 'absolute',
top: 'calc(50% - 100px)',
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(33, 150, 243, 0.95)',
color: 'white',
padding: '8px 16px',
borderRadius: '20px',
fontSize: '12px',
fontWeight: 500,
whiteSpace: 'nowrap',
boxShadow: '0 2px 12px rgba(33, 150, 243, 0.4)',
pointerEvents: 'auto',
animation: 'fadeInDown 0.3s ease-out',
zIndex: 10,
display: 'flex',
alignItems: 'center',
gap: '8px'
}}
>
<span></span>
<button
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
}}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
console.log('Close button clicked')
setShowTextPrompt(false)
setSelectedText('')
setIsActionMenuOpen(false)
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.4)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.2)'
}}
style={{
background: 'rgba(255, 255, 255, 0.2)',
border: 'none',
borderRadius: '50%',
width: '16px',
height: '16px',
cursor: 'pointer',
fontSize: '10px',
color: 'white',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
padding: 0,
flexShrink: 0,
transition: 'background 0.2s ease'
}}
>
×
</button>
</div>
)}
{/* Action Menu Items */} {/* Action Menu Items */}
<AnimatePresence>
{isActionMenuOpen && ( {isActionMenuOpen && (
<> <>
{/* Action Item 1 - Top Left */} {/* Action Item 1 - 对话 (Top Left) */}
<div <motion.div
initial={{ opacity: 0, scale: 0.3, x: 30, y: 30 }}
animate={{
opacity: 1,
scale: selectedButtonIndex === 0 ? 1.1 : 1,
x: 0,
y: 0
}}
exit={{ opacity: 0, scale: 0.3, x: 30, y: 30 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
style={{ style={{
position: 'absolute', position: 'absolute',
left: 'calc(50% - 90px)', left: 'calc(50% - 105px)',
top: 'calc(50% - 60px)', top: 'calc(50% - 75px)',
width: '40px', width: '44px',
height: '40px', height: '44px',
borderRadius: '50%', borderRadius: '50%',
background: 'linear-gradient(135deg, #4CAF50 0%, #45a049 100%)', background: selectedButtonIndex === 0
? 'rgba(0, 122, 255, 0.15)'
: 'rgba(255, 255, 255, 0.95)',
backdropFilter: 'blur(20px)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'pointer', cursor: 'pointer',
boxShadow: '0 2px 8px rgba(76, 175, 80, 0.4)', boxShadow: selectedButtonIndex === 0
? '0 4px 16px rgba(0, 122, 255, 0.3)'
: '0 2px 8px rgba(0, 0, 0, 0.1)',
pointerEvents: 'auto', pointerEvents: 'auto',
animation: 'slideIn1 0.3s ease-out', transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
transition: 'transform 0.2s ease, box-shadow 0.2s ease' border: selectedButtonIndex === 0
? '2px solid #007AFF'
: '0.5px solid rgba(0, 0, 0, 0.04)'
}} }}
onClick={() => { onClick={() => {
console.log('Action 1 clicked - Opening chat window') console.log('对话按钮点击 - 打开聊天窗口')
// Open chat window with selected text
window.electron.ipcRenderer.send('open-chat', selectedText || undefined) window.electron.ipcRenderer.send('open-chat', selectedText || undefined)
setIsActionMenuOpen(false) setIsActionMenuOpen(false)
setShowTextPrompt(false)
setSelectedText('') setSelectedText('')
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
setSelectedButtonIndex(0)
window.electron.ipcRenderer.send('set-ignore-mouse-events', false) window.electron.ipcRenderer.send('set-ignore-mouse-events', false)
e.currentTarget.style.transform = 'scale(1.1)'
e.currentTarget.style.boxShadow = '0 4px 12px rgba(76, 175, 80, 0.6)'
}} }}
onMouseLeave={(e) => { onMouseLeave={() => {
e.currentTarget.style.transform = 'scale(1)' window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
e.currentTarget.style.boxShadow = '0 2px 8px rgba(76, 175, 80, 0.4)'
}} }}
> >
<span style={{ fontSize: '20px', color: 'white' }}></span> <svg
</div> width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#007AFF"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
</motion.div>
{/* Action Item 2 - Middle Left */} {/* Action Item 2 - 设置 (Middle Left) */}
<div <motion.div
initial={{ opacity: 0, scale: 0.3, x: 40 }}
animate={{
opacity: 1,
scale: selectedButtonIndex === 1 ? 1.1 : 1,
x: 0
}}
exit={{ opacity: 0, scale: 0.3, x: 40 }}
transition={{ duration: 0.2, ease: 'easeOut', delay: 0.05 }}
style={{ style={{
position: 'absolute', position: 'absolute',
left: 'calc(50% - 100px)', left: 'calc(50% - 120px)',
top: 'calc(50% - 20px)', top: 'calc(50% - 22px)',
width: '40px', width: '44px',
height: '40px', height: '44px',
borderRadius: '50%', borderRadius: '50%',
background: 'linear-gradient(135deg, #FF9800 0%, #F57C00 100%)', background: selectedButtonIndex === 1
? 'rgba(142, 142, 147, 0.15)'
: 'rgba(255, 255, 255, 0.95)',
backdropFilter: 'blur(20px)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'pointer', cursor: 'pointer',
boxShadow: '0 2px 8px rgba(255, 152, 0, 0.4)', boxShadow: selectedButtonIndex === 1
? '0 4px 16px rgba(142, 142, 147, 0.3)'
: '0 2px 8px rgba(0, 0, 0, 0.1)',
pointerEvents: 'auto', pointerEvents: 'auto',
animation: 'slideIn2 0.3s ease-out', transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
transition: 'transform 0.2s ease, box-shadow 0.2s ease' border: selectedButtonIndex === 1
? '2px solid #8E8E93'
: '0.5px solid rgba(0, 0, 0, 0.04)'
}} }}
onClick={() => { onClick={() => {
console.log('Action 2 clicked') console.log('设置按钮点击 - 打开设置窗口')
if (selectedText) {
console.log('Selected text:', selectedText)
}
setIsActionMenuOpen(false)
setShowTextPrompt(false)
setSelectedText('')
}}
onMouseEnter={(e) => {
e.currentTarget.style.transform = 'scale(1.1)'
e.currentTarget.style.boxShadow = '0 4px 12px rgba(255, 152, 0, 0.6)'
}}
onMouseLeave={(e) => {
e.currentTarget.style.transform = 'scale(1)'
e.currentTarget.style.boxShadow = '0 2px 8px rgba(255, 152, 0, 0.4)'
}}
>
<span style={{ fontSize: '20px', color: 'white' }}></span>
</div>
{/* Action Item 3 - Bottom Left */}
<div
style={{
position: 'absolute',
left: 'calc(50% - 90px)',
top: 'calc(50% + 20px)',
width: '40px',
height: '40px',
borderRadius: '50%',
background: 'linear-gradient(135deg, #f44336 0%, #d32f2f 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
boxShadow: '0 2px 8px rgba(244, 67, 54, 0.4)',
pointerEvents: 'auto',
animation: 'slideIn3 0.3s ease-out',
transition: 'transform 0.2s ease, box-shadow 0.2s ease'
}}
onClick={() => {
console.log('Action 3 clicked - Opening settings window')
// Open settings window
window.electron.ipcRenderer.send('open-settings') window.electron.ipcRenderer.send('open-settings')
setIsActionMenuOpen(false) setIsActionMenuOpen(false)
setShowTextPrompt(false)
setSelectedText('') setSelectedText('')
}} }}
onMouseEnter={(e) => { onMouseEnter={() => {
setSelectedButtonIndex(1)
window.electron.ipcRenderer.send('set-ignore-mouse-events', false) window.electron.ipcRenderer.send('set-ignore-mouse-events', false)
e.currentTarget.style.transform = 'scale(1.1)'
e.currentTarget.style.boxShadow = '0 4px 12px rgba(244, 67, 54, 0.6)'
}} }}
onMouseLeave={(e) => { onMouseLeave={() => {
e.currentTarget.style.transform = 'scale(1)' window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
e.currentTarget.style.boxShadow = '0 2px 8px rgba(244, 67, 54, 0.4)'
}} }}
> >
<span style={{ fontSize: '20px', color: 'white' }}></span> <svg
</div> width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#8E8E93"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="12" cy="12" r="3" />
<path d="M12 1v6M12 17v6M4.22 4.22l4.24 4.24M15.54 15.54l4.24 4.24M1 12h6M17 12h6M4.22 19.78l4.24-4.24M15.54 8.46l4.24-4.24" />
</svg>
</motion.div>
{/* Action Item 3 - 退出 (Bottom Left) */}
<motion.div
initial={{ opacity: 0, scale: 0.3, x: 30, y: -30 }}
animate={{
opacity: 1,
scale: selectedButtonIndex === 2 ? 1.1 : 1,
x: 0,
y: 0
}}
exit={{ opacity: 0, scale: 0.3, x: 30, y: -30 }}
transition={{ duration: 0.2, ease: 'easeOut', delay: 0.1 }}
style={{
position: 'absolute',
left: 'calc(50% - 105px)',
top: 'calc(50% + 31px)',
width: '44px',
height: '44px',
borderRadius: '50%',
background: selectedButtonIndex === 2
? 'rgba(255, 59, 48, 0.15)'
: 'rgba(255, 255, 255, 0.95)',
backdropFilter: 'blur(20px)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
boxShadow: selectedButtonIndex === 2
? '0 4px 16px rgba(255, 59, 48, 0.3)'
: '0 2px 8px rgba(0, 0, 0, 0.1)',
pointerEvents: 'auto',
transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
border: selectedButtonIndex === 2
? '2px solid #FF3B30'
: '0.5px solid rgba(0, 0, 0, 0.04)'
}}
onClick={() => {
console.log('退出按钮点击 - 退出应用')
window.electron.ipcRenderer.send('quit-app')
setIsActionMenuOpen(false)
setSelectedText('')
}}
onMouseEnter={() => {
setSelectedButtonIndex(2)
window.electron.ipcRenderer.send('set-ignore-mouse-events', false)
}}
onMouseLeave={() => {
window.electron.ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
}}
>
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#FF3B30"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="15 18 9 12 15 6" />
</svg>
</motion.div>
</> </>
)} )}
</AnimatePresence>
{/* Robot Ball Container */} {/* Robot Ball Container */}
<div <div
@@ -433,7 +444,6 @@ const FloatingBall: React.FC = () => {
{/* Robot Ball */} {/* Robot Ball */}
<div <div
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onContextMenu={handleContextMenu}
onMouseEnter={(e) => { onMouseEnter={(e) => {
handleMouseEnterBall() handleMouseEnterBall()
e.currentTarget.style.boxShadow = '0 4px 14px rgba(33, 150, 243, 0.6)' e.currentTarget.style.boxShadow = '0 4px 14px rgba(33, 150, 243, 0.6)'
+262
View File
@@ -0,0 +1,262 @@
// Apple-inspired design system
export interface Theme {
colors: {
// Surfaces
background: string
surface: string
surfaceElevated: string
surfaceHover: string
// Text
textPrimary: string
textSecondary: string
textTertiary: string
// Brand
primary: string
primaryHover: string
primaryActive: string
// Message bubbles
userBubble: string
userBubbleText: string
aiBubble: string
aiBubbleText: string
// Borders
border: string
borderLight: string
// States
success: string
error: string
warning: string
// Glass effect
glassBackground: string
glassBorder: string
}
shadows: {
sm: string
md: string
lg: string
xl: string
}
spacing: {
xs: string
sm: string
md: string
lg: string
xl: string
xxl: string
}
borderRadius: {
sm: string
md: string
lg: string
xl: string
full: string
}
typography: {
fontFamily: string
fontSize: {
xs: string
sm: string
base: string
lg: string
xl: string
xxl: string
}
fontWeight: {
normal: number
medium: number
semibold: number
bold: number
}
}
animation: {
fast: string
normal: string
slow: string
}
}
export const lightTheme: Theme = {
colors: {
// Surfaces - Apple's clean light grays
background: '#f5f5f7',
surface: '#ffffff',
surfaceElevated: '#ffffff',
surfaceHover: '#f9f9f9',
// Text - Apple's neutral grays
textPrimary: '#1d1d1f',
textSecondary: '#6e6e73',
textTertiary: '#86868b',
// Brand - Apple's blue accent
primary: '#007aff',
primaryHover: '#0051d5',
primaryActive: '#004ecb',
// Message bubbles
userBubble: 'linear-gradient(135deg, #007aff 0%, #0051d5 100%)',
userBubbleText: '#ffffff',
aiBubble: 'rgba(255, 255, 255, 0.7)',
aiBubbleText: '#1d1d1f',
// Borders
border: '#d2d2d7',
borderLight: '#e5e5ea',
// States
success: '#34c759',
error: '#ff3b30',
warning: '#ff9500',
// Glass effect
glassBackground: 'rgba(255, 255, 255, 0.7)',
glassBorder: 'rgba(255, 255, 255, 0.18)',
},
shadows: {
sm: '0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02)',
md: '0 4px 6px rgba(0, 0, 0, 0.04), 0 2px 4px rgba(0, 0, 0, 0.02)',
lg: '0 10px 15px rgba(0, 0, 0, 0.06), 0 4px 6px rgba(0, 0, 0, 0.03)',
xl: '0 20px 25px rgba(0, 0, 0, 0.08), 0 10px 10px rgba(0, 0, 0, 0.04)',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
xxl: '48px',
},
borderRadius: {
sm: '6px',
md: '12px',
lg: '16px',
xl: '24px',
full: '9999px',
},
typography: {
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Arial, sans-serif',
fontSize: {
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
xxl: '24px',
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
},
},
animation: {
fast: '150ms',
normal: '250ms',
slow: '350ms',
},
}
export const darkTheme: Theme = {
colors: {
// Surfaces - Apple's dark mode grays
background: '#000000',
surface: '#1c1c1e',
surfaceElevated: '#2c2c2e',
surfaceHover: '#3a3a3c',
// Text - Apple's light grays for dark mode
textPrimary: '#f5f5f7',
textSecondary: '#98989d',
textTertiary: '#636366',
// Brand - Apple's blue accent (slightly brighter for dark mode)
primary: '#0a84ff',
primaryHover: '#409cff',
primaryActive: '#0077ed',
// Message bubbles
userBubble: 'linear-gradient(135deg, #0a84ff 0%, #0077ed 100%)',
userBubbleText: '#ffffff',
aiBubble: 'rgba(44, 44, 46, 0.9)',
aiBubbleText: '#f5f5f7',
// Borders
border: '#38383a',
borderLight: '#48484a',
// States
success: '#32d74b',
error: '#ff453a',
warning: '#ff9f0a',
// Glass effect
glassBackground: 'rgba(44, 44, 46, 0.7)',
glassBorder: 'rgba(255, 255, 255, 0.1)',
},
shadows: {
sm: '0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.2)',
md: '0 4px 6px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2)',
lg: '0 10px 15px rgba(0, 0, 0, 0.4), 0 4px 6px rgba(0, 0, 0, 0.3)',
xl: '0 20px 25px rgba(0, 0, 0, 0.5), 0 10px 10px rgba(0, 0, 0, 0.4)',
},
spacing: {
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
xxl: '48px',
},
borderRadius: {
sm: '6px',
md: '12px',
lg: '16px',
xl: '24px',
full: '9999px',
},
typography: {
fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Arial, sans-serif',
fontSize: {
xs: '12px',
sm: '14px',
base: '16px',
lg: '18px',
xl: '20px',
xxl: '24px',
},
fontWeight: {
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
},
},
animation: {
fast: '150ms',
normal: '250ms',
slow: '350ms',
},
}