From 3bd67318ba2ac206f894c999defa8e9f1cba68ab Mon Sep 17 00:00:00 2001 From: kerwenard Date: Wed, 27 May 2026 09:22:41 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=88=20perf:=20=E8=A1=A5=E5=85=A8run.ba?= =?UTF-8?q?t=EF=BC=8C=E5=9C=A8=E6=96=B0=E8=AE=BE=E5=A4=87=E4=B8=8A?= =?UTF-8?q?=E5=88=9D=E6=AC=A1=E4=B8=8B=E8=BD=BD=E8=87=AA=E5=8A=A8=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E7=8E=AF=E5=A2=83=EF=BC=8C=E6=96=B0=E5=A2=9Ebilibili?= =?UTF-8?q?=E7=9A=84cookies=E8=8E=B7=E5=8F=96=E8=84=9A=E6=9C=AC=EF=BC=8C?= =?UTF-8?q?=E8=A1=A5=E5=85=A8=20requirements=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BillNote_frontend/package-lock.json | 549 +++++++++++++++++++-- backend/requirements.txt | 1 + backend/scripts/bilibili_login.py | 723 ++++++++++++++++++++++++++++ login_bilibili.bat | 24 + run.bat | 90 +++- 5 files changed, 1341 insertions(+), 46 deletions(-) create mode 100644 backend/scripts/bilibili_login.py create mode 100644 login_bilibili.bat diff --git a/BillNote_frontend/package-lock.json b/BillNote_frontend/package-lock.json index 76b9320b..04af9997 100644 --- a/BillNote_frontend/package-lock.json +++ b/BillNote_frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "bili_note", "version": "0.0.0", "dependencies": { + "@ant-design/x": "^2.4.0", "@hookform/resolvers": "^5.0.1", "@lobehub/icons": "^1.97.1", "@lobehub/icons-static-svg": "^1.45.0", @@ -22,7 +23,8 @@ "@radix-ui/react-tabs": "^1.1.9", "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.1.3", - "@tauri-apps/plugin-shell": "~2.2.2", + "@tauri-apps/api": "^2.11.0", + "@tauri-apps/plugin-shell": "~2.3.5", "@uiw/react-markdown-preview": "^5.1.3", "antd": "^5.24.8", "axios": "^1.8.4", @@ -30,6 +32,7 @@ "clsx": "^2.1.1", "fuse.js": "^7.1.0", "github-markdown-css": "^5.8.1", + "idb-keyval": "^6.2.2", "jszip": "^3.10.1", "katex": "^0.16.22", "lottie-react": "^2.4.1", @@ -52,6 +55,7 @@ "react-router-dom": "^7.5.1", "react-syntax-highlighter": "^15.6.1", "rehype-katex": "^6.0.2", + "rehype-slug": "5.1.0", "remark-gfm": "3.0.1", "remark-math": "^5.1.1", "sonner": "^2.0.3", @@ -192,6 +196,182 @@ "react": ">=16.9.0" } }, + "node_modules/@ant-design/x": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@ant-design/x/-/x-2.7.0.tgz", + "integrity": "sha512-p5OtxQ9elbmeFRllGt1yj5wi6VHe41PIAmwrBU/OlaYydru5qIYsJzCS3DPRhkWkVdErU5oZwU74Z2oce2F5Uw==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.0", + "@ant-design/cssinjs": "^2.0.1", + "@ant-design/cssinjs-utils": "^2.0.2", + "@ant-design/fast-color": "^3.0.0", + "@ant-design/icons": "^6.0.0", + "@babel/runtime": "^7.25.6", + "@rc-component/motion": "^1.1.6", + "@rc-component/resize-observer": "^1.0.1", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "lodash.throttle": "^4.1.1", + "mermaid": "^11.12.1", + "react-syntax-highlighter": "^16.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "antd": "^6.1.1", + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@ant-design/x/node_modules/@ant-design/colors": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-8.0.1.tgz", + "integrity": "sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==", + "license": "MIT", + "dependencies": { + "@ant-design/fast-color": "^3.0.0" + } + }, + "node_modules/@ant-design/x/node_modules/@ant-design/cssinjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz", + "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "@rc-component/util": "^1.4.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/x/node_modules/@ant-design/cssinjs-utils": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-2.1.2.tgz", + "integrity": "sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^2.1.2", + "@babel/runtime": "^7.23.2", + "@rc-component/util": "^1.4.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@ant-design/x/node_modules/@ant-design/fast-color": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-3.0.1.tgz", + "integrity": "sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==", + "license": "MIT", + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/x/node_modules/@ant-design/icons": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-6.2.3.tgz", + "integrity": "sha512-Pl3aoAtxQeKryYnt6VvDJtOxMOtA8wrRSACe/pTjOAIG3fdHrWm6Ivb4ku9tsFjYroSXBKirvuxG4QkwBXD9gg==", + "license": "MIT", + "dependencies": { + "@ant-design/colors": "^8.0.1", + "@ant-design/icons-svg": "^4.4.2", + "@rc-component/util": "^1.10.1", + "clsx": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/x/node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@ant-design/x/node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@ant-design/x/node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/@ant-design/x/node_modules/react-syntax-highlighter": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.1.tgz", + "integrity": "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^5.0.0" + }, + "engines": { + "node": ">= 16.20.2" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/@ant-design/x/node_modules/refractor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-5.0.0.tgz", + "integrity": "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^9.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@antfu/install-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", @@ -232,8 +412,8 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -333,6 +513,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.28.6", @@ -386,6 +567,7 @@ "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", @@ -590,7 +772,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -826,6 +1007,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -842,6 +1024,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -858,6 +1041,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -874,6 +1058,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -890,6 +1075,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -906,6 +1092,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -922,6 +1109,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -938,6 +1126,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -954,6 +1143,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -970,6 +1160,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -986,6 +1177,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1002,6 +1194,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1018,6 +1211,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1034,6 +1228,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1050,6 +1245,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1066,6 +1262,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1082,6 +1279,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1098,6 +1296,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1114,6 +1313,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1130,6 +1330,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1146,6 +1347,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1162,6 +1364,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1178,6 +1381,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1194,6 +1398,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1210,6 +1415,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1226,6 +1432,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3292,6 +3499,20 @@ "node": ">=8.x" } }, + "node_modules/@rc-component/motion": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rc-component/motion/-/motion-1.3.2.tgz", + "integrity": "sha512-itfd+GztzJYAb04Z4RkEub1TbJAfZc2Iuy8p44U44xD1F5+fNYFKI3897ijlbIyfvXkTmMm+KGcjkQQGMHywEQ==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@rc-component/mutate-observer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", @@ -3344,6 +3565,19 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@rc-component/resize-observer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/resize-observer/-/resize-observer-1.1.2.tgz", + "integrity": "sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==", + "license": "MIT", + "dependencies": { + "@rc-component/util": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, "node_modules/@rc-component/tour": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", @@ -3385,6 +3619,26 @@ "react-dom": ">=16.9.0" } }, + "node_modules/@rc-component/util": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz", + "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==", + "license": "MIT", + "dependencies": { + "is-mobile": "^5.0.0", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + } + }, + "node_modules/@rc-component/util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -3399,6 +3653,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3412,6 +3667,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3425,6 +3681,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3438,6 +3695,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3451,6 +3709,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3464,6 +3723,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3477,6 +3737,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3490,6 +3751,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3503,6 +3765,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3516,6 +3779,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3529,6 +3793,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3542,6 +3807,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3555,6 +3821,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3568,6 +3835,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3581,6 +3849,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3594,6 +3863,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3607,6 +3877,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3620,6 +3891,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3633,6 +3905,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3646,6 +3919,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3659,6 +3933,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3672,6 +3947,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3685,6 +3961,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3698,6 +3975,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3711,6 +3989,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4144,9 +4423,9 @@ } }, "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", + "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", "license": "Apache-2.0 OR MIT", "funding": { "type": "opencollective", @@ -4371,12 +4650,12 @@ } }, "node_modules/@tauri-apps/plugin-shell": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.2.2.tgz", - "integrity": "sha512-fg9XKWfzRQsN8p+Zrk82WeHvXFvGVnG0/mTlujQdLWNnO5cM6WD9qCrHbFytScVS+WhmRAkuypQPcxeKKl3VBg==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.5.tgz", + "integrity": "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==", "license": "MIT OR Apache-2.0", "dependencies": { - "@tauri-apps/api": "^2.0.0" + "@tauri-apps/api": "^2.10.1" } }, "node_modules/@types/babel__core": { @@ -4760,9 +5039,8 @@ "version": "22.19.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.9.tgz", "integrity": "sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -4789,8 +5067,8 @@ "version": "19.2.13", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4799,9 +5077,8 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "devOptional": true, + "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4863,7 +5140,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -5373,6 +5649,23 @@ "react": ">=18" } }, + "node_modules/@uiw/react-markdown-preview/node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/@uiw/react-markdown-preview/node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -5450,7 +5743,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5527,7 +5819,6 @@ "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", "license": "MIT", - "peer": true, "dependencies": { "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^1.23.0", @@ -5833,7 +6124,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6154,6 +6444,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -6305,15 +6596,13 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cytoscape": { "version": "3.33.1", "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10" } @@ -6723,7 +7012,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -7035,8 +7323,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/emoji-regex": { "version": "10.6.0", @@ -7172,6 +7459,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -7236,7 +7524,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7563,6 +7850,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -7755,6 +8043,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7787,6 +8076,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -8747,6 +9037,12 @@ "node": ">=0.10.0" } }, + "node_modules/idb-keyval": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.4.tgz", + "integrity": "sha512-D/NzHWUmYJGXi++z67aMSrnisb9A3621CyRK5G89JyTlN13C8xf0g04DLxUKMufPem3e3L2JAXR6Z00OWy183Q==", + "license": "Apache-2.0" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -8950,6 +9246,12 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-mobile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz", + "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==", + "license": "MIT" + }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -9085,6 +9387,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -9566,6 +9869,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "license": "MIT" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -9762,7 +10071,6 @@ "resolved": "https://registry.npmjs.org/markmap-common/-/markmap-common-0.18.9.tgz", "integrity": "sha512-MV2HQO7IGIm3jWEJXSG8vmdpqf4WIDXcEyAEN52lrWR1qD53Zg5l81JwjXoZ2l0rY5mofKYqUFlmdM2fqTGMVg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.22.6", "@gera2ld/jsx-dom": "^2.2.2", @@ -14062,6 +14370,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -14393,8 +14702,8 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -14451,6 +14760,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -14466,7 +14776,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14499,7 +14808,6 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -15316,7 +15624,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -15336,7 +15643,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -15407,7 +15713,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.1.tgz", "integrity": "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -17015,16 +17320,173 @@ } }, "node_modules/rehype-slug": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", - "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-5.1.0.tgz", + "integrity": "sha512-Gf91dJoXneiorNEnn+Phx97CO7oRMrpi+6r155tTxzGuLtm+QrI4cTwCa9e1rtePdL4i9tSO58PeSS6HWfgsiw==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", + "@types/hast": "^2.0.0", "github-slugger": "^2.0.0", - "hast-util-heading-rank": "^3.0.0", - "hast-util-to-string": "^3.0.0", - "unist-util-visit": "^5.0.0" + "hast-util-has-property": "^2.0.0", + "hast-util-heading-rank": "^2.0.0", + "hast-util-to-string": "^2.0.0", + "unified": "^10.0.0", + "unist-util-visit": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/rehype-slug/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/rehype-slug/node_modules/hast-util-has-property": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-2.0.1.tgz", + "integrity": "sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/hast-util-heading-rank": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-2.1.1.tgz", + "integrity": "sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/hast-util-to-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz", + "integrity": "sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unified": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", + "integrity": "sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "bail": "^2.0.0", + "extend": "^3.0.0", + "is-buffer": "^2.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-is": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.2.1.tgz", + "integrity": "sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-stringify-position": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz", + "integrity": "sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-visit": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.2.tgz", + "integrity": "sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0", + "unist-util-visit-parents": "^5.1.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-visit-parents": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz", + "integrity": "sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-is": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/vfile": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", + "integrity": "sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "is-buffer": "^2.0.0", + "unist-util-stringify-position": "^3.0.0", + "vfile-message": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/vfile-message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", + "integrity": "sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^3.0.0" }, "funding": { "type": "opencollective", @@ -17354,6 +17816,7 @@ "version": "4.57.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -17804,6 +18267,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -17907,7 +18371,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17965,7 +18428,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -18432,8 +18895,8 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/backend/requirements.txt b/backend/requirements.txt index b0d23268..96789562 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -105,6 +105,7 @@ soupsieve==2.7 starlette==0.46.1 sympy==1.13.1 SQLAlchemy==2.0.41 +setuptools>=65.0.0,<81.0.0 tenacity==9.1.2 tinycss2==1.4.0 tinyhtml5==2.0.0 diff --git a/backend/scripts/bilibili_login.py b/backend/scripts/bilibili_login.py new file mode 100644 index 00000000..1fd17cf1 --- /dev/null +++ b/backend/scripts/bilibili_login.py @@ -0,0 +1,723 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import argparse +import html +import json +import sys +import time +import urllib.error +import urllib.parse +import urllib.request +import webbrowser +from http.cookiejar import CookieJar +from pathlib import Path +from typing import Any + + +GENERATE_API = ( + "https://passport.bilibili.com/x/passport-login/web/qrcode/generate" + "?source=main-fe-header" +) +POLL_API = "https://passport.bilibili.com/x/passport-login/web/qrcode/poll" +NAV_API = "https://api.bilibili.com/x/web-interface/nav" +USER_AGENT = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/125.0 Safari/537.36" +) + +COOKIE_PRIORITY = [ + "SESSDATA", + "bili_jct", + "DedeUserID", + "DedeUserID__ckMd5", + "sid", + "buvid3", + "buvid4", + "b_nut", + "CURRENT_FNVAL", +] + + +class BilibiliLoginError(RuntimeError): + pass + + +def _request_json(opener: urllib.request.OpenerDirector, url: str) -> dict[str, Any]: + request = urllib.request.Request( + url, + headers={ + "User-Agent": USER_AGENT, + "Accept": "application/json, text/plain, */*", + "Referer": "https://www.bilibili.com/", + }, + ) + + try: + with opener.open(request, timeout=20) as response: + body = response.read().decode("utf-8", errors="replace") + except urllib.error.HTTPError as exc: + detail = exc.read().decode("utf-8", errors="replace")[:500] + raise BilibiliLoginError(f"HTTP {exc.code}: {detail}") from exc + except urllib.error.URLError as exc: + raise BilibiliLoginError(f"Network error: {exc}") from exc + + try: + payload = json.loads(body) + except json.JSONDecodeError as exc: + raise BilibiliLoginError(f"Invalid JSON response: {body[:500]}") from exc + + if payload.get("code") != 0: + raise BilibiliLoginError( + f"Bilibili API error: code={payload.get('code')}, " + f"message={payload.get('message')}" + ) + return payload + + +def _create_opener(cookie_jar: CookieJar) -> urllib.request.OpenerDirector: + return urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie_jar)) + + +def generate_login_qr( + opener: urllib.request.OpenerDirector, +) -> tuple[str, str]: + payload = _request_json(opener, GENERATE_API) + data = payload.get("data") or {} + login_url = data.get("url") + qrcode_key = data.get("qrcode_key") + + if not qrcode_key and login_url: + parsed = urllib.parse.urlparse(login_url) + qrcode_key = urllib.parse.parse_qs(parsed.query).get("qrcode_key", [""])[0] + + if not login_url or not qrcode_key: + raise BilibiliLoginError(f"Unexpected QR payload: {payload}") + return login_url, qrcode_key + + +def poll_login( + opener: urllib.request.OpenerDirector, + qrcode_key: str, + timeout_seconds: int, +) -> dict[str, Any]: + poll_url = f"{POLL_API}?{urllib.parse.urlencode({'qrcode_key': qrcode_key})}" + started_at = time.time() + last_code: int | None = None + + while True: + if time.time() - started_at > timeout_seconds: + raise BilibiliLoginError("Login timed out. Please run this script again.") + + payload = _request_json(opener, poll_url) + data = payload.get("data") or {} + code = data.get("code") + message = data.get("message") or "" + + if code == 0: + print("[OK] Login confirmed.") + return data + + if code != last_code: + if code == 86101: + print("[WAIT] Waiting for scan in the Bilibili mobile app...") + elif code == 86090: + print("[WAIT] Scanned. Please confirm login in the app...") + elif code == 86038: + raise BilibiliLoginError("QR code expired. Please run this script again.") + else: + print(f"[WAIT] Bilibili status code={code}, message={message}") + last_code = code + + time.sleep(2) + + +def cookie_header_from_jar(cookie_jar: CookieJar) -> str: + cookies = list(cookie_jar) + selected: list[tuple[str, str]] = [] + selected_names: set[str] = set() + + def is_bilibili_cookie(cookie: Any) -> bool: + return "bilibili.com" in (cookie.domain or "") + + for name in COOKIE_PRIORITY: + for cookie in cookies: + if cookie.name == name and is_bilibili_cookie(cookie): + selected.append((cookie.name, cookie.value)) + selected_names.add(cookie.name) + break + + for cookie in cookies: + if cookie.name not in selected_names and is_bilibili_cookie(cookie): + selected.append((cookie.name, cookie.value)) + selected_names.add(cookie.name) + + return "; ".join(f"{name}={value}" for name, value in selected) + + +def verify_cookie(cookie: str) -> tuple[bool, str | None]: + cookie_jar = CookieJar() + opener = _create_opener(cookie_jar) + request = urllib.request.Request( + NAV_API, + headers={ + "User-Agent": USER_AGENT, + "Accept": "application/json, text/plain, */*", + "Referer": "https://www.bilibili.com/", + "Cookie": cookie, + }, + ) + + try: + with opener.open(request, timeout=20) as response: + payload = json.loads(response.read().decode("utf-8", errors="replace")) + except Exception: + return False, None + + data = payload.get("data") or {} + return bool(data.get("isLogin")), data.get("uname") + + +def save_downloader_cookie(config_path: Path, cookie: str) -> None: + config_path.parent.mkdir(parents=True, exist_ok=True) + if config_path.exists(): + try: + data = json.loads(config_path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + data = {} + except Exception: + data = {} + else: + data = {} + + data["bilibili"] = {"cookie": cookie} + config_path.write_text( + json.dumps(data, ensure_ascii=False, indent=2) + "\n", + encoding="utf-8", + ) + + +def default_config_path() -> Path: + backend_dir = Path(__file__).resolve().parents[1] + return backend_dir / "config" / "downloader.json" + + +def write_qr_page(login_url: str, output_path: Path) -> None: + svg = make_qr_svg(login_url) + escaped_url = html.escape(login_url) + page = f""" + + + + + Bilibili Login QR + + + +
+

Bilibili QR Login

+

Scan this QR code with the Bilibili mobile app, then confirm login.

+
{svg}
+

The terminal will continue automatically after confirmation.

+ +
+ + +""" + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(page, encoding="utf-8") + + +ALIGNMENT_POSITIONS = { + 1: [], + 2: [6, 18], + 3: [6, 22], + 4: [6, 26], + 5: [6, 30], + 6: [6, 34], + 7: [6, 22, 38], + 8: [6, 24, 42], + 9: [6, 26, 46], + 10: [6, 28, 50], +} + +# Level L block data for versions 1 through 10. This comfortably covers +# Bilibili's current QR login URL without requiring any third-party package. +LEVEL_L_BLOCKS = { + 1: (7, [19]), + 2: (10, [34]), + 3: (15, [55]), + 4: (20, [80]), + 5: (26, [108]), + 6: (18, [68, 68]), + 7: (20, [78, 78]), + 8: (24, [97, 97]), + 9: (30, [116, 116]), + 10: (18, [68, 68, 69, 69]), +} + + +def make_qr_svg(text: str, border: int = 4) -> str: + data = text.encode("utf-8") + version = _choose_version(len(data)) + ecc_len, block_lengths = LEVEL_L_BLOCKS[version] + data_codewords = sum(block_lengths) + codewords = _make_data_codewords(data, version, data_codewords) + all_codewords = _add_error_correction(codewords, ecc_len, block_lengths) + modules = _make_qr_matrix(version, all_codewords, mask=0) + + size = len(modules) + dimension = size + border * 2 + path_parts: list[str] = [] + for y, row in enumerate(modules): + for x, dark in enumerate(row): + if dark: + path_parts.append(f"M{x + border},{y + border}h1v1h-1z") + + path = "".join(path_parts) + return ( + f'' + f'' + f'' + f"" + ) + + +def _choose_version(byte_count: int) -> int: + for version in sorted(LEVEL_L_BLOCKS): + _, block_lengths = LEVEL_L_BLOCKS[version] + count_bits = 8 if version <= 9 else 16 + capacity_bits = sum(block_lengths) * 8 + required_bits = 4 + count_bits + byte_count * 8 + if required_bits <= capacity_bits: + return version + raise BilibiliLoginError( + "QR login URL is too long for the bundled QR encoder. " + "Please install a QR package or update this script." + ) + + +def _append_bits(bits: list[int], value: int, length: int) -> None: + for i in range(length - 1, -1, -1): + bits.append((value >> i) & 1) + + +def _make_data_codewords(data: bytes, version: int, data_codewords: int) -> list[int]: + bits: list[int] = [] + _append_bits(bits, 0x4, 4) + _append_bits(bits, len(data), 8 if version <= 9 else 16) + for byte in data: + _append_bits(bits, byte, 8) + + capacity_bits = data_codewords * 8 + _append_bits(bits, 0, min(4, capacity_bits - len(bits))) + while len(bits) % 8 != 0: + bits.append(0) + + codewords = [ + sum(bits[i + bit] << (7 - bit) for bit in range(8)) + for i in range(0, len(bits), 8) + ] + pad = 0xEC + while len(codewords) < data_codewords: + codewords.append(pad) + pad ^= 0xEC ^ 0x11 + return codewords + + +def _add_error_correction( + data_codewords: list[int], + ecc_len: int, + block_lengths: list[int], +) -> list[int]: + divisor = _reed_solomon_divisor(ecc_len) + blocks: list[list[int]] = [] + offset = 0 + for length in block_lengths: + block = data_codewords[offset : offset + length] + offset += length + blocks.append(block + _reed_solomon_remainder(block, divisor)) + + result: list[int] = [] + max_data_len = max(block_lengths) + for index in range(max_data_len): + for block, data_len in zip(blocks, block_lengths): + if index < data_len: + result.append(block[index]) + + for index in range(ecc_len): + for block, data_len in zip(blocks, block_lengths): + result.append(block[data_len + index]) + + return result + + +GF_EXP = [0] * 512 +GF_LOG = [0] * 256 +_x = 1 +for _i in range(255): + GF_EXP[_i] = _x + GF_LOG[_x] = _i + _x <<= 1 + if _x & 0x100: + _x ^= 0x11D +for _i in range(255, 512): + GF_EXP[_i] = GF_EXP[_i - 255] + + +def _gf_multiply(x: int, y: int) -> int: + if x == 0 or y == 0: + return 0 + return GF_EXP[GF_LOG[x] + GF_LOG[y]] + + +def _reed_solomon_divisor(degree: int) -> list[int]: + result = [0] * (degree - 1) + [1] + root = 1 + for _ in range(degree): + for i in range(degree): + result[i] = _gf_multiply(result[i], root) + if i + 1 < degree: + result[i] ^= result[i + 1] + root = _gf_multiply(root, 0x02) + return result + + +def _reed_solomon_remainder(data: list[int], divisor: list[int]) -> list[int]: + result = [0] * len(divisor) + for byte in data: + factor = byte ^ result.pop(0) + result.append(0) + for i, coefficient in enumerate(divisor): + result[i] ^= _gf_multiply(coefficient, factor) + return result + + +def _make_qr_matrix(version: int, codewords: list[int], mask: int) -> list[list[bool]]: + size = version * 4 + 17 + modules = [[False] * size for _ in range(size)] + reserved = [[False] * size for _ in range(size)] + + def set_module(x: int, y: int, dark: bool, reserve: bool = True) -> None: + if 0 <= x < size and 0 <= y < size: + modules[y][x] = dark + if reserve: + reserved[y][x] = True + + def reserve_module(x: int, y: int) -> None: + if 0 <= x < size and 0 <= y < size: + reserved[y][x] = True + + def draw_finder(left: int, top: int) -> None: + for dy in range(-1, 8): + for dx in range(-1, 8): + x = left + dx + y = top + dy + dark = ( + 0 <= dx <= 6 + and 0 <= dy <= 6 + and ( + dx in (0, 6) + or dy in (0, 6) + or (2 <= dx <= 4 and 2 <= dy <= 4) + ) + ) + set_module(x, y, dark) + + def draw_alignment(cx: int, cy: int) -> None: + for dy in range(-2, 3): + for dx in range(-2, 3): + distance = max(abs(dx), abs(dy)) + set_module(cx + dx, cy + dy, distance in (0, 2)) + + draw_finder(0, 0) + draw_finder(size - 7, 0) + draw_finder(0, size - 7) + + for i in range(8, size - 8): + set_module(i, 6, i % 2 == 0) + set_module(6, i, i % 2 == 0) + + for cy in ALIGNMENT_POSITIONS[version]: + for cx in ALIGNMENT_POSITIONS[version]: + if ( + (cx == 6 and cy == 6) + or (cx == size - 7 and cy == 6) + or (cx == 6 and cy == size - 7) + ): + continue + draw_alignment(cx, cy) + + _reserve_format_bits(size, reserve_module) + if version >= 7: + _reserve_version_bits(size, reserve_module) + + data_bits = [ + (codeword >> bit) & 1 + for codeword in codewords + for bit in range(7, -1, -1) + ] + + bit_index = 0 + upward = True + x = size - 1 + while x > 0: + if x == 6: + x -= 1 + y_range = range(size - 1, -1, -1) if upward else range(size) + for y in y_range: + for column in (x, x - 1): + if not reserved[y][column]: + bit = data_bits[bit_index] if bit_index < len(data_bits) else 0 + if _mask_bit(mask, column, y): + bit ^= 1 + set_module(column, y, bool(bit), reserve=False) + bit_index += 1 + upward = not upward + x -= 2 + + _draw_format_bits(version, mask, set_module) + if version >= 7: + _draw_version_bits(version, set_module) + return modules + + +def _mask_bit(mask: int, x: int, y: int) -> bool: + if mask == 0: + return (x + y) % 2 == 0 + raise ValueError(f"Unsupported mask: {mask}") + + +def _reserve_format_bits(size: int, reserve_module: Any) -> None: + for i in range(6): + reserve_module(8, i) + reserve_module(i, 8) + reserve_module(8, 7) + reserve_module(8, 8) + reserve_module(7, 8) + for i in range(9, 15): + reserve_module(8, size - 15 + i) + reserve_module(14 - i, 8) + for i in range(8): + reserve_module(size - 1 - i, 8) + reserve_module(8, size - 1 - i) + reserve_module(8, size - 8) + + +def _draw_format_bits(version: int, mask: int, set_module: Any) -> None: + size = version * 4 + 17 + bits = _format_bits(mask) + + for i in range(6): + set_module(8, i, _get_bit(bits, i)) + set_module(i, 8, _get_bit(bits, i)) + set_module(8, 7, _get_bit(bits, 6)) + set_module(8, 8, _get_bit(bits, 7)) + set_module(7, 8, _get_bit(bits, 8)) + for i in range(9, 15): + set_module(14 - i, 8, _get_bit(bits, i)) + + for i in range(8): + set_module(size - 1 - i, 8, _get_bit(bits, i)) + for i in range(8, 15): + set_module(8, size - 15 + i, _get_bit(bits, i)) + + set_module(8, size - 8, True) + + +def _format_bits(mask: int) -> int: + data = (1 << 3) | mask # Error correction level L is encoded as 01. + rem = data + for _ in range(10): + rem = (rem << 1) ^ ((rem >> 9) * 0x537) + return ((data << 10) | rem) ^ 0x5412 + + +def _reserve_version_bits(size: int, reserve_module: Any) -> None: + for i in range(18): + a = size - 11 + i % 3 + b = i // 3 + reserve_module(a, b) + reserve_module(b, a) + + +def _draw_version_bits(version: int, set_module: Any) -> None: + size = version * 4 + 17 + bits = _version_bits(version) + for i in range(18): + bit = _get_bit(bits, i) + a = size - 11 + i % 3 + b = i // 3 + set_module(a, b, bit) + set_module(b, a, bit) + + +def _version_bits(version: int) -> int: + rem = version + for _ in range(12): + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25) + return (version << 12) | rem + + +def _get_bit(value: int, index: int) -> bool: + return ((value >> index) & 1) != 0 + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description=( + "Login to Bilibili with a QR code and write the cookie to " + "backend/config/downloader.json." + ) + ) + parser.add_argument( + "--config", + type=Path, + default=default_config_path(), + help="Path to downloader.json. Defaults to backend/config/downloader.json.", + ) + parser.add_argument( + "--timeout", + type=int, + default=180, + help="Seconds to wait for QR scan confirmation.", + ) + parser.add_argument( + "--no-open", + action="store_true", + help="Do not open the QR page in the default browser.", + ) + parser.add_argument( + "--keep-qr-page", + action="store_true", + help="Keep the temporary QR HTML page after the script exits.", + ) + return parser.parse_args() + + +def main() -> int: + args = parse_args() + config_path = args.config.resolve() + qr_page = config_path.parent / "bilibili_login_qr.html" + + cookie_jar = CookieJar() + opener = _create_opener(cookie_jar) + + print("[INFO] Requesting Bilibili QR login ticket...") + try: + login_url, qrcode_key = generate_login_qr(opener) + write_qr_page(login_url, qr_page) + print(f"[INFO] QR page written to: {qr_page}") + if not args.no_open: + webbrowser.open(qr_page.resolve().as_uri()) + print("[INFO] Opened the QR page in your default browser.") + else: + print("[INFO] Open this file and scan the QR code:") + print(f" {qr_page}") + + poll_login(opener, qrcode_key, args.timeout) + cookie = cookie_header_from_jar(cookie_jar) + if "SESSDATA=" not in cookie: + raise BilibiliLoginError( + "Login succeeded, but SESSDATA was not found in returned cookies." + ) + + is_login, uname = verify_cookie(cookie) + if not is_login: + raise BilibiliLoginError("Cookie verification failed.") + + save_downloader_cookie(config_path, cookie) + display_name = f" ({uname})" if uname else "" + print(f"[OK] Bilibili cookie verified{display_name}.") + print(f"[OK] Wrote downloader config: {config_path}") + print("[INFO] Restart the backend if it is already running.") + return 0 + except BilibiliLoginError as exc: + print(f"[ERROR] {exc}", file=sys.stderr) + return 1 + finally: + if not args.keep_qr_page: + try: + qr_page.unlink() + except FileNotFoundError: + pass + except OSError: + pass + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/login_bilibili.bat b/login_bilibili.bat new file mode 100644 index 00000000..c0705468 --- /dev/null +++ b/login_bilibili.bat @@ -0,0 +1,24 @@ +@echo off +setlocal +cd /d "%~dp0" + +set "SCRIPT=%~dp0backend\scripts\bilibili_login.py" + +where python >nul 2>nul +if not errorlevel 1 ( + python "%SCRIPT%" %* + goto done +) + +where py >nul 2>nul +if not errorlevel 1 ( + py -3 "%SCRIPT%" %* + goto done +) + +echo [ERROR] Python was not found. Please install Python 3 or run this from your BiliNote Python environment. +exit /b 1 + +:done +echo. +pause diff --git a/run.bat b/run.bat index 77083a85..9c47c263 100644 --- a/run.bat +++ b/run.bat @@ -1,7 +1,91 @@ @echo off +setlocal cd /d "%~dp0" -start "Backend" powershell -NoExit -Command "cd backend; conda activate bili; python main.py" -start "Frontend" powershell -NoExit -Command "cd BillNote_frontend; npm run dev" +set "CONDA_ENV=bili" +set "PYTHON_VERSION=3.11" +set "CONDA_CHANNEL=conda-forge" +set "BACKEND_DIR=%~dp0backend" +set "FRONTEND_DIR=%~dp0BillNote_frontend" -start http://localhost:3015/ \ No newline at end of file +where conda >nul 2>nul +if errorlevel 1 ( + echo [ERROR] conda was not found. Please install Anaconda or Miniconda first. + pause + exit /b 1 +) + +for /f "delims=" %%I in ('conda info --base') do set "CONDA_BASE=%%I" +set "CONDA_BAT=%CONDA_BASE%\condabin\conda.bat" +if not exist "%CONDA_BAT%" set "CONDA_BAT=conda" + +set "INSTALL_BACKEND_DEPS=0" +call "%CONDA_BAT%" env list | findstr /R /C:"^%CONDA_ENV%[ ]" >nul +if errorlevel 1 ( + echo [INFO] Conda environment "%CONDA_ENV%" not found. Creating it... + echo [INFO] Using "%CONDA_CHANNEL%" with --override-channels to avoid non-interactive Anaconda ToS prompts. + call "%CONDA_BAT%" create -y -n "%CONDA_ENV%" --override-channels --channel "%CONDA_CHANNEL%" python=%PYTHON_VERSION% + if errorlevel 1 ( + echo [ERROR] Failed to create conda environment "%CONDA_ENV%". + pause + exit /b 1 + ) + set "INSTALL_BACKEND_DEPS=1" +) else ( + echo [INFO] Conda environment "%CONDA_ENV%" already exists. +) + +call "%CONDA_BAT%" run -n "%CONDA_ENV%" python -c "import pkg_resources, fastapi, faster_whisper" >nul 2>nul +if errorlevel 1 set "INSTALL_BACKEND_DEPS=1" + +if "%INSTALL_BACKEND_DEPS%"=="1" ( + echo [INFO] Installing backend dependencies for "%CONDA_ENV%"... + call "%CONDA_BAT%" run -n "%CONDA_ENV%" python -m pip install --upgrade pip "setuptools<81" wheel + if errorlevel 1 ( + echo [ERROR] Failed to install Python bootstrap dependencies in "%CONDA_ENV%". + pause + exit /b 1 + ) + + call "%CONDA_BAT%" run -n "%CONDA_ENV%" python -m pip install -r "%BACKEND_DIR%\requirements.txt" + if errorlevel 1 ( + echo [ERROR] Failed to install backend dependencies. + pause + exit /b 1 + ) + call "%CONDA_BAT%" run -n "%CONDA_ENV%" python -c "import pkg_resources" >nul 2>nul + if errorlevel 1 ( + echo [ERROR] Python package pkg_resources is still missing after dependency installation. + pause + exit /b 1 + ) +) else ( + echo [INFO] Backend dependencies already installed. +) + +where npm >nul 2>nul +if errorlevel 1 ( + echo [ERROR] npm was not found. Please install Node.js first. + pause + exit /b 1 +) + +if not exist "%FRONTEND_DIR%\node_modules\.bin\vite.cmd" ( + echo [INFO] Frontend dependencies not found. Running npm install... + pushd "%FRONTEND_DIR%" + call npm install --legacy-peer-deps + if errorlevel 1 ( + popd + echo [ERROR] Failed to install frontend dependencies. + pause + exit /b 1 + ) + popd +) else ( + echo [INFO] Frontend dependencies already installed. +) + +start "Backend" cmd /k call "%CONDA_BAT%" activate "%CONDA_ENV%" ^&^& cd /d "%BACKEND_DIR%" ^&^& python main.py +start "Frontend" powershell -NoExit -Command "Set-Location -LiteralPath '%FRONTEND_DIR%'; npm run dev" + +start http://localhost:3015/