diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e19f643..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2779 +0,0 @@ -{ - "name": "omniai-web-preview", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "omniai-web-preview", - "version": "0.1.0", - "dependencies": { - "@ant-design/icons": "5.3.0", - "@xyflow/react": "12.10.2", - "iconv-lite": "^0.7.2", - "react": "18.2.0", - "react-dom": "18.2.0", - "zustand": "5.0.13" - }, - "devDependencies": { - "@types/react": "18.2.0", - "@types/react-dom": "18.2.0", - "@vitejs/plugin-react": "4.2.1", - "playwright": "1.60.0", - "sharp": "0.34.5", - "typescript": "5.3.3", - "vite": "5.1.0", - "vite-plugin-compression2": "2.5.3" - } - }, - "node_modules/@ant-design/colors": { - "version": "7.2.1", - "resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-7.2.1.tgz", - "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", - "license": "MIT", - "dependencies": { - "@ant-design/fast-color": "^2.0.6" - } - }, - "node_modules/@ant-design/fast-color": { - "version": "2.0.6", - "resolved": "https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-2.0.6.tgz", - "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.24.7" - }, - "engines": { - "node": ">=8.x" - } - }, - "node_modules/@ant-design/icons": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.0.tgz", - "integrity": "sha512-69FgBsIkeCjw72ZU3fJpqjhmLCPrzKGEllbrAZK7MUdt1BrKsyG6A8YDCBPKea27UQ0tRXi33PcjR4tp/tEXMg==", - "license": "MIT", - "dependencies": { - "@ant-design/colors": "^7.0.0", - "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.11.2", - "classnames": "^2.2.6", - "rc-util": "^5.31.1" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": ">=16.0.0", - "react-dom": ">=16.0.0" - } - }, - "node_modules/@ant-design/icons-svg": { - "version": "4.4.2", - "resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", - "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@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", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.3.tgz", - "integrity": "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.3.tgz", - "integrity": "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.3.tgz", - "integrity": "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.3.tgz", - "integrity": "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.3.tgz", - "integrity": "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.3.tgz", - "integrity": "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.3.tgz", - "integrity": "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.3.tgz", - "integrity": "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.3.tgz", - "integrity": "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.3.tgz", - "integrity": "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.3.tgz", - "integrity": "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.3.tgz", - "integrity": "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.3.tgz", - "integrity": "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.3.tgz", - "integrity": "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.3.tgz", - "integrity": "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.3.tgz", - "integrity": "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.3.tgz", - "integrity": "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.3.tgz", - "integrity": "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.3.tgz", - "integrity": "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.3.tgz", - "integrity": "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.3.tgz", - "integrity": "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.3.tgz", - "integrity": "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.3.tgz", - "integrity": "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.3.tgz", - "integrity": "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.3.tgz", - "integrity": "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmmirror.com/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmmirror.com/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmmirror.com/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmmirror.com/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.0.tgz", - "integrity": "sha512-0FLj93y5USLHdnhIhABk83rm8XEGA7kH3cr+YUlvxoUGp1xNt/DINUMvqPxLyOQMzLmZe8i4RTHbvb8MC7NmrA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-8yQrvS6sMpSwIovhPOwfyNf2Wz6v/B62LFSVYQ85+Rq3tLsBIG7rP5geMxaijTUxSkrO6RzN/IRuIAADYQsleA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" - } - }, - "node_modules/@xyflow/react": { - "version": "12.10.2", - "resolved": "https://registry.npmmirror.com/@xyflow/react/-/react-12.10.2.tgz", - "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", - "license": "MIT", - "dependencies": { - "@xyflow/system": "0.0.76", - "classcat": "^5.0.3", - "zustand": "^4.4.0" - }, - "peerDependencies": { - "react": ">=17", - "react-dom": ">=17" - } - }, - "node_modules/@xyflow/react/node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/@xyflow/system": { - "version": "0.0.76", - "resolved": "https://registry.npmmirror.com/@xyflow/system/-/system-0.0.76.tgz", - "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", - "license": "MIT", - "dependencies": { - "@types/d3-drag": "^3.0.7", - "@types/d3-interpolate": "^3.0.4", - "@types/d3-selection": "^3.0.10", - "@types/d3-transition": "^3.0.8", - "@types/d3-zoom": "^3.0.8", - "d3-drag": "^3.0.0", - "d3-interpolate": "^3.0.1", - "d3-selection": "^3.0.0", - "d3-zoom": "^3.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.29", - "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", - "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001792", - "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", - "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/classcat": { - "version": "5.0.5", - "resolved": "https://registry.npmmirror.com/classcat/-/classcat-5.0.5.tgz", - "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", - "license": "MIT" - }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmmirror.com/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmmirror.com/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.353", - "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.353.tgz", - "integrity": "sha512-kOrWphBi8TOZyiJZqsgqIle0lw+tzmnQK83pV9dZUd01Nm2POECSyFQMAuarzZdYqQW7FH9RaYOuaRo3h+bQ3w==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/playwright": { - "version": "1.60.0", - "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.60.0.tgz", - "integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.60.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.60.0", - "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.60.0.tgz", - "integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/rc-util": { - "version": "5.44.4", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", - "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "react-is": "^18.2.0" - }, - "peerDependencies": { - "react": ">=16.9.0", - "react-dom": ">=16.9.0" - } - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "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/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.60.3", - "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.60.3.tgz", - "integrity": "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.3", - "@rollup/rollup-android-arm64": "4.60.3", - "@rollup/rollup-darwin-arm64": "4.60.3", - "@rollup/rollup-darwin-x64": "4.60.3", - "@rollup/rollup-freebsd-arm64": "4.60.3", - "@rollup/rollup-freebsd-x64": "4.60.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", - "@rollup/rollup-linux-arm-musleabihf": "4.60.3", - "@rollup/rollup-linux-arm64-gnu": "4.60.3", - "@rollup/rollup-linux-arm64-musl": "4.60.3", - "@rollup/rollup-linux-loong64-gnu": "4.60.3", - "@rollup/rollup-linux-loong64-musl": "4.60.3", - "@rollup/rollup-linux-ppc64-gnu": "4.60.3", - "@rollup/rollup-linux-ppc64-musl": "4.60.3", - "@rollup/rollup-linux-riscv64-gnu": "4.60.3", - "@rollup/rollup-linux-riscv64-musl": "4.60.3", - "@rollup/rollup-linux-s390x-gnu": "4.60.3", - "@rollup/rollup-linux-x64-gnu": "4.60.3", - "@rollup/rollup-linux-x64-musl": "4.60.3", - "@rollup/rollup-openbsd-x64": "4.60.3", - "@rollup/rollup-openharmony-arm64": "4.60.3", - "@rollup/rollup-win32-arm64-msvc": "4.60.3", - "@rollup/rollup-win32-ia32-msvc": "4.60.3", - "@rollup/rollup-win32-x64-gnu": "4.60.3", - "@rollup/rollup-win32-x64-msvc": "4.60.3", - "fsevents": "~2.3.2" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, - "node_modules/sharp/node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tar-mini": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/tar-mini/-/tar-mini-0.2.0.tgz", - "integrity": "sha512-+qfUHz700DWnRutdUsxRRVZ38G1Qr27OetwaMYTdg8hcPxf46U0S1Zf76dQMWRBmusOt2ZCK5kbIaiLkoGO7WQ==", - "dev": true, - "license": "MIT" - }, - "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==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/vite": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.0.tgz", - "integrity": "sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-compression2": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/vite-plugin-compression2/-/vite-plugin-compression2-2.5.3.tgz", - "integrity": "sha512-ItPgqQWkcnBbVw7is9OKwiZ8v6+ju9rYROl5Lp6QfQDEx/d55AwJQb/KLpsQqsU9HoigYBsZ8tK6I02UwJNvEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.1.0", - "tar-mini": "^0.2.0" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/zustand": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.13.tgz", - "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/package.json b/package.json index 5ba6785..3b24b5a 100644 --- a/package.json +++ b/package.json @@ -7,15 +7,10 @@ "dev": "vite --host 127.0.0.1", "build": "vite build", "preview": "vite preview --host 127.0.0.1", - "type-check": "tsc -p tsconfig.json --noEmit", - "governance:check": "node scripts/check-governance.mjs", - "style:check": "node scripts/check-style-governance.mjs", - "smoke:generation:mocked": "node scripts/smoke-generation-mocked.mjs" + "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { "@ant-design/icons": "5.3.0", - "@xyflow/react": "12.10.2", - "iconv-lite": "^0.7.2", "react": "18.2.0", "react-dom": "18.2.0", "zustand": "5.0.13" @@ -24,8 +19,6 @@ "@types/react": "18.2.0", "@types/react-dom": "18.2.0", "@vitejs/plugin-react": "4.2.1", - "playwright": "1.60.0", - "sharp": "0.34.5", "typescript": "5.3.3", "vite": "5.1.0", "vite-plugin-compression2": "2.5.3" diff --git a/scripts/check-governance.mjs b/scripts/check-governance.mjs deleted file mode 100644 index efd2246..0000000 --- a/scripts/check-governance.mjs +++ /dev/null @@ -1,80 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import process from "node:process"; - -const repoRoot = process.cwd(); -const mediaExtensions = new Set([".png", ".jpg", ".jpeg", ".webp", ".gif", ".mp4", ".mov", ".webm", ".avif"]); -const textExtensions = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".html", ".css", ".md", ".env", ".example"]); - -const scanRoots = ["src", "vite.config.ts", "index.html", "package.json", ".env.example"]; -const allowedFiles = new Set([ - normalizePath("src/data/ossAssets.ts"), - normalizePath("src/utils/ossImageOptimize.ts"), -]); - -const forbiddenPatterns = [ - { label: "frontend env config", pattern: /\b(?:import\.meta\.env|VITE_[A-Z0-9_]+)\b/ }, - { label: "direct provider proxy", pattern: /\/dashscope-api\b|dashscope\.aliyuncs\.com/i }, - { label: "third-party demo media host", pattern: /picsum\.photos|xiuxiu-pro(?:-new)?\.meitudata\.com|meitudata\.com/i }, - { label: "hard-coded provider secret marker", pattern: /Bearer\s+sk-|DASHSCOPE_API_KEY|ACCESS_KEY_SECRET|SECRET_ACCESS_KEY/i }, - { label: "local media import", pattern: /from\s+["'][^"']*\/assets\/[^"']*\.(?:png|jpe?g|webp|gif|mp4|mov|webm|avif|svg)["']/i }, -]; - -const failures = []; - -function normalizePath(value) { - return value.replace(/\\/g, "/"); -} - -function walk(targetPath, visitor) { - if (!fs.existsSync(targetPath)) return; - const stat = fs.statSync(targetPath); - if (stat.isDirectory()) { - for (const entry of fs.readdirSync(targetPath)) { - if (entry === "node_modules" || entry === "dist" || entry === ".git") continue; - walk(path.join(targetPath, entry), visitor); - } - return; - } - visitor(targetPath, stat); -} - -function report(file, message) { - failures.push(`${normalizePath(path.relative(repoRoot, file))}: ${message}`); -} - -walk(path.join(repoRoot, "src", "assets"), (file) => { - if (mediaExtensions.has(path.extname(file).toLowerCase())) { - report(file, "media files must live in OSS, not src/assets"); - } -}); - -for (const root of scanRoots) { - walk(path.join(repoRoot, root), (file) => { - const relative = normalizePath(path.relative(repoRoot, file)); - const ext = path.extname(file).toLowerCase(); - if (!textExtensions.has(ext) && !relative.endsWith(".env.example")) return; - if (relative.startsWith("src/assets/")) return; - - const content = fs.readFileSync(file, "utf8"); - const isAllowed = allowedFiles.has(relative); - for (const rule of forbiddenPatterns) { - if (isAllowed && (rule.label === "third-party demo media host" || rule.label === "hard-coded provider secret marker")) { - continue; - } - if (rule.pattern.test(content)) { - report(file, `forbidden ${rule.label}`); - } - } - }); -} - -if (failures.length) { - console.error("Governance check failed:"); - for (const failure of failures) { - console.error(`- ${failure}`); - } - process.exit(1); -} - -console.log("Governance check passed."); diff --git a/scripts/check-style-governance.mjs b/scripts/check-style-governance.mjs deleted file mode 100644 index 3f5d301..0000000 --- a/scripts/check-style-governance.mjs +++ /dev/null @@ -1 +0,0 @@ -import "./check-governance.mjs"; diff --git a/scripts/dynamic-analysis-v2.mjs b/scripts/dynamic-analysis-v2.mjs deleted file mode 100644 index 5d43054..0000000 --- a/scripts/dynamic-analysis-v2.mjs +++ /dev/null @@ -1,301 +0,0 @@ -/** - * Dynamic analysis without Playwright - uses Node.js to analyze module structure, - * dependency graph, import patterns, and potential runtime costs. - */ -import { readFileSync, readdirSync, statSync } from 'fs'; -import { join, relative, basename } from 'path'; - -const SRC = join(import.meta.dirname, '..', 'src'); -const DIST = join(import.meta.dirname, '..', 'dist'); - -const results = []; -function walk(dir) { - for (const entry of readdirSync(dir, { withFileTypes: true })) { - const full = join(dir, entry.name); - if (entry.isDirectory() && entry.name !== 'node_modules') walk(full); - else if (/\.(tsx?|jsx?)$/.test(entry.name)) { - const content = readFileSync(full, 'utf-8'); - results.push({ file: relative(join(SRC, '..'), full), content, lines: content.split('\n').length }); - } - } -} -walk(SRC); - -// ─── 1. Dependency Graph Analysis ─── -console.log('═══════════════════════════════════════════════'); -console.log(' 1. MODULE DEPENDENCY GRAPH ANALYSIS'); -console.log('═══════════════════════════════════════════════'); - -const importMap = new Map(); // file -> [imports] -const importedBy = new Map(); // file -> [importers] - -for (const r of results) { - const imports = []; - // Match import statements - const importRe = /import\s+(?:.*?\s+from\s+)?['"]([^'"]+)['"]/g; - let m; - while ((m = importRe.exec(r.content)) !== null) { - imports.push(m[1]); - } - // Match dynamic imports - const dynRe = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g; - while ((m = dynRe.exec(r.content)) !== null) { - imports.push(`[dynamic]${m[1]}`); - } - importMap.set(r.file, imports); -} - -// Find circular dependencies -console.log('\n--- Circular Dependency Detection ---'); -function findCircular(file, visited = new Set(), path = []) { - if (visited.has(file)) { - if (path.includes(file)) { - console.log(` [CIRCULAR] ${path.slice(path.indexOf(file)).join(' -> ')} -> ${file}`); - } - return; - } - visited.add(file); - path.push(file); - const deps = importMap.get(file) || []; - for (const dep of deps) { - if (dep.startsWith('.') || dep.startsWith('/')) { - // Resolve relative path - const dir = file.split('/').slice(0, -1).join('/'); - const resolved = dep.replace(/^\.\//, dir + '/').replace(/^\.\.\//, ''); - // Find matching file - for (const r of results) { - if (r.file.includes(resolved) || r.file.includes(basename(resolved))) { - findCircular(r.file, new Set(visited), [...path]); - } - } - } - } -} - -// Check high-fanin files (imported by many) -const fanIn = new Map(); -for (const [file, imports] of importMap) { - for (const imp of imports) { - const key = imp.replace(/\[dynamic\]/, ''); - fanIn.set(key, (fanIn.get(key) || 0) + 1); - } -} - -console.log('\n--- High Fan-In Modules (most imported) ---'); -const sortedFanIn = [...fanIn.entries()].sort((a, b) => b[1] - a[1]).slice(0, 15); -for (const [mod, count] of sortedFanIn) { - const bar = '█'.repeat(Math.min(30, count)); - console.log(` ${mod.padEnd(50)} ${String(count).padStart(3)}x ${bar}`); -} - -// Check high-fanout files (import many) -console.log('\n--- High Fan-Out Modules (import most) ---'); -const sortedFanOut = [...importMap.entries()] - .map(([f, imps]) => [f, imps.length]) - .sort((a, b) => b[1] - a[1]).slice(0, 15); -for (const [file, count] of sortedFanOut) { - const bar = '█'.repeat(Math.min(30, count)); - console.log(` ${file.padEnd(50)} ${String(count).padStart(3)} deps ${bar}`); -} - -// Dynamic imports analysis (lazy loading effectiveness) -console.log('\n--- Lazy Loading (Dynamic Imports) ---'); -let dynamicImports = 0, staticImports = 0; -for (const [file, imports] of importMap) { - for (const imp of imports) { - if (imp.startsWith('[dynamic]')) dynamicImports++; - else staticImports++; - } -} -console.log(` Static imports: ${staticImports}`); -console.log(` Dynamic imports: ${dynamicImports}`); -console.log(` Lazy load ratio: ${((dynamicImports / (staticImports + dynamicImports)) * 100).toFixed(1)}%`); - -// Find files that should be lazy loaded but aren't -const largePages = results.filter(r => r.lines > 500 && r.file.includes('Page')); -for (const r of largePages) { - const isLazyImported = [...importMap.values()].some(imps => - imps.some(i => i.startsWith('[dynamic]') && i.includes(basename(r.file, '.tsx'))) - ); - if (!isLazyImported && !r.file.includes('App')) { - // Check if it's referenced in App.tsx - const appContent = results.find(x => x.file === 'src/App.tsx')?.content || ''; - if (appContent.includes(basename(r.file, '.tsx'))) { - console.log(` [INFO] ${r.file} (${r.lines} lines) - loaded via App.tsx`); - } - } -} - -// ─── 2. React Rendering Cost Analysis ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' 2. REACT RENDERING COST ANALYSIS'); -console.log('═══════════════════════════════════════════════'); - -// Count useState/useReducer per component (state update triggers re-render) -console.log('\n--- State Hook Density ---'); -for (const r of results) { - if (!r.file.endsWith('.tsx')) continue; - const stateHooks = (r.content.match(/useState\s*[<(]/g) || []).length; - const reducers = (r.content.match(/useReducer\s*[<(]/g) || []).length; - const effects = (r.content.match(/useEffect\s*\(/g) || []).length; - const memos = (r.content.match(/useMemo\s*\(/g) || []).length; - const callbacks = (r.content.match(/useCallback\s*[<(]/g) || []).length; - const refs = (r.content.match(/useRef\s*[<(]/g) || []).length; - - const totalHooks = stateHooks + reducers + effects + memos + callbacks + refs; - if (totalHooks > 15) { - const risk = totalHooks > 30 ? '🔴 HIGH' : totalHooks > 20 ? '🟡 MEDIUM' : '🟢 LOW'; - console.log(` ${risk} ${r.file}`); - console.log(` useState:${stateHooks} useReducer:${reducers} useEffect:${effects} useMemo:${memos} useCallback:${callbacks} useRef:${refs} (total:${totalHooks})`); - - // Check if there are many state updates that could be batched - const setters = r.content.match(/set\w+\(/g) || []; - if (setters.length > 20) { - console.log(` ⚠️ ${setters.length} state setter calls — potential for excessive re-renders`); - } - - // Check for missing useMemo on expensive computations - const expensiveInRender = (r.content.match(/\.map\(|\.filter\(|\.reduce\(|\.sort\(/g) || []).length; - if (expensiveInRender > 5 && memos === 0) { - console.log(` ⚠️ ${expensiveInRender} array operations in render body with 0 useMemo`); - } - } -} - -// ─── 3. useEffect Dependency Analysis ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' 3. useEffect DEPENDENCY ANALYSIS'); -console.log('═══════════════════════════════════════════════'); - -for (const r of results) { - if (!r.file.endsWith('.tsx')) continue; - // Find useEffect with no dependency array (runs every render) - const noDeps = (r.content.match(/useEffect\s*\(\s*\(\)\s*=>\s*\{[\s\S]*?\}\s*\)/g) || []).length; - // Find useEffect with empty deps - const emptyDeps = (r.content.match(/useEffect\s*\(\s*\(\)\s*=>\s*\{[\s\S]*?\}\s*,\s*\[\s*\]\s*\)/g) || []).length; - - if (noDeps > 0) { - console.log(` [RENDER-COST] ${r.file}: ${noDeps} useEffect(s) run EVERY render`); - } -} - -// ─── 4. Zustand Store Analysis ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' 4. ZUSTAND STORE ANALYSIS'); -console.log('═══════════════════════════════════════════════'); - -const storeFiles = results.filter(r => r.file.includes('store') || r.file.includes('Store')); -for (const r of storeFiles) { - const stateFields = (r.content.match(/^\s+\w+:/gm) || []).length; - const actions = (r.content.match(/^\s+\w+\s*:\s*(\(|function|\w+\s*=>)/gm) || []).length; - const subscribers = (r.content.match(/subscribe\s*\(/g) || []).length; - - console.log(`\n ${r.file}`); - console.log(` State fields: ~${stateFields}`); - console.log(` Actions: ~${actions}`); - console.log(` Subscribers: ${subscribers}`); - - // Check for selector usage patterns - if (r.content.includes('set(') && !r.content.includes('useShallow')) { - console.log(` ⚠️ Uses set() without useShallow — may cause unnecessary re-renders`); - } -} - -// ─── 5. Bundle Composition Analysis ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' 5. BUNDLE COMPOSITION ANALYSIS'); -console.log('═══════════════════════════════════════════════'); - -if (readdirSync(DIST).includes('assets')) { - const assets = readdirSync(join(DIST, 'assets')); - const jsFiles = assets.filter(f => f.endsWith('.js') && !f.endsWith('.br')); - const cssFiles = assets.filter(f => f.endsWith('.css') && !f.endsWith('.br')); - - let totalJs = 0, totalCss = 0; - const chunks = []; - for (const f of jsFiles) { - const size = statSync(join(DIST, 'assets', f)).size; - totalJs += size; - chunks.push({ name: f, sizeKB: size / 1024, type: 'js' }); - } - for (const f of cssFiles) { - const size = statSync(join(DIST, 'assets', f)).size; - totalCss += size; - chunks.push({ name: f, sizeKB: size / 1024, type: 'css' }); - } - - chunks.sort((a, b) => b.sizeKB - a.sizeKB); - - console.log(`\n Total JS: ${(totalJs / 1024).toFixed(1)} KB (${(totalJs / 1024 / 1024).toFixed(2)} MB)`); - console.log(` Total CSS: ${(totalCss / 1024).toFixed(1)} KB (${(totalCss / 1024 / 1024).toFixed(2)} MB)`); - console.log(` Total: ${((totalJs + totalCss) / 1024).toFixed(1)} KB`); - - // CSS is suspiciously large - const cssRatio = totalCss / totalJs; - if (cssRatio > 0.5) { - console.log(`\n ⚠️ CSS/JS ratio: ${(cssRatio * 100).toFixed(0)}% — CSS bundle may contain unused styles`); - console.log(` Consider: PurgeCSS, CSS Modules, or extracting to separate loads`); - } - - // Check initial load budget - const initialChunks = chunks.filter(c => - c.name.includes('index') || c.name.includes('vendor-react') || c.name.includes('vendor-antd') - ); - const initialLoad = initialChunks.reduce((s, c) => s + c.sizeKB, 0); - console.log(`\n Critical path (initial load):`); - for (const c of initialChunks) { - console.log(` ${c.name.padEnd(45)} ${c.sizeKB.toFixed(1).padStart(8)} KB`); - } - console.log(` ${'TOTAL'.padEnd(45)} ${initialLoad.toFixed(1).padStart(8)} KB`); - - if (initialLoad > 300) { - console.log(`\n ⚠️ Initial load > 300KB — consider code splitting or tree-shaking`); - } -} - -// ─── 6. API Client Efficiency ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' 6. API CLIENT EFFICIENCY'); -console.log('═══════════════════════════════════════════════'); - -const apiFiles = results.filter(r => r.file.includes('src/api/')); -for (const r of apiFiles) { - const fetchCalls = (r.content.match(/fetch\s*\(/g) || []).length; - const retries = (r.content.match(/retry|Retry|RETRY/g) || []).length; - const abortSignals = (r.content.match(/AbortSignal|AbortController/g) || []).length; - const timeouts = (r.content.match(/timeout|Timeout|setTimeout/g) || []).length; - - if (fetchCalls > 0) { - console.log(`\n ${r.file}`); - console.log(` fetch calls: ${fetchCalls}`); - console.log(` retry logic: ${retries > 0 ? '✅' : '❌ none'}`); - console.log(` abort signals: ${abortSignals > 0 ? '✅' : '❌ none'}`); - console.log(` timeouts: ${timeouts}`); - } -} - -// ─── 7. TypeScript Compilation Metrics ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' 7. TYPE COMPLEXITY'); -console.log('═══════════════════════════════════════════════'); - -let totalTypes = 0, totalInterfaces = 0, totalEnums = 0, totalGenerics = 0; -for (const r of results) { - totalTypes += (r.content.match(/^export\s+type\s+/gm) || []).length; - totalTypes += (r.content.match(/^type\s+/gm) || []).length; - totalInterfaces += (r.content.match(/^export\s+interface\s+/gm) || []).length; - totalInterfaces += (r.content.match(/^interface\s+/gm) || []).length; - totalEnums += (r.content.match(/enum\s+\w+/g) || []).length; - totalGenerics += (r.content.match(/<\w+(?:\s*,\s*\w+)*>/g) || []).length; -} - -console.log(` Type aliases: ${totalTypes}`); -console.log(` Interfaces: ${totalInterfaces}`); -console.log(` Enums: ${totalEnums}`); -console.log(` Generic usages: ${totalGenerics}`); -console.log(` Total TS files: ${results.length}`); - -// ─── Final Summary ─── -console.log('\n═══════════════════════════════════════════════'); -console.log(' ANALYSIS COMPLETE'); -console.log('═══════════════════════════════════════════════'); diff --git a/scripts/dynamic-analysis.mjs b/scripts/dynamic-analysis.mjs deleted file mode 100644 index ae8106e..0000000 --- a/scripts/dynamic-analysis.mjs +++ /dev/null @@ -1,305 +0,0 @@ -/** - * Dynamic performance analysis using Playwright. - * Measures: page load, bundle sizes, memory, rendering, network. - */ -import { chromium } from 'playwright'; -import { readFileSync, readdirSync, statSync } from 'fs'; -import { join } from 'path'; - -const DIST = join(import.meta.dirname, '..', 'dist'); -const PORT = 4174; - -// ─── Bundle analysis from dist ─── -function analyzeBundles() { - const assets = readdirSync(join(DIST, 'assets')); - const jsFiles = assets.filter(f => f.endsWith('.js')); - const cssFiles = assets.filter(f => f.endsWith('.css')); - const brFiles = assets.filter(f => f.endsWith('.js.br')); - - let totalJsSize = 0, totalCssSize = 0, totalBrSize = 0; - const bundles = []; - - for (const f of jsFiles) { - const size = statSync(join(DIST, 'assets', f)).size; - totalJsSize += size; - bundles.push({ name: f, sizeKB: (size / 1024).toFixed(2), type: 'js' }); - } - for (const f of cssFiles) { - const size = statSync(join(DIST, 'assets', f)).size; - totalCssSize += size; - bundles.push({ name: f, sizeKB: (size / 1024).toFixed(2), type: 'css' }); - } - for (const f of brFiles) { - const size = statSync(join(DIST, 'assets', f)).size; - totalBrSize += size; - } - - bundles.sort((a, b) => parseFloat(b.sizeKB) - parseFloat(a.sizeKB)); - - console.log('\n═══════════════════════════════════════════════'); - console.log(' BUNDLE SIZE ANALYSIS'); - console.log('═══════════════════════════════════════════════'); - console.log(`\nTotal JS (raw): ${(totalJsSize / 1024).toFixed(1)} KB`); - console.log(`Total CSS (raw): ${(totalCssSize / 1024).toFixed(1)} KB`); - console.log(`Total JS (brotli): ${(totalBrSize / 1024).toFixed(1)} KB`); - console.log(`\nTop 15 bundles by raw size:`); - for (const b of bundles.slice(0, 15)) { - const bar = '█'.repeat(Math.min(40, Math.round(parseFloat(b.sizeKB) / 5))); - console.log(` ${b.name.padEnd(45)} ${String(b.sizeKB).padStart(8)} KB ${bar}`); - } - - // Identify oversized chunks - console.log('\n⚠️ Oversized chunks (>50KB raw):'); - for (const b of bundles.filter(b => parseFloat(b.sizeKB) > 50)) { - console.log(` [WARN] ${b.name} = ${b.sizeKB} KB`); - } - - return { totalJsSize, totalCssSize, totalBrSize, bundles }; -} - -// ─── Runtime performance with Playwright ─── -async function runtimeAnalysis() { - console.log('\n═══════════════════════════════════════════════'); - console.log(' RUNTIME PERFORMANCE ANALYSIS'); - console.log('═══════════════════════════════════════════════'); - - const browser = await chromium.launch({ headless: true }); - const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const page = await context.newPage(); - - // Collect performance metrics - const perfEntries = []; - page.on('console', msg => { - if (msg.type() === 'info' && msg.text().startsWith('[PERF]')) { - perfEntries.push(msg.text()); - } - }); - - // Track network requests - const networkRequests = []; - page.on('request', req => { - networkRequests.push({ - url: req.url(), - method: req.method(), - startTime: Date.now() - }); - }); - - const networkResponses = []; - page.on('response', res => { - networkResponses.push({ - url: res.url(), - status: res.status(), - size: res.headers()['content-length'] || '0', - endTime: Date.now() - }); - }); - - // Measure page load - console.log('\n--- Page Load Performance ---'); - const navStart = Date.now(); - - try { - const response = await page.goto(`http://127.0.0.1:${PORT}/`, { - waitUntil: 'networkidle', - timeout: 30000 - }); - const loadTime = Date.now() - navStart; - console.log(` Initial page load: ${loadTime}ms`); - console.log(` HTTP status: ${response.status()}`); - - // Get navigation timing from the browser - const navTiming = await page.evaluate(() => { - const perf = performance.getEntriesByType('navigation')[0]; - if (!perf) return null; - return { - dns: Math.round(perf.domainLookupEnd - perf.domainLookupStart), - tcp: Math.round(perf.connectEnd - perf.connectStart), - ttfb: Math.round(perf.responseStart - perf.requestStart), - download: Math.round(perf.responseEnd - perf.responseStart), - domInteractive: Math.round(perf.domInteractive), - domComplete: Math.round(perf.domComplete), - loadEvent: Math.round(perf.loadEventEnd), - transferSize: perf.transferSize, - }; - }); - - if (navTiming) { - console.log(`\n Navigation Timing:`); - console.log(` DNS lookup: ${navTiming.dns}ms`); - console.log(` TCP connect: ${navTiming.tcp}ms`); - console.log(` TTFB: ${navTiming.ttfb}ms`); - console.log(` Download: ${navTiming.download}ms`); - console.log(` DOM interactive: ${navTiming.domInteractive}ms`); - console.log(` DOM complete: ${navTiming.domComplete}ms`); - console.log(` Load event end: ${navTiming.loadEvent}ms`); - console.log(` Transfer size: ${(navTiming.transferSize / 1024).toFixed(1)} KB`); - } - - // Resource timing - const resources = await page.evaluate(() => { - return performance.getEntriesByType('resource').map(r => ({ - name: r.name.split('/').pop(), - type: r.initiatorType, - duration: Math.round(r.duration), - size: r.transferSize, - })); - }); - - console.log(`\n--- Resource Loading ---`); - console.log(` Total resources: ${resources.length}`); - const totalTransfer = resources.reduce((s, r) => s + r.size, 0); - console.log(` Total transfer: ${(totalTransfer / 1024).toFixed(1)} KB`); - - const slowResources = resources.filter(r => r.duration > 100).sort((a, b) => b.duration - a.duration); - if (slowResources.length > 0) { - console.log(`\n Slow resources (>100ms):`); - for (const r of slowResources.slice(0, 10)) { - console.log(` [SLOW] ${r.name.padEnd(40)} ${r.duration}ms (${(r.size/1024).toFixed(1)}KB)`); - } - } - - // Memory analysis - console.log(`\n--- Memory Analysis ---`); - const memory = await page.evaluate(() => { - if (performance.memory) { - return { - usedJSHeap: performance.memory.usedJSHeapSize, - totalJSHeap: performance.memory.totalJSHeapSize, - heapLimit: performance.memory.jsHeapSizeLimit, - }; - } - return null; - }); - - if (memory) { - console.log(` Used JS heap: ${(memory.usedJSHeap / 1024 / 1024).toFixed(1)} MB`); - console.log(` Total JS heap: ${(memory.totalJSHeap / 1024 / 1024).toFixed(1)} MB`); - console.log(` Heap limit: ${(memory.heapLimit / 1024 / 1024).toFixed(1)} MB`); - console.log(` Heap utilization: ${((memory.usedJSHeap / memory.heapLimit) * 100).toFixed(1)}%`); - } else { - console.log(' Memory API not available (Chromium flag needed: --enable-precise-memory-info)'); - } - - // DOM complexity - console.log(`\n--- DOM Complexity ---`); - const domStats = await page.evaluate(() => { - const allElements = document.querySelectorAll('*'); - const tagCounts = {}; - let maxDepth = 0; - let totalNodes = allElements.length; - - allElements.forEach(el => { - const tag = el.tagName.toLowerCase(); - tagCounts[tag] = (tagCounts[tag] || 0) + 1; - let depth = 0, parent = el.parentElement; - while (parent) { depth++; parent = parent.parentElement; } - if (depth > maxDepth) maxDepth = depth; - }); - - return { totalNodes, maxDepth, tagCounts }; - }); - - console.log(` Total DOM nodes: ${domStats.totalNodes}`); - console.log(` Max DOM depth: ${domStats.maxDepth}`); - console.log(` Top 10 tags:`); - const sortedTags = Object.entries(domStats.tagCounts).sort((a, b) => b[1] - a[1]); - for (const [tag, count] of sortedTags.slice(0, 10)) { - console.log(` <${tag}>: ${count}`); - } - - // DOM warnings - if (domStats.totalNodes > 1500) { - console.log(` ⚠️ DOM nodes > 1500 — may cause sluggish rendering`); - } - if (domStats.maxDepth > 15) { - console.log(` ⚠️ DOM depth > 15 — may slow style/layout calculations`); - } - - // React-specific analysis: check for unnecessary re-renders - console.log(`\n--- Render Performance ---`); - const renderMetrics = await page.evaluate(() => { - // Check if React DevTools hook exists - const hasReact = !!window.__REACT_DEVTOOLS_GLOBAL_HOOK__; - return { - hasReact, - eventListeners: typeof getEventListeners !== 'undefined' ? 'available' : 'not-in-page-context', - }; - }); - console.log(` React DevTools: ${renderMetrics.hasReact ? 'detected' : 'not detected'}`); - - // Measure interaction performance - simulate scroll - console.log(`\n--- Interaction Performance ---`); - const scrollStart = Date.now(); - await page.evaluate(() => { - const container = document.querySelector('.app-shell-content') || document.documentElement; - for (let i = 0; i < 10; i++) { - container.scrollTop = i * 100; - } - container.scrollTop = 0; - }); - const scrollTime = Date.now() - scrollStart; - console.log(` 10x scroll ops: ${scrollTime}ms`); - - // Navigate to different routes to test lazy loading - const routes = ['#/', '#/canvas', '#/workbench', '#/ecommerce', '#/image-workbench', '#/home']; - console.log(`\n--- Route Navigation (Lazy Loading) ---`); - for (const route of routes) { - const routeStart = Date.now(); - await page.goto(`http://127.0.0.1:${PORT}/${route}`, { waitUntil: 'domcontentloaded', timeout: 15000 }); - const routeTime = Date.now() - routeStart; - console.log(` ${route.padEnd(30)} ${routeTime}ms`); - } - - // Memory after navigation - const memoryAfter = await page.evaluate(() => { - if (performance.memory) { - return { - usedJSHeap: performance.memory.usedJSHeapSize, - totalJSHeap: performance.memory.totalJSHeapSize, - }; - } - return null; - }); - - if (memoryAfter) { - console.log(`\n--- Memory After Route Navigation ---`); - console.log(` Used JS heap: ${(memoryAfter.usedJSHeap / 1024 / 1024).toFixed(1)} MB`); - console.log(` Total JS heap: ${(memoryAfter.totalJSHeap / 1024 / 1024).toFixed(1)} MB`); - if (memory) { - const delta = memoryAfter.usedJSHeap - memory.usedJSHeap; - console.log(` Heap delta: ${(delta > 0 ? '+' : '')}${(delta / 1024 / 1024).toFixed(1)} MB`); - } - } - - // Network summary - console.log(`\n--- Network Summary ---`); - console.log(` Total requests: ${networkResponses.length}`); - const totalNetworkSize = networkResponses.reduce((s, r) => s + parseInt(r.size || '0'), 0); - console.log(` Total response: ${(totalNetworkSize / 1024).toFixed(1)} KB`); - const failedRequests = networkResponses.filter(r => r.status >= 400); - if (failedRequests.length > 0) { - console.log(` Failed requests: ${failedRequests.length}`); - for (const r of failedRequests) { - console.log(` [${r.status}] ${r.url}`); - } - } - - } catch (err) { - console.log(`\n ❌ Error during runtime analysis: ${err.message}`); - } finally { - await browser.close(); - } -} - -// ─── Main ─── -console.log('╔═══════════════════════════════════════════════╗'); -console.log('║ OmniAI Web Preview - Performance Analysis ║'); -console.log('╚═══════════════════════════════════════════════╝'); - -const bundleResult = analyzeBundles(); -await runtimeAnalysis(); - -console.log('\n═══════════════════════════════════════════════'); -console.log(' ANALYSIS COMPLETE'); -console.log('═══════════════════════════════════════════════'); diff --git a/scripts/smoke-generation-mocked.mjs b/scripts/smoke-generation-mocked.mjs deleted file mode 100644 index ddf1a75..0000000 --- a/scripts/smoke-generation-mocked.mjs +++ /dev/null @@ -1,72 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import process from "node:process"; - -const repoRoot = process.cwd(); -const failures = []; - -function read(relativePath) { - return fs.readFileSync(path.join(repoRoot, relativePath), "utf8"); -} - -function assertMatch(label, content, pattern) { - if (!pattern.test(content)) { - failures.push(label); - } -} - -function assertNoMatch(label, content, pattern) { - if (pattern.test(content)) { - failures.push(label); - } -} - -const serverConnection = read("src/api/serverConnection.ts"); -const generationClient = read("src/api/aiGenerationClient.ts"); -const ecommerceVideoService = read("src/features/ecommerce/ecommerceVideoService.ts"); -const workbenchPersistence = read("src/features/workbench/workbenchResultPersistence.ts"); - -assertMatch( - "serverConnection must build same-origin /api URLs", - serverConnection, - /return\s+`\/api\/\$\{cleanPath\}`;/, -); -assertNoMatch( - "frontend generation flow must not use fixed VITE environment config", - `${serverConnection}\n${generationClient}`, - /\b(?:import\.meta\.env|VITE_[A-Z0-9_]+)\b/, -); -assertNoMatch( - "frontend generation flow must not call provider hosts directly", - generationClient, - /dashscope\.aliyuncs\.com|\/dashscope-api\b|Bearer\s+sk-/i, -); -assertMatch("image generation must go through the app API", generationClient, /buildApiUrl\("ai\/image"\)/); -assertMatch("video generation must go through the app API", generationClient, /serverRequest<\{ taskId: string \}>\("ai\/video"/); -assertMatch("binary uploads must go through the app OSS API", generationClient, /buildApiUrl\("oss\/upload-binary"\)/); -assertMatch("URL uploads must go through the app OSS API", generationClient, /serverRequest<\{ url: string; signedUrl\?: string; ossKey\?: string \}>\("oss\/upload-by-url"/); -assertMatch( - "ecommerce video history must durable-copy media before saving", - ecommerceVideoService, - /buildDurableVideoHistoryPayload\(payload\)/, -); -assertMatch( - "ecommerce video history must filter temporary provider URLs on read", - ecommerceVideoService, - /items:\s*history\.items\.map\(removeTemporaryHistoryUrls\)/, -); -assertMatch( - "workbench results must persist generated media through OSS", - workbenchPersistence, - /uploadAssetByUrl\(/, -); - -if (failures.length) { - console.error("Mocked generation smoke check failed:"); - for (const failure of failures) { - console.error(`- ${failure}`); - } - process.exit(1); -} - -console.log("Mocked generation smoke check passed."); diff --git a/scripts/static-analysis.mjs b/scripts/static-analysis.mjs deleted file mode 100644 index afa32f9..0000000 --- a/scripts/static-analysis.mjs +++ /dev/null @@ -1,148 +0,0 @@ -import { readdirSync, readFileSync, statSync } from 'fs'; -import { join, relative } from 'path'; - -const SRC = join(import.meta.dirname, '..', 'src'); -const results = []; - -function walk(dir) { - for (const entry of readdirSync(dir, { withFileTypes: true })) { - const full = join(dir, entry.name); - if (entry.isDirectory() && entry.name !== 'node_modules') walk(full); - else if (/\.(tsx?|jsx?)$/.test(entry.name)) { - const content = readFileSync(full, 'utf-8'); - const lines = content.split('\n').length; - results.push({ file: relative(join(SRC, '..'), full), lines, content }); - } - } -} -walk(SRC); -results.sort((a, b) => b.lines - a.lines); - -console.log('=== TOP 30 FILES BY LINE COUNT ==='); -for (const r of results.slice(0, 30)) { - console.log(`${String(r.lines).padStart(5)} ${r.file}`); -} - -// Detect nested loops (3+ levels) -console.log('\n=== NESTED LOOP DETECTION (3+ levels) ==='); -const loopPatterns = [ - /for\s*\(/g, /while\s*\(/g, /\.forEach\s*\(/g, /\.map\s*\(/g, - /\.filter\s*\(/g, /\.reduce\s*\(/g, /\.some\s*\(/g, /\.every\s*\(/g, - /\.flatMap\s*\(/g, /\.find\s*\(/g -]; - -for (const r of results) { - const lines = r.content.split('\n'); - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - let loopCount = 0; - for (const p of loopPatterns) { - p.lastIndex = 0; - if (p.test(line)) loopCount++; - } - // Check surrounding context for nesting - if (loopCount > 0 || /for\s*\(/.test(line) || /\.map\(/.test(line) || /\.forEach\(/.test(line) || /\.filter\(/.test(line) || /\.reduce\(/.test(line)) { - // Count loop keywords on this single line - let singleLineLoops = 0; - for (const p of loopPatterns) { - p.lastIndex = 0; - const matches = line.match(new RegExp(p.source, 'g')); - if (matches) singleLineLoops += matches.length; - } - if (singleLineLoops >= 2) { - console.log(` [NESTED] ${r.file}:${i + 1} (${singleLineLoops} loops on one line)`); - console.log(` ${line.trim().substring(0, 120)}`); - } - } - } -} - -// Detect missing cleanup in useEffect -console.log('\n=== MEMORY LEAK RISK: useEffect without cleanup ==='); -for (const r of results) { - if (!r.file.endsWith('.tsx') && !r.file.endsWith('.ts')) continue; - const content = r.content; - // Find useEffect blocks that contain setInterval/setTimeout/addEventListener but no return - const useEffectRegex = /useEffect\s*\(\s*\(\)\s*=>\s*\{([\s\S]*?)\}\s*,/g; - let match; - while ((match = useEffectRegex.exec(content)) !== null) { - const body = match[1]; - const hasTimer = /setInterval|setTimeout/.test(body); - const hasListener = /addEventListener/.test(body); - const hasSubscribe = /\.subscribe\(/.test(body); - const hasCleanup = /return\s*\(\)\s*=>|return\s*function|return\s*\(\{/.test(body); - - if ((hasTimer || hasListener || hasSubscribe) && !hasCleanup) { - const lineNum = content.substring(0, match.index).split('\n').length; - console.log(` [RISK] ${r.file}:${lineNum}`); - if (hasTimer) console.log(` - Has setInterval/setTimeout without cleanup`); - if (hasListener) console.log(` - Has addEventListener without cleanup`); - if (hasSubscribe) console.log(` - Has subscribe without cleanup`); - console.log(` ${body.trim().substring(0, 200)}`); - console.log(''); - } - } -} - -// Detect objects/arrays/functions created in render body (not memoized) -console.log('\n=== REDUNDANT COMPUTATION: Non-memoized values in components ==='); -for (const r of results) { - if (!r.file.endsWith('.tsx')) continue; - const lines = r.content.split('\n'); - // Look for const x = [...], const x = {...}, const x = (...) => patterns outside useMemo - let inUseMemo = 0; - for (let i = 0; i < lines.length; i++) { - if (/useMemo\s*\(/.test(lines[i])) inUseMemo++; - if (inUseMemo > 0 && /\)/.test(lines[i])) { - // Rough heuristic - not perfect - } - if (inUseMemo === 0) { - // Expensive operations in render - if (/\.map\s*\(.*\.map\s*\(/.test(lines[i])) { - console.log(` [PERF] ${r.file}:${i + 1} - Chained .map calls in render`); - console.log(` ${lines[i].trim().substring(0, 120)}`); - } - if (/\.filter\s*\(.*\.map\s*\(/.test(lines[i]) || /\.map\s*\(.*\.filter\s*\(/.test(lines[i])) { - console.log(` [PERF] ${r.file}:${i + 1} - filter+map chain in render`); - console.log(` ${lines[i].trim().substring(0, 120)}`); - } - } - } -} - -// Detect deeply nested conditionals (4+ levels) -console.log('\n=== HIGH COMPLEXITY: Deep nesting ==='); -for (const r of results) { - const lines = r.content.split('\n'); - let maxIndent = 0; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (line.trim() === '') continue; - const indent = line.match(/^(\s*)/)[1].length; - // Only flag if inside if/else/switch/ternary - if (indent >= 16 && /if\s*\(|else|switch\s*\(|case\s+/.test(line.trim())) { - console.log(` [DEEP] ${r.file}:${i + 1} (indent=${indent})`); - console.log(` ${line.trim().substring(0, 120)}`); - } - } -} - -// Detect inline style objects in JSX (recreated every render) -console.log('\n=== REDUNDANT: Inline style objects in JSX ==='); -for (const r of results) { - if (!r.file.endsWith('.tsx')) continue; - const lines = r.content.split('\n'); - for (let i = 0; i < lines.length; i++) { - if (/style\s*=\s*\{\s*\{/.test(lines[i]) && !/useMemo/.test(lines[i])) { - console.log(` [INLINE] ${r.file}:${i + 1}`); - console.log(` ${lines[i].trim().substring(0, 120)}`); - } - } -} - -// Total stats -console.log('\n=== SUMMARY ==='); -console.log(`Total files: ${results.length}`); -console.log(`Total lines: ${results.reduce((s, r) => s + r.lines, 0)}`); -console.log(`Files > 500 lines: ${results.filter(r => r.lines > 500).length}`); -console.log(`Files > 1000 lines: ${results.filter(r => r.lines > 1000).length}`); diff --git a/src/App.tsx b/src/App.tsx index fb6a1c6..110a7d0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,6 +21,7 @@ import ErrorBoundary from "./components/ErrorBoundary"; import ToastContainer from "./components/toast/ToastContainer"; import { toast } from "./components/toast/toastStore"; import EcommercePage from "./features/ecommerce/EcommercePage"; +import { flushPendingGenerationRecords } from "./api/generationRecordClient"; import { ossAssets } from "./data/ossAssets"; import { keyServerClient } from "./api/keyServerClient"; import { setUserMaxConcurrency } from "./api/generationConcurrency"; @@ -58,7 +59,6 @@ const profileWorks = [ { title: "商品场景图", desc: "按平台比例输出营销素材", image: ossAssets.ecommerce.detail.gridA, type: "图像", time: "6/9 10:01" }, { title: "高度复刻", desc: "参考图结构复刻与商品替换", image: ossAssets.ecommerce.detail.gridB, type: "图像", time: "6/9 09:39" }, { title: "详情模块", desc: "功能卖点、参数和包装模块", image: ossAssets.ecommerce.detail.gridC, type: "图像", time: "6/8 21:20" }, - { title: "广告视频", desc: "商品卖点短视频预览", image: ossAssets.ecommerce.tryOn.jacketResultA, type: "视频", time: "6/8 19:42" }, { title: "平台素材", desc: "淘宝/天猫投放图批量生成", image: ossAssets.ecommerce.detail.gridD, type: "图像", time: "6/8 18:26" }, ]; @@ -210,6 +210,11 @@ function App() { initNotificationPermission(); }, []); + useEffect(() => { + if (!session) return; + void flushPendingGenerationRecords(); + }, [session]); + useEffect(() => { const handleUnhandled = (event: ErrorEvent) => { reportError(event.error || new Error(event.message), "unhandled"); diff --git a/src/api/adVideoPlanClient.ts b/src/api/adVideoPlanClient.ts index f767d95..1b245fc 100644 --- a/src/api/adVideoPlanClient.ts +++ b/src/api/adVideoPlanClient.ts @@ -3,6 +3,26 @@ import { buildApiUrl, buildAuthHeaders } from "./serverConnection"; const TEXT_MODELS = ["qwen-max", "qwen-plus", "qwen-turbo"]; const VISION_MODELS = ["qwen3.7-plus", "qwen-vl-plus", "qwen-vl-max"]; +type AbortSignalConstructorWithAny = typeof AbortSignal & { + any?: (signals: AbortSignal[]) => AbortSignal; +}; + +function combineAbortSignals(signal: AbortSignal | undefined, timeoutSignal: AbortSignal): AbortSignal { + if (!signal) return timeoutSignal; + const abortSignal = AbortSignal as AbortSignalConstructorWithAny; + if (typeof abortSignal.any === "function") return abortSignal.any([signal, timeoutSignal]); + + const controller = new AbortController(); + const abortFrom = (source: AbortSignal) => { + if (!controller.signal.aborted) controller.abort(source.reason); + }; + if (signal.aborted) abortFrom(signal); + else signal.addEventListener("abort", () => abortFrom(signal), { once: true }); + if (timeoutSignal.aborted) abortFrom(timeoutSignal); + else timeoutSignal.addEventListener("abort", () => abortFrom(timeoutSignal), { once: true }); + return controller.signal; +} + export interface AdVideoUserConfig { platform: string; aspectRatio: string; @@ -162,9 +182,7 @@ async function chat( { role: "user", content: userContent }, ]; const timeoutSignal = AbortSignal.timeout(CHAT_TIMEOUT_MS); - const combinedSignal = options?.signal - ? AbortSignal.any([options.signal, timeoutSignal]) - : timeoutSignal; + const combinedSignal = combineAbortSignals(options?.signal, timeoutSignal); const res = await fetch(buildApiUrl("ai/chat"), { method: "POST", headers: buildAuthHeaders(), @@ -210,9 +228,7 @@ async function visionChat( let lastError: Error | null = null; for (const model of VISION_MODELS) { const timeoutSignal = AbortSignal.timeout(CHAT_TIMEOUT_MS); - const combinedSignal = signal - ? AbortSignal.any([signal, timeoutSignal]) - : timeoutSignal; + const combinedSignal = combineAbortSignals(signal, timeoutSignal); try { const out = await retryOnTransient(async () => { const res = await fetch(buildApiUrl("ai/chat"), { diff --git a/src/api/aiGenerationClient.ts b/src/api/aiGenerationClient.ts index 5e78c26..a69c91c 100644 --- a/src/api/aiGenerationClient.ts +++ b/src/api/aiGenerationClient.ts @@ -64,17 +64,6 @@ export interface VideoGenInput { style?: "speech" | "sing" | "performance" | string; } -export interface VideoEditInput { - projectId?: string; - conversationId?: number; - videoUrl: string; - referenceUrls: string[]; - prompt?: string; - model?: string; - ratio?: string; - resolution?: string; -} - export interface VideoSuperResolveInput { projectId?: string; conversationId?: number; @@ -304,16 +293,6 @@ export const aiGenerationClient = { }); }, - async createVideoEditTask(input: VideoEditInput): Promise<{ taskId: string }> { - return serverRequest<{ taskId: string }>("ai/video/edit", { - method: "POST", - body: { ...input, model: input.model || "happyhorse-1.0-video-edit" }, - timeoutMs: TASK_SUBMIT_TIMEOUT_MS, - maxRetries: NON_RETRYING_REQUEST.maxRetries, - fallbackMessage: "Video edit request failed", - }); - }, - async createImageSuperResolveTask(input: ImageSuperResolveInput): Promise<{ taskId: string }> { return serverRequest<{ taskId: string }>("ai/image/super-resolve", { method: "POST", diff --git a/src/api/betaApplicationClient.ts b/src/api/betaApplicationClient.ts deleted file mode 100644 index 39921b2..0000000 --- a/src/api/betaApplicationClient.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { serverRequest } from "./serverConnection"; - -export interface BetaApplicationInput { - name: string; - email: string; - phone: string; - wechat: string; - industry: string; - company: string; - city: string; - aiTools: string; - aiDuration: string; - aiTrack: string; - aiDirection: string[]; - weeklyUsage: string; - feedbackWilling: string; - wantFeature: string[]; - selfStatement: string; - signature: string; - applicationDate: string; - agreeRules: boolean; -} - -export type BetaApplicationStatus = "pending" | "approved" | "rejected"; - -export interface BetaApplicationItem extends BetaApplicationInput { - id: number; - userId: number | null; - username: string | null; - status: BetaApplicationStatus; - inviteCode: string | null; - reviewNote: string | null; - reviewedBy: number | null; - reviewerUsername: string | null; - reviewedAt: string | null; - ipAddress: string | null; - userAgent: string | null; - createdAt: string; - updatedAt: string; -} - -export interface BetaApplicationSubmitResult { - id: number; - status: BetaApplicationStatus; - createdAt: string; -} - -function readString(value: unknown): string { - return typeof value === "string" ? value : ""; -} - -function readNullableString(value: unknown): string | null { - return typeof value === "string" && value ? value : null; -} - -function readNumberOrNull(value: unknown): number | null { - if (value === null || value === undefined || value === "") return null; - const next = Number(value); - return Number.isFinite(next) ? next : null; -} - -function readStringArray(value: unknown): string[] { - return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : []; -} - -function normalizeStatus(value: unknown): BetaApplicationStatus { - return value === "approved" || value === "rejected" ? value : "pending"; -} - -function normalizeApplication(raw: unknown): BetaApplicationItem { - const item = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as Record) : {}; - return { - id: Number(item.id) || 0, - userId: readNumberOrNull(item.userId), - username: readNullableString(item.username), - name: readString(item.name), - email: readString(item.email), - phone: readString(item.phone), - wechat: readString(item.wechat), - industry: readString(item.industry), - company: readString(item.company), - city: readString(item.city), - aiTools: readString(item.aiTools), - aiDuration: readString(item.aiDuration), - aiTrack: readString(item.aiTrack), - aiDirection: readStringArray(item.aiDirection), - weeklyUsage: readString(item.weeklyUsage), - feedbackWilling: readString(item.feedbackWilling), - wantFeature: readStringArray(item.wantFeature), - selfStatement: readString(item.selfStatement), - signature: readString(item.signature), - applicationDate: readString(item.applicationDate), - agreeRules: item.agreeRules === true, - status: normalizeStatus(item.status), - inviteCode: readNullableString(item.inviteCode), - reviewNote: readNullableString(item.reviewNote), - reviewedBy: readNumberOrNull(item.reviewedBy), - reviewerUsername: readNullableString(item.reviewerUsername), - reviewedAt: readNullableString(item.reviewedAt), - ipAddress: readNullableString(item.ipAddress), - userAgent: readNullableString(item.userAgent), - createdAt: readString(item.createdAt), - updatedAt: readString(item.updatedAt), - }; -} - -export const betaApplicationClient = { - async submit(input: BetaApplicationInput): Promise { - const payload = await serverRequest<{ application: BetaApplicationSubmitResult }>("beta-applications", { - method: "POST", - body: input, - maxRetries: 0, - fallbackMessage: "提交内测申请失败", - }); - return payload.application; - }, - - async listAdminApplications(status?: BetaApplicationStatus | ""): Promise { - const query = status ? `?status=${encodeURIComponent(status)}` : ""; - const payload = await serverRequest<{ applications?: unknown[] }>(`admin/beta-applications${query}`, { - fallbackMessage: "读取内测申请失败", - }); - return Array.isArray(payload.applications) ? payload.applications.map(normalizeApplication) : []; - }, - - async reviewApplication( - id: number, - action: "approve" | "reject", - reviewNote?: string, - ): Promise { - const payload = await serverRequest<{ application: unknown }>(`admin/beta-applications/${id}`, { - method: "PATCH", - body: { action, reviewNote }, - maxRetries: 0, - fallbackMessage: "审核内测申请失败", - }); - return normalizeApplication(payload.application); - }, -}; diff --git a/src/api/communityClient.ts b/src/api/communityClient.ts deleted file mode 100644 index 706fb2a..0000000 --- a/src/api/communityClient.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { isRecord, serverRequest } from "./serverConnection"; - -export interface ServerCommunityAsset { - id?: number; - assetType: "image" | "video" | "project" | "workflow" | "asset" | "cover" | "other"; - title?: string | null; - url?: string | null; - ossKey?: string | null; - metadata?: Record; - sortOrder?: number; -} - -export interface ServerCommunityCase { - id: number; - userId?: number; - username?: string | null; - projectId?: string | null; - title: string; - description?: string | null; - coverUrl?: string | null; - tags: string[]; - metadata: Record; - status: "pending" | "approved" | "rejected"; - reviewNote?: string | null; - publishedAt?: string | null; - copyCount: number; - favoriteCount: number; - likeCount: number; - isFavorited: boolean; - isLiked: boolean; - createdAt: string; - updatedAt: string; - assets: ServerCommunityAsset[]; -} - -export interface PublishCommunityCaseInput { - projectId?: string | null; - title: string; - description?: string | null; - coverUrl?: string | null; - tags?: string[]; - metadata?: Record; - assets?: Array<{ - assetType?: ServerCommunityAsset["assetType"]; - title?: string; - url?: string; - ossKey?: string; - metadata?: Record; - sortOrder?: number; - }>; -} - -function toNumber(value: unknown, fallback = 0): number { - const numeric = typeof value === "number" ? value : Number(value); - return Number.isFinite(numeric) ? numeric : fallback; -} - -function toStringValue(value: unknown, fallback = ""): string { - if (typeof value === "string") return value.trim() || fallback; - if (typeof value === "number" && Number.isFinite(value)) return String(value); - return fallback; -} - -function toStringArray(value: unknown): string[] { - if (!Array.isArray(value)) return []; - const result: string[] = []; - for (const item of value) { - const text = toStringValue(item); - if (text) result.push(text); - } - return result; -} - -function toMetadata(value: unknown): Record { - return isRecord(value) ? value : {}; -} - -function normalizeAsset(raw: unknown): ServerCommunityAsset { - const asset = isRecord(raw) ? raw : {}; - const assetType = toStringValue(asset.assetType ?? asset.asset_type ?? asset.type, "other"); - return { - id: Number.isFinite(Number(asset.id)) ? Number(asset.id) : undefined, - assetType: - assetType === "image" || - assetType === "video" || - assetType === "image" || - assetType === "project" || - assetType === "workflow" || - assetType === "asset" || - assetType === "cover" - ? assetType - : "other", - title: toStringValue(asset.title) || null, - url: toStringValue(asset.url) || null, - ossKey: toStringValue(asset.ossKey ?? asset.oss_key) || null, - metadata: toMetadata(asset.metadata), - sortOrder: toNumber(asset.sortOrder ?? asset.sort_order), - }; -} - -function normalizeCase(raw: unknown): ServerCommunityCase { - const item = isRecord(raw) ? raw : {}; - const status = toStringValue(item.status, "pending"); - return { - id: toNumber(item.id), - userId: Number.isFinite(Number(item.userId ?? item.user_id)) ? Number(item.userId ?? item.user_id) : undefined, - username: toStringValue(item.username) || null, - projectId: toStringValue(item.projectId ?? item.project_id) || null, - title: toStringValue(item.title, "未命名案例"), - description: toStringValue(item.description) || null, - coverUrl: toStringValue(item.coverUrl ?? item.cover_url) || null, - tags: toStringArray(item.tags), - metadata: toMetadata(item.metadata), - status: status === "approved" || status === "rejected" ? status : "pending", - reviewNote: toStringValue(item.reviewNote ?? item.review_note) || null, - publishedAt: toStringValue(item.publishedAt ?? item.published_at) || null, - copyCount: toNumber(item.copyCount ?? item.copy_count), - favoriteCount: toNumber(item.favoriteCount ?? item.favorite_count), - likeCount: toNumber(item.likeCount ?? item.like_count), - isFavorited: Boolean(item.isFavorited ?? item.is_favorited), - isLiked: Boolean(item.isLiked ?? item.is_liked), - createdAt: toStringValue(item.createdAt ?? item.created_at, new Date().toISOString()), - updatedAt: toStringValue(item.updatedAt ?? item.updated_at, new Date().toISOString()), - assets: Array.isArray(item.assets) ? item.assets.map(normalizeAsset) : [], - }; -} - -function extractCases(payload: unknown): ServerCommunityCase[] { - if (Array.isArray(payload)) return payload.map(normalizeCase); - if (!isRecord(payload)) return []; - const rows = payload.cases ?? payload.items; - return Array.isArray(rows) ? rows.map(normalizeCase) : []; -} - -export const communityClient = { - async listApprovedCases( - params: number | { limit?: number; q?: string; category?: string; tag?: string; sort?: string } = 30, - ): Promise { - const search = new URLSearchParams(); - if (typeof params === "number") { - search.set("limit", String(params)); - } else { - search.set("limit", String(params.limit ?? 30)); - if (params.q) search.set("q", params.q); - if (params.category && params.category !== "全部") search.set("category", params.category); - if (params.tag) search.set("tag", params.tag); - if (params.sort) search.set("sort", params.sort); - } - return extractCases(await serverRequest(`community/cases?${search.toString()}`)); - }, - - async listMyCases(): Promise { - return extractCases(await serverRequest("community/me/cases")); - }, - - async listCasesForReview(status: "" | ServerCommunityCase["status"] = "pending"): Promise { - const search = new URLSearchParams(); - if (status) search.set("status", status); - const query = search.toString(); - return extractCases(await serverRequest(`admin/community/cases${query ? `?${query}` : ""}`)); - }, - - async publishCase(input: PublishCommunityCaseInput): Promise { - const payload = await serverRequest<{ case: unknown }>("community/cases", { - method: "POST", - body: input, - }); - return normalizeCase(payload.case); - }, - - async updateReviewStatus( - caseId: number | string, - status: Exclude, - reviewNote: string, - ): Promise { - const payload = await serverRequest<{ case: unknown }>(`admin/community/cases/${encodeURIComponent(String(caseId))}/status`, { - method: "PATCH", - body: { status, reviewNote, review_note: reviewNote }, - }); - return normalizeCase(payload.case); - }, - - async copyCase(caseId: number, input?: { projectId?: string; name?: string; ossKey?: string }): Promise { - await serverRequest(`community/cases/${caseId}/copy`, { - method: "POST", - body: input || {}, - }); - }, - - async setReaction( - caseId: number, - reactionType: "favorite" | "like", - active: boolean, - ): Promise<{ favoriteCount: number; likeCount: number; isFavorited: boolean; isLiked: boolean }> { - const payload = await serverRequest<{ stats: unknown }>(`community/cases/${caseId}/reactions`, { - method: "POST", - body: { reactionType, active }, - }); - const stats = isRecord(payload.stats) ? payload.stats : {}; - return { - favoriteCount: toNumber(stats.favoriteCount), - likeCount: toNumber(stats.likeCount), - isFavorited: Boolean(stats.isFavorited), - isLiked: Boolean(stats.isLiked), - }; - }, -}; diff --git a/src/api/conversationClient.ts b/src/api/conversationClient.ts deleted file mode 100644 index 562ebc8..0000000 --- a/src/api/conversationClient.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { serverRequest } from "./serverConnection"; - -export interface ConversationSummary { - id: number; - title: string; - mode: string; - createdAt: string; - updatedAt: string; -} - -export interface ConversationDetail extends ConversationSummary { - messages: ConversationMessage[]; -} - -export interface ConversationMessage { - id: string; - role: "user" | "assistant"; - author: string; - mode: string; - body: string; - createdAt: string; - status?: string; - taskId?: string; - conversationId?: number; - taskProgress?: number; - taskStatusLabel?: string; - attachments?: Array<{ kind: string; name: string; token: string; previewUrl?: string; remoteUrl?: string }>; - resultUrl?: string; - resultType?: string; - resultOriginalUrl?: string; - resultOssKey?: string; - resultMimeType?: string; - result?: { title: string; summary: string; specs: string[] }; -} - -export interface DeleteConversationOptions { - cleanupUserData?: boolean; -} - -export const conversationClient = { - async list(): Promise { - const data = await serverRequest<{ conversations: ConversationSummary[] }>("conversations"); - return data.conversations || []; - }, - - async create(title: string, mode: string, messages?: ConversationMessage[]): Promise { - const data = await serverRequest<{ conversation: ConversationSummary }>("conversations", { - method: "POST", - body: { title, mode, messages }, - }); - return data.conversation; - }, - - async get(id: number): Promise { - const data = await serverRequest<{ conversation: ConversationDetail }>(`conversations/${id}`); - return data.conversation; - }, - - async update(id: number, data: { title?: string; messages?: ConversationMessage[] }): Promise { - await serverRequest(`conversations/${id}`, { method: "PUT", body: data }); - }, - - async delete(id: number, options?: DeleteConversationOptions): Promise { - const path = options?.cleanupUserData ? `conversations/${id}?cleanupUserData=1` : `conversations/${id}`; - await serverRequest(path, { method: "DELETE" }); - }, -}; diff --git a/src/api/draftClient.ts b/src/api/draftClient.ts deleted file mode 100644 index c5c72e0..0000000 --- a/src/api/draftClient.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { isRecord, serverRequest } from "./serverConnection"; - -export interface WebDraft { - id: string; - scope: string; - targetId: string; - payload: TPayload; - createdAt: string; - updatedAt: string; -} - -function toStringValue(value: unknown, fallback = ""): string { - if (typeof value === "string") return value.trim() || fallback; - if (typeof value === "number" && Number.isFinite(value)) return String(value); - return fallback; -} - -function normalizeDraft(raw: unknown): WebDraft { - const item = isRecord(raw) ? raw : {}; - return { - id: toStringValue(item.id, `draft-${Date.now()}`), - scope: toStringValue(item.scope), - targetId: toStringValue(item.targetId ?? item.target_id, "default"), - payload: item.payload, - createdAt: toStringValue(item.createdAt ?? item.created_at), - updatedAt: toStringValue(item.updatedAt ?? item.updated_at), - }; -} - -function extractDrafts(payload: unknown): WebDraft[] { - if (Array.isArray(payload)) return payload.map(normalizeDraft); - if (!isRecord(payload)) return []; - const rows = payload.drafts ?? payload.items; - return Array.isArray(rows) ? rows.map(normalizeDraft) : []; -} - -export const draftClient = { - async list(params?: { scope?: string; targetId?: string }): Promise { - const search = new URLSearchParams(); - if (params?.scope) search.set("scope", params.scope); - if (params?.targetId) search.set("targetId", params.targetId); - const query = search.toString(); - return extractDrafts(await serverRequest(`drafts${query ? `?${query}` : ""}`)); - }, - - async save(input: { scope: string; targetId?: string; payload: TPayload }): Promise> { - const payload = await serverRequest<{ draft: unknown }>("drafts", { - method: "PUT", - body: input, - }); - return normalizeDraft(payload.draft ?? payload) as WebDraft; - }, -}; diff --git a/src/api/generationRecordClient.ts b/src/api/generationRecordClient.ts new file mode 100644 index 0000000..bf6e480 --- /dev/null +++ b/src/api/generationRecordClient.ts @@ -0,0 +1,131 @@ +import { isOptionalApiRouteMissing } from "./apiErrorUtils"; +import { serverRequest } from "./serverConnection"; + +const PENDING_RECORDS_KEY = "omniai:generation-records.pending"; +const MAX_PENDING_RECORDS = 80; + +export type GenerationRecordStatus = "queued" | "running" | "completed" | "failed" | "cancelled"; + +export interface GenerationRecordAsset { + role: "source" | "reference" | "intermediate" | "result" | "thumbnail"; + mediaType: "image" | "video" | "text" | "asset" | string; + url: string; + ossKey?: string | null; + scope?: string; + label?: string; + taskId?: string | null; + metadata?: Record; +} + +export interface SaveGenerationRecordInput { + clientRecordId: string; + tool: string; + mode?: string; + title: string; + status: GenerationRecordStatus; + prompt?: string; + taskIds?: string[]; + assets?: GenerationRecordAsset[]; + config?: Record; + result?: Record; + metadata?: Record; + createdAt?: string; + updatedAt?: string; +} + +export interface SaveGenerationRecordResult { + source: "server" | "local"; + id: string; +} + +function readPendingRecords(): SaveGenerationRecordInput[] { + try { + const raw = window.localStorage.getItem(PENDING_RECORDS_KEY); + if (!raw) return []; + const parsed = JSON.parse(raw); + return Array.isArray(parsed) ? parsed.filter((item): item is SaveGenerationRecordInput => Boolean(item?.clientRecordId)) : []; + } catch { + return []; + } +} + +function writePendingRecord(input: SaveGenerationRecordInput): void { + try { + const records = readPendingRecords(); + const next = [input, ...records.filter((item) => item.clientRecordId !== input.clientRecordId)].slice(0, MAX_PENDING_RECORDS); + window.localStorage.setItem(PENDING_RECORDS_KEY, JSON.stringify(next)); + } catch { + // Ignore storage quota failures; generation itself must not be blocked by history persistence. + } +} + +export async function saveGenerationRecord(input: SaveGenerationRecordInput): Promise { + try { + const response = await serverRequest<{ id?: string | number }>("ai/generation-records", { + method: "POST", + body: input, + maxRetries: 0, + fallbackMessage: "Failed to save generation record", + }); + return { source: "server", id: String(response.id ?? input.clientRecordId) }; + } catch (error) { + if (!isOptionalApiRouteMissing(error)) { + // Keep a local recovery copy even when the route exists but the save fails. + writePendingRecord(input); + return { source: "local", id: input.clientRecordId }; + } + writePendingRecord(input); + return { source: "local", id: input.clientRecordId }; + } +} + +export async function flushPendingGenerationRecords(): Promise<{ synced: number; remaining: number }> { + const pending = readPendingRecords(); + if (!pending.length) return { synced: 0, remaining: 0 }; + + const remaining: SaveGenerationRecordInput[] = []; + let synced = 0; + + for (const record of pending.slice().reverse()) { + try { + await serverRequest<{ id?: string | number }>("ai/generation-records", { + method: "POST", + body: record, + maxRetries: 0, + fallbackMessage: "Failed to sync generation record", + }); + synced += 1; + } catch { + remaining.unshift(record); + } + } + + try { + if (remaining.length) { + window.localStorage.setItem(PENDING_RECORDS_KEY, JSON.stringify(remaining.slice(0, MAX_PENDING_RECORDS))); + } else { + window.localStorage.removeItem(PENDING_RECORDS_KEY); + } + } catch { + // Keep runtime generation unaffected if browser storage is unavailable. + } + + return { synced, remaining: remaining.length }; +} + +export async function deleteGenerationRecordByClientId(clientRecordId: string): Promise { + await serverRequest<{ success: boolean }>(`ai/generation-records/by-client-id/${encodeURIComponent(clientRecordId)}`, { + method: "DELETE", + maxRetries: 0, + fallbackMessage: "Failed to delete generation record", + }); +} + +export function buildGenerationOssScope(parts: Array): string { + return parts + .map((part) => String(part ?? "").trim().toLowerCase()) + .filter(Boolean) + .map((part) => part.replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "")) + .filter(Boolean) + .join("/"); +} diff --git a/src/api/modelCapabilitiesClient.ts b/src/api/modelCapabilitiesClient.ts deleted file mode 100644 index 22614bf..0000000 --- a/src/api/modelCapabilitiesClient.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { isOptionalApiRouteMissing } from "./apiErrorUtils"; -import { isRecord, serverRequest } from "./serverConnection"; - -export interface ModelCapabilityOption { - value: string; - label: string; - description?: string; - badge?: string; - enabled?: boolean; - status?: "available" | "maintenance" | "disabled" | string; -} - -export interface WebModelCapabilities { - source: "server" | "fallback"; - imageModels: ModelCapabilityOption[]; - videoModels: ModelCapabilityOption[]; - chatModels: ModelCapabilityOption[]; - updatedAt?: string; -} - -function toStringValue(value: unknown, fallback = ""): string { - if (typeof value === "string") return value.trim() || fallback; - if (typeof value === "number" && Number.isFinite(value)) return String(value); - return fallback; -} - -function normalizeModelOption(raw: unknown): ModelCapabilityOption | null { - if (typeof raw === "string") { - const value = raw.trim(); - return value ? { value, label: value } : null; - } - if (!isRecord(raw)) return null; - - const value = toStringValue(raw.value ?? raw.id ?? raw.model ?? raw.modelKey ?? raw.model_key); - if (!value) return null; - - const status = toStringValue(raw.status); - const enabled = raw.enabled === undefined ? status !== "maintenance" && status !== "disabled" : Boolean(raw.enabled); - if (!enabled) return null; - - const label = toStringValue(raw.label ?? raw.displayName ?? raw.display_name ?? raw.name, value); - - return { - value, - label: - value === "wan2.7-image-pro" - ? label.replace(/\s*4k\b/i, "").trim() || "wan 2.7 Pro" - : label, - description: toStringValue(raw.description) || undefined, - badge: toStringValue(raw.badge) || undefined, - enabled, - status: status || "available", - }; -} - -function normalizeModelList(value: unknown): ModelCapabilityOption[] { - if (!Array.isArray(value)) return []; - const options: ModelCapabilityOption[] = []; - for (const item of value) { - const option = normalizeModelOption(item); - if (option) options.push(option); - } - return options; -} - -function createFallbackCapabilities(): WebModelCapabilities { - return { - source: "fallback", - imageModels: [], - videoModels: [], - chatModels: [], - }; -} - -let modelCapabilitiesRouteMissing = false; - -export const modelCapabilitiesClient = { - async get(name = "web-model-capabilities"): Promise { - if (modelCapabilitiesRouteMissing) return createFallbackCapabilities(); - - let payload: unknown; - try { - payload = await serverRequest(`public/config/profile?name=${encodeURIComponent(name)}`); - } catch (error) { - if (isOptionalApiRouteMissing(error)) { - modelCapabilitiesRouteMissing = true; - return createFallbackCapabilities(); - } - throw error; - } - - const raw = isRecord(payload) && isRecord(payload.config) ? payload.config : payload; - const config = isRecord(raw) ? raw : {}; - const models = isRecord(config.models) ? config.models : {}; - - return { - source: "server", - imageModels: normalizeModelList(config.imageModels ?? config.image_models ?? models.image), - videoModels: normalizeModelList(config.videoModels ?? config.video_models ?? models.video), - chatModels: normalizeModelList(config.chatModels ?? config.chat_models ?? models.chat ?? models.agent ?? models.text), - updatedAt: toStringValue((payload as { updatedAt?: unknown; updated_at?: unknown })?.updatedAt ?? (payload as { updated_at?: unknown })?.updated_at), - }; - }, -}; diff --git a/src/api/notificationClient.ts b/src/api/notificationClient.ts deleted file mode 100644 index b50afb4..0000000 --- a/src/api/notificationClient.ts +++ /dev/null @@ -1,173 +0,0 @@ -import type { WebNotification, WebNotificationType, WebViewKey } from "../types"; -import { isOptionalApiRouteMissing } from "./apiErrorUtils"; -import { isRecord, isServerRequestError, serverRequest, writeStoredSession } from "./serverConnection"; - -interface CreateNotificationInput { - type: WebNotificationType; - title: string; - description?: string; - targetType?: string; - targetId?: string; - metadata?: Record; -} - -const NOTIFICATION_VIEW_BY_TARGET: Record = { - task: "workbench", - generation_task: "workbench", - community_case: "login", - asset: "assets", - project: "canvas", - draft: "workbench", -}; - -let notificationsRouteMissing = false; -let notificationsUnauthorized = false; - -function toStringValue(value: unknown, fallback = ""): string { - if (typeof value === "string") return value.trim() || fallback; - if (typeof value === "number" && Number.isFinite(value)) return String(value); - return fallback; -} - -function normalizeType(value: unknown): WebNotificationType { - const type = toStringValue(value); - if ( - type === "task_completed" || - type === "task_failed" || - type === "review_pending" || - type === "review_passed" || - type === "review_rejected" || - type === "credits_low" || - type === "session_expired" - ) { - return type; - } - return "info"; -} - -function normalizeNotification(raw: unknown): WebNotification { - const item = isRecord(raw) ? raw : {}; - const targetType = toStringValue(item.targetType ?? item.target_type) || null; - const targetId = toStringValue(item.targetId ?? item.target_id) || undefined; - const readAt = toStringValue(item.readAt ?? item.read_at) || null; - return { - id: toStringValue(item.id, `notice-${Date.now()}`), - type: normalizeType(item.type), - title: toStringValue(item.title, "通知"), - description: toStringValue(item.description), - createdAt: toStringValue(item.createdAt ?? item.created_at, new Date().toISOString()), - isRead: Boolean(item.isRead ?? item.is_read ?? readAt), - targetType, - targetId, - targetView: targetType ? NOTIFICATION_VIEW_BY_TARGET[targetType] : undefined, - readAt, - metadata: isRecord(item.metadata) ? item.metadata : {}, - }; -} - -function extractNotifications(payload: unknown): WebNotification[] { - if (Array.isArray(payload)) return payload.map(normalizeNotification); - if (!isRecord(payload)) return []; - const rows = payload.notifications ?? payload.items; - return Array.isArray(rows) ? rows.map(normalizeNotification) : []; -} - -function isUnauthorized(error: unknown): boolean { - return isServerRequestError(error) && (error.status === 401 || error.status === 403); -} - -function handleUnauthorizedNotifications(): void { - notificationsUnauthorized = true; - writeStoredSession(null); -} - -export const notificationClient = { - async list(): Promise { - if (notificationsRouteMissing || notificationsUnauthorized) return []; - try { - return extractNotifications(await serverRequest("notifications")); - } catch (error) { - if (isOptionalApiRouteMissing(error)) { - notificationsRouteMissing = true; - return []; - } - if (isUnauthorized(error)) { - handleUnauthorizedNotifications(); - return []; - } - throw error; - } - }, - - async create(input: CreateNotificationInput): Promise { - if (notificationsRouteMissing || notificationsUnauthorized) { - return normalizeNotification({ - ...input, - id: `local-notice-${Date.now()}`, - createdAt: new Date().toISOString(), - }); - } - try { - const payload = await serverRequest<{ notification: unknown }>("notifications", { - method: "POST", - body: input, - }); - return normalizeNotification(payload.notification ?? payload); - } catch (error) { - if (isOptionalApiRouteMissing(error)) { - notificationsRouteMissing = true; - return normalizeNotification({ - ...input, - id: `local-notice-${Date.now()}`, - createdAt: new Date().toISOString(), - }); - } - if (isUnauthorized(error)) { - handleUnauthorizedNotifications(); - return normalizeNotification({ - ...input, - id: `local-notice-${Date.now()}`, - createdAt: new Date().toISOString(), - }); - } - throw error; - } - }, - - async markRead(id: string, isRead = true): Promise { - if (notificationsRouteMissing || notificationsUnauthorized) return; - try { - await serverRequest(`notifications/${id}/read`, { - method: "PATCH", - body: { isRead }, - }); - } catch (error) { - if (isOptionalApiRouteMissing(error)) { - notificationsRouteMissing = true; - return; - } - if (isUnauthorized(error)) { - handleUnauthorizedNotifications(); - return; - } - throw error; - } - }, - - async markAllRead(): Promise { - if (notificationsRouteMissing || notificationsUnauthorized) return; - try { - await serverRequest("notifications/read-all", { method: "POST" }); - } catch (error) { - if (isOptionalApiRouteMissing(error)) { - notificationsRouteMissing = true; - return; - } - if (isUnauthorized(error)) { - handleUnauthorizedNotifications(); - return; - } - throw error; - } - }, -}; diff --git a/src/api/projectTaskClient.ts b/src/api/projectTaskClient.ts deleted file mode 100644 index 65c96aa..0000000 --- a/src/api/projectTaskClient.ts +++ /dev/null @@ -1,154 +0,0 @@ -import type { WebGenerationPreviewTask } from "../types"; -import { isRecord, serverRequest } from "./serverConnection"; - -type ServerTaskStatus = "pending" | "running" | "completed" | "failed" | "cancelled"; - -interface ServerProjectTask { - id: string; - projectId?: string | null; - clientQueueId?: string | null; - type: "image" | "video"; - status: ServerTaskStatus; - params?: Record; - resultUrl?: string | null; - progress?: number; - error?: string | null; - createdAt?: string; - updatedAt?: string; -} - -export interface ProjectTaskUpsertInput { - clientQueueId: string; - type: "image" | "video"; - status: ServerTaskStatus; - params?: Record; - providerTaskId?: string | null; - resultUrl?: string | null; - progress?: number; - error?: string | null; - dedupeKey?: string | null; - sourceDeviceId?: string | null; - createdAt?: string | null; - completedAt?: string | null; -} - -function toStringValue(value: unknown, fallback = ""): string { - if (typeof value === "string") return value.trim() || fallback; - if (typeof value === "number" && Number.isFinite(value)) return String(value); - return fallback; -} - -function toNumber(value: unknown, fallback = 0): number { - const numeric = Number(value); - return Number.isFinite(numeric) ? numeric : fallback; -} - -function normalizeStatus(value: unknown): WebGenerationPreviewTask["status"] { - const status = toStringValue(value); - if (status === "running" || status === "completed" || status === "failed") return status; - if (status === "cancelled") return "failed"; - return "queued"; -} - -function normalizeTask(raw: unknown): ServerProjectTask | null { - if (!isRecord(raw)) return null; - const type = toStringValue(raw.type); - if (type !== "image" && type !== "video") return null; - - return { - id: toStringValue(raw.id), - projectId: toStringValue(raw.projectId ?? raw.project_id) || null, - clientQueueId: toStringValue(raw.clientQueueId ?? raw.client_queue_id) || null, - type, - status: toStringValue(raw.status, "pending") as ServerTaskStatus, - params: isRecord(raw.params) ? raw.params : {}, - resultUrl: toStringValue(raw.resultUrl ?? raw.result_url) || null, - progress: toNumber(raw.progress), - error: toStringValue(raw.error) || null, - createdAt: toStringValue(raw.createdAt ?? raw.created_at), - updatedAt: toStringValue(raw.updatedAt ?? raw.updated_at), - }; -} - -function extractTasks(payload: unknown): ServerProjectTask[] { - const normalizeTasks = (rows: unknown[]): ServerProjectTask[] => { - const tasks: ServerProjectTask[] = []; - for (const row of rows) { - const task = normalizeTask(row); - if (task) tasks.push(task); - } - return tasks; - }; - - if (Array.isArray(payload)) return normalizeTasks(payload); - if (!isRecord(payload)) return []; - const rows = payload.tasks ?? payload.items; - return Array.isArray(rows) ? normalizeTasks(rows) : []; -} - -function taskTitle(task: ServerProjectTask): string { - const prompt = toStringValue(task.params?.prompt); - if (prompt) return prompt.length > 20 ? `${prompt.slice(0, 20)}...` : prompt; - return task.type === "video" ? "视频生成任务" : "图像生成任务"; -} - -function toPreviewTask(task: ServerProjectTask): WebGenerationPreviewTask { - return { - id: task.clientQueueId || task.id, - title: taskTitle(task), - type: task.type, - status: normalizeStatus(task.status), - progress: Math.max(0, Math.min(100, Math.trunc(task.progress || 0))), - prompt: toStringValue(task.params?.prompt, taskTitle(task)), - createdAt: task.createdAt || task.updatedAt || "", - projectId: task.projectId || undefined, - outputUrl: task.resultUrl || undefined, - source: "server", - errorMessage: task.error || undefined, - }; -} - -async function listProjectTasks(projectId: string): Promise { - const payload = await serverRequest(`projects/${encodeURIComponent(projectId)}/tasks`); - return extractTasks(payload).map(toPreviewTask); -} - -export const projectTaskClient = { - async list(projectId: string): Promise { - return listProjectTasks(projectId); - }, - - async listForProjects(projectIds: string[]): Promise { - const uniqueIds = new Set(); - for (const projectId of projectIds) { - const id = projectId.trim(); - if (id) uniqueIds.add(id); - } - const results = await Promise.all(Array.from(uniqueIds, (id) => listProjectTasks(id))); - return results.flat(); - }, - - async upsert(projectId: string, input: ProjectTaskUpsertInput): Promise { - const payload = await serverRequest<{ task: unknown }>( - `projects/${encodeURIComponent(projectId)}/tasks/upsert`, - { - method: "POST", - body: input, - }, - ); - const task = normalizeTask(payload.task ?? payload); - if (!task) throw new Error("Project task response did not include a task"); - return toPreviewTask(task); - }, - - async batchUpsert(projectId: string, tasks: ProjectTaskUpsertInput[]): Promise { - const payload = await serverRequest<{ tasks: unknown }>( - `projects/${encodeURIComponent(projectId)}/tasks/batch-upsert`, - { - method: "POST", - body: { tasks }, - }, - ); - return extractTasks(payload).map(toPreviewTask); - }, -}; diff --git a/src/api/providerHealthClient.ts b/src/api/providerHealthClient.ts deleted file mode 100644 index 6552661..0000000 --- a/src/api/providerHealthClient.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { serverRequest } from "./serverConnection"; - -export interface ProviderHealthEntry { - status: string; - lastCheck: string | null; - lastError: string | null; - details: Record | null; -} - -export interface CallStatRow { - provider: string; - model: string; - status: string; - count: string; - avg_ms: string | null; - total_cost: string | null; -} - -export interface KeyStatRow { - provider: string; - total_keys: string; - active_keys: string; - current_load: string; -} - -export interface ProviderHealthResponse { - health: Record; - callStats: CallStatRow[]; - keyStats: KeyStatRow[]; - checkedAt: string; -} - -export const providerHealthClient = { - async getStatus(): Promise { - return serverRequest("admin/providers/status", { - fallbackMessage: "Provider health request failed", - }); - }, -}; diff --git a/src/api/publicConfigClient.ts b/src/api/publicConfigClient.ts deleted file mode 100644 index 6398ede..0000000 --- a/src/api/publicConfigClient.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { isOptionalApiRouteMissing } from "./apiErrorUtils"; -import { isRecord, serverRequest } from "./serverConnection"; - -export interface WebPublicConfig { - contactEmail?: string; - contactPhone?: string; - companyAddress?: string; - icpRecord?: string; -} - -function readString(config: Record, keys: string[]): string | undefined { - for (const key of keys) { - const value = config[key]; - if (typeof value === "string" && value.trim()) return value.trim(); - } - return undefined; -} - -function normalizePublicConfig(raw: unknown): WebPublicConfig { - const config = isRecord(raw) && isRecord(raw.config) ? raw.config : raw; - if (!isRecord(config)) return {}; - - return { - contactEmail: readString(config, ["contactEmail", "contact_email", "supportEmail", "support_email"]), - contactPhone: readString(config, ["contactPhone", "contact_phone", "supportPhone", "support_phone"]), - companyAddress: readString(config, ["companyAddress", "company_address", "address"]), - icpRecord: readString(config, ["icpRecord", "icp_record", "filingInfo", "filing_info"]), - }; -} - -let cachedPublicConfig: WebPublicConfig | null = null; -let publicConfigRouteMissing = false; - -export const publicConfigClient = { - async get(): Promise { - if (cachedPublicConfig) return cachedPublicConfig; - if (publicConfigRouteMissing) return {}; - - try { - const payload = await serverRequest("public/config/profile?name=web-public-config"); - cachedPublicConfig = normalizePublicConfig(payload); - return cachedPublicConfig; - } catch (error) { - if (isOptionalApiRouteMissing(error)) { - publicConfigRouteMissing = true; - return {}; - } - throw error; - } - }, -}; diff --git a/src/api/referenceUploadService.ts b/src/api/referenceUploadService.ts deleted file mode 100644 index 89f60ff..0000000 --- a/src/api/referenceUploadService.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { aiGenerationClient } from "./aiGenerationClient"; - -interface UploadEntry { - promise: Promise; - url: string | null; - status: "pending" | "done" | "failed"; -} - -const uploadCache = new Map(); - -function fileToDataUrl(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(String(reader.result || "")); - reader.onerror = () => reject(reader.error || new Error("文件读取失败")); - reader.readAsDataURL(file); - }); -} - -function buildCacheKey(file: File, fingerprint?: string): string { - if (fingerprint) return fingerprint; - return `${file.name}__${file.size}__${file.lastModified}`; -} - -export function preUploadReference( - file: File, - name: string, - fingerprint?: string, -): Promise { - const key = buildCacheKey(file, fingerprint); - const cached = uploadCache.get(key); - if (cached) return cached.promise; - - const scope = file.type.startsWith("video/") ? "reference-video" : "reference-image"; - - const promise = (async () => { - try { - const dataUrl = await fileToDataUrl(file); - const uploaded = await aiGenerationClient.uploadAsset({ - dataUrl, - name, - mimeType: file.type, - scope, - }); - const entry = uploadCache.get(key); - if (entry) { - entry.url = uploaded.url; - entry.status = "done"; - } - return uploaded.url; - } catch (error) { - const entry = uploadCache.get(key); - if (entry) entry.status = "failed"; - console.warn("[referenceUpload] pre-upload failed:", error); - return null; - } - })(); - - uploadCache.set(key, { promise, url: null, status: "pending" }); - return promise; -} - -export function getPreUploadedUrl( - file: File, - fingerprint?: string, -): string | null { - const key = buildCacheKey(file, fingerprint); - return uploadCache.get(key)?.url ?? null; -} - -export async function resolvePreUploadedUrl( - file: File, - name: string, - fingerprint?: string, -): Promise { - const key = buildCacheKey(file, fingerprint); - const cached = uploadCache.get(key); - if (cached) return cached.promise; - return preUploadReference(file, name, fingerprint); -} - -export function clearUploadCache(): void { - uploadCache.clear(); -} diff --git a/src/api/reportClient.ts b/src/api/reportClient.ts deleted file mode 100644 index 12a02bb..0000000 --- a/src/api/reportClient.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { serverRequest } from "./serverConnection"; - -export interface ReportInput { - reportType: string; - targetType?: string; - targetId?: string; - contactName?: string; - contactEmail?: string; - contactPhone?: string; - title: string; - description: string; - pageUrl?: string; -} - -export interface AdminReportItem { - id: number; - userId?: number | null; - username?: string | null; - reportType?: string | null; - targetType?: string | null; - targetId?: string | null; - contactName?: string | null; - contactEmail?: string | null; - contactPhone?: string | null; - title: string; - description: string; - pageUrl?: string | null; - status: string; - ipAddress?: string | null; - userAgent?: string | null; - createdAt: string; - updatedAt?: string | null; -} - -function normalizeReport(raw: unknown): AdminReportItem { - const item = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as Record) : {}; - return { - id: Number(item.id) || 0, - userId: item.userId == null ? null : Number(item.userId), - username: typeof item.username === "string" ? item.username : null, - reportType: typeof item.reportType === "string" ? item.reportType : null, - targetType: typeof item.targetType === "string" ? item.targetType : null, - targetId: typeof item.targetId === "string" ? item.targetId : null, - contactName: typeof item.contactName === "string" ? item.contactName : null, - contactEmail: typeof item.contactEmail === "string" ? item.contactEmail : null, - contactPhone: typeof item.contactPhone === "string" ? item.contactPhone : null, - title: typeof item.title === "string" && item.title.trim() ? item.title : "未命名举报", - description: typeof item.description === "string" ? item.description : "", - pageUrl: typeof item.pageUrl === "string" ? item.pageUrl : null, - status: typeof item.status === "string" && item.status.trim() ? item.status : "pending", - ipAddress: typeof item.ipAddress === "string" ? item.ipAddress : null, - userAgent: typeof item.userAgent === "string" ? item.userAgent : null, - createdAt: typeof item.createdAt === "string" ? item.createdAt : "", - updatedAt: typeof item.updatedAt === "string" ? item.updatedAt : null, - }; -} - -export const reportClient = { - async submit(input: ReportInput): Promise<{ id: number; status: string; createdAt: string }> { - const payload = await serverRequest<{ report: { id: number; status: string; createdAt: string } }>("reports", { - method: "POST", - body: input, - }); - return payload.report; - }, - - async listAdminReports(): Promise { - const payload = await serverRequest<{ reports?: unknown[] }>("admin/reports"); - return Array.isArray(payload.reports) ? payload.reports.map(normalizeReport) : []; - }, -}; diff --git a/src/api/scriptEvalClient.ts b/src/api/scriptEvalClient.ts deleted file mode 100644 index bc11345..0000000 --- a/src/api/scriptEvalClient.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { serverRequest } from "./serverConnection"; - -export interface ScriptEvalResult { - totalScore: number; - grade: string; - dimensionScores: Record; - subScores?: Record>; - evidence?: Record; - summary: string; - issues: string[]; - highlights: string[]; - suggestions: string[]; -} - -const MODEL = "qwen3.7-max"; - -const EVAL_OUTPUT_CONTRACT = ` -强制输出 JSON,主维度键名必须严格为: -hook(20), plot(20), character(15), logic(15), visual(15), content(15)。 -不要把 dialogue 作为主维度返回;台词对白作为 character/plot/content 的证据和子项分析。 - -同时返回 subScores 和 evidence: -- subScores:每个主维度 3-5 个细分参数,分值按该维度满分拆分。 -- evidence:每个主维度 1-3 条具体证据,必须指向场景、台词、设定、冲突或段落。 - -返回结构: -{ - "dimensionScores": { "hook": 数字, "plot": 数字, "character": 数字, "logic": 数字, "visual": 数字, "content": 数字 }, - "subScores": { - "hook": { "openingImpact": 数字, "suspenseChain": 数字, "sceneHook": 数字 }, - "plot": { "structure": 数字, "rhythm": 数字, "conflict": 数字, "reversal": 数字 }, - "character": { "motivation": 数字, "arc": 数字, "voice": 数字, "relationship": 数字 }, - "logic": { "causality": 数字, "worldRules": 数字, "foreshadowing": 数字, "continuity": 数字 }, - "visual": { "sceneDetail": 数字, "shotPotential": 数字, "aigcFeasibility": 数字 }, - "content": { "theme": 数字, "emotion": 数字, "marketFit": 数字, "originality": 数字 } - }, - "evidence": { "hook": ["..."], "plot": ["..."], "character": ["..."], "logic": ["..."], "visual": ["..."], "content": ["..."] }, - "summary": "200-300字综合评价", - "issues": ["具体扣分点,带维度和证据", ...], - "highlights": ["具体亮点,带维度和证据", ...], - "suggestions": ["按优先级排列的改稿建议", ...] -}`; - -const EVAL_SYSTEM_PROMPT = `你是一位资深影视剧本评审专家,拥有二十年以上的编剧、制片和剧本医生经验。你精通各类影视叙事理论(三幕式、英雄之旅、起承转合、序列法),同时深度跟踪AIGC短剧/漫剧行业最新趋势。你的任务是对用户提供的剧本进行严谨、系统、多维度的量化评分。 - -【剧本类型识别】 -收到剧本后,首先判断类型:AIGC短剧/漫剧(单集5-30分钟,竖屏平台,高密度反转、强节奏)或传统影视剧本(单集40分钟以上,长视频平台,完整起承转合)。类型判定将影响各维度的评价侧重点。 - -【评分体系(100分制,六个维度)】 -1. hook 钩子设计(20分):开篇钩子、集末钩子、场景内钩子、悬念链完整性。短剧前3秒须有即时爆点;长剧第一幕结束前须建立核心悬念。 -2. plot 剧情结构(20分):结构框架、节奏控制、冲突设计、逻辑自洽。短剧"每分钟有事件",反转密度加分;长剧需处理好B线C线与主线交织。 -3. character 角色塑造(18分):主角弧光、角色辨识度、角色动机、配角质量。短剧角色须在前2分钟建立;长剧需要内在矛盾和多阶段成长。 -4. dialogue 台词对白(15分):角色语言区分度、信息传递效率、潜台词与留白、金句与记忆点。 -5. visual 画面表现(15分):场景描写质量、视觉叙事技巧、镜头感与节奏、制作可行性。AIGC需考虑AI生成技术边界与一致性。 -6. content 内容深度(12分):主题表达、情感共鸣、社会/人性洞察。 - -【评分铁律】 -- 扣分必须明确指出剧本中的具体段落/场景/台词。 -- 严禁给出任何维度的满分,必须有扣分理由。 -- 优缺点都要充分展开,不可只批不夸或只夸不批。 -- 不因题材类型偏见降低评分,不因某一方面出色而抬高其他维度(避免光环效应)。 -- 敢于拉开各维度分数差距,避免全部给中等分数。 - -【等级标准】按总分百分比:S≥90 | A 80-89 | B 70-79 | C 60-69 | D<60。 - -请严格按以下 JSON 格式返回(不要包含任何其他文字,不要用代码块包裹以外的说明): -{ - "dimensionScores": { "hook": 数字, "plot": 数字, "character": 数字, "dialogue": 数字, "visual": 数字, "content": 数字 }, - "summary": "200-300字综合评价,概括整体质量、市场潜力与目标受众匹配度", - "issues": ["每条指出具体维度的扣分点并引用剧本原文位置", ...], - "highlights": ["核心亮点,引用剧本具体场景", ...], - "suggestions": ["按优先级排列的改进建议(最优先/次优先/可优化)", ...] -}`; - -const DIMENSION_WEIGHTS: Record = { - hook: { maxScore: 20 }, - plot: { maxScore: 20 }, - character: { maxScore: 15 }, - logic: { maxScore: 15 }, - visual: { maxScore: 15 }, - content: { maxScore: 15 }, -}; - -function computeTotalAndGrade(scores: Record): { totalScore: number; grade: string } { - const totalScore = Math.round( - Object.entries(DIMENSION_WEIGHTS).reduce((sum, [key, dim]) => { - return sum + Math.max(0, Math.min(dim.maxScore, scores[key] ?? 0)); - }, 0), - ); - const grade = totalScore >= 90 ? "S" : totalScore >= 80 ? "A" : totalScore >= 70 ? "B" : totalScore >= 60 ? "C" : "D"; - return { totalScore, grade }; -} - -function extractJson(text: string): unknown { - const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/); - const raw = fenced ? fenced[1].trim() : text.trim(); - return JSON.parse(raw); -} - -function normalizeScoreValue(value: unknown, maxScore: number): number { - const score = Number(value); - if (!Number.isFinite(score)) return 0; - return Math.max(0, Math.min(maxScore, Math.round(score * 10) / 10)); -} - -function isRecord(value: unknown): value is Record { - return Boolean(value && typeof value === "object" && !Array.isArray(value)); -} - -function normalizeEvidenceItems(source: unknown[], limit: number): string[] { - const items: string[] = []; - for (const item of source) { - const value = String(item).trim(); - if (!value) continue; - items.push(value); - if (items.length >= limit) break; - } - return items; -} - -function normalizeNestedScores(value: unknown): Record> { - if (!isRecord(value)) return {}; - - const normalized: Record> = {}; - for (const [dimensionKey, dimension] of Object.entries(DIMENSION_WEIGHTS)) { - const source = value[dimensionKey] ?? (dimensionKey === "logic" ? value.dialogue : undefined); - if (!isRecord(source)) continue; - - const entries = Object.entries(source) - .map(([key, score]) => [key, normalizeScoreValue(score, dimension.maxScore)] as const) - .filter(([, score]) => score > 0); - if (entries.length > 0) normalized[dimensionKey] = Object.fromEntries(entries); - } - - return normalized; -} - -function normalizeEvidence(value: unknown): Record { - if (!isRecord(value)) return {}; - - const normalized: Record = {}; - for (const dimensionKey of Object.keys(DIMENSION_WEIGHTS)) { - const source = value[dimensionKey] ?? (dimensionKey === "logic" ? value.dialogue : undefined); - if (!Array.isArray(source)) continue; - - const items = normalizeEvidenceItems(source, 3); - if (items.length > 0) normalized[dimensionKey] = items; - } - - return normalized; -} - -export async function evaluateScript(script: string, signal?: AbortSignal): Promise { - const payload = await serverRequest<{ - content?: string; - choices?: Array<{ message?: { content?: string } }>; - text?: string; - }>("ai/chat", { - method: "POST", - body: { - model: MODEL, - messages: [ - { role: "system", content: EVAL_SYSTEM_PROMPT }, - { role: "system", content: EVAL_OUTPUT_CONTRACT }, - { role: "user", content: `请评测以下剧本:\n\n${script.slice(0, 8000)}` }, - ], - stream: false, - temperature: 0.3, - max_tokens: 4096, - }, - signal, - timeoutMs: 180_000, - maxRetries: 0, - fallbackMessage: "评测请求失败", - }); - - const content: string = payload?.content ?? payload?.choices?.[0]?.message?.content ?? payload?.text ?? ""; - - if (!content) throw new Error("模型未返回有效内容"); - - const parsed = extractJson(content) as Record; - const dimensionScores: Record = {}; - const rawScores = parsed.dimensionScores as Record | undefined; - if (!rawScores || typeof rawScores !== "object") throw new Error("评分格式异常"); - - for (const key of Object.keys(DIMENSION_WEIGHTS)) { - const rawValue = key === "logic" ? rawScores.logic ?? rawScores.dialogue : rawScores[key]; - dimensionScores[key] = normalizeScoreValue(rawValue, DIMENSION_WEIGHTS[key].maxScore); - } - - const { totalScore, grade } = computeTotalAndGrade(dimensionScores); - - return { - totalScore, - grade, - dimensionScores, - subScores: normalizeNestedScores(parsed.subScores), - evidence: normalizeEvidence(parsed.evidence), - summary: String(parsed.summary || ""), - issues: Array.isArray(parsed.issues) ? parsed.issues.map(String) : [], - highlights: Array.isArray(parsed.highlights) ? parsed.highlights.map(String) : [], - suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.map(String) : [], - }; -} diff --git a/src/api/uploadWithProgress.ts b/src/api/uploadWithProgress.ts deleted file mode 100644 index 149420a..0000000 --- a/src/api/uploadWithProgress.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { buildApiUrl, buildAuthHeaders, readJsonResponse, throwResponseError } from "./serverConnection"; - -export interface UploadProgressOptions { - onProgress?: (percent: number) => void; - signal?: AbortSignal; -} - -export async function uploadAssetWithProgress( - input: { dataUrl: string; name?: string; mimeType?: string; scope?: string }, - options?: UploadProgressOptions, -): Promise<{ url: string; signedUrl?: string; ossKey?: string }> { - const { onProgress, signal } = options || {}; - - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - const url = buildApiUrl("oss/upload"); - const headers = buildAuthHeaders(); - - xhr.open("POST", url); - Object.entries(headers).forEach(([k, v]) => xhr.setRequestHeader(k, v)); - - xhr.upload.addEventListener("progress", (e) => { - if (e.lengthComputable && onProgress) { - onProgress(Math.round((e.loaded / e.total) * 100)); - } - }); - - xhr.addEventListener("load", async () => { - const fakeResponse = new Response(xhr.responseText, { - status: xhr.status, - statusText: xhr.statusText, - headers: { "Content-Type": "application/json" }, - }); - try { - if (!fakeResponse.ok) { - await throwResponseError(fakeResponse, "Asset upload failed"); - } - const result = await readJsonResponse<{ url: string; ossKey?: string }>( - fakeResponse.clone(), - "Asset upload response failed", - ); - resolve(result); - } catch (e) { - reject(e); - } - }); - - xhr.addEventListener("error", () => reject(new Error("上传失败,请检查网络连接"))); - xhr.addEventListener("abort", () => reject(new Error("上传已取消"))); - - if (signal) { - signal.addEventListener("abort", () => xhr.abort()); - } - - xhr.send(JSON.stringify(input)); - }); -} diff --git a/src/api/webGenerationGateway.ts b/src/api/webGenerationGateway.ts index 16ae3e7..9a59609 100644 --- a/src/api/webGenerationGateway.ts +++ b/src/api/webGenerationGateway.ts @@ -50,7 +50,7 @@ export const webGenerationGateway = { const result = await aiGenerationClient.createImageTask({ projectId: params?.projectId, conversationId: params?.conversationId, - model: params?.model || "nano-banana-pro", + model: "gpt-image-2", prompt, ratio: params?.ratio || "16:9", quality: params?.quality || "1K", diff --git a/src/assets/platform-logos/aliexpress.webp b/src/assets/platform-logos/aliexpress.webp deleted file mode 100644 index 2bcdd78..0000000 Binary files a/src/assets/platform-logos/aliexpress.webp and /dev/null differ diff --git a/src/assets/platform-logos/amazon.webp b/src/assets/platform-logos/amazon.webp deleted file mode 100644 index df07796..0000000 Binary files a/src/assets/platform-logos/amazon.webp and /dev/null differ diff --git a/src/assets/platform-logos/douyin.webp b/src/assets/platform-logos/douyin.webp deleted file mode 100644 index 6a3ff4a..0000000 Binary files a/src/assets/platform-logos/douyin.webp and /dev/null differ diff --git a/src/assets/platform-logos/ebay.webp b/src/assets/platform-logos/ebay.webp deleted file mode 100644 index 2efde47..0000000 Binary files a/src/assets/platform-logos/ebay.webp and /dev/null differ diff --git a/src/assets/platform-logos/instagram.webp b/src/assets/platform-logos/instagram.webp deleted file mode 100644 index 34fc586..0000000 Binary files a/src/assets/platform-logos/instagram.webp and /dev/null differ diff --git a/src/assets/platform-logos/jd.webp b/src/assets/platform-logos/jd.webp deleted file mode 100644 index 793751d..0000000 Binary files a/src/assets/platform-logos/jd.webp and /dev/null differ diff --git a/src/assets/platform-logos/lazada.webp b/src/assets/platform-logos/lazada.webp deleted file mode 100644 index 91d1375..0000000 Binary files a/src/assets/platform-logos/lazada.webp and /dev/null differ diff --git a/src/assets/platform-logos/pinduoduo.webp b/src/assets/platform-logos/pinduoduo.webp deleted file mode 100644 index 6ab68a2..0000000 Binary files a/src/assets/platform-logos/pinduoduo.webp and /dev/null differ diff --git a/src/assets/platform-logos/shopee.webp b/src/assets/platform-logos/shopee.webp deleted file mode 100644 index 3356ad8..0000000 Binary files a/src/assets/platform-logos/shopee.webp and /dev/null differ diff --git a/src/assets/platform-logos/taobao.webp b/src/assets/platform-logos/taobao.webp deleted file mode 100644 index 855a1e2..0000000 Binary files a/src/assets/platform-logos/taobao.webp and /dev/null differ diff --git a/src/assets/platform-logos/tiktok-shop.webp b/src/assets/platform-logos/tiktok-shop.webp deleted file mode 100644 index 00254fc..0000000 Binary files a/src/assets/platform-logos/tiktok-shop.webp and /dev/null differ diff --git a/src/assets/platform-logos/tmall.webp b/src/assets/platform-logos/tmall.webp deleted file mode 100644 index dc7eb0e..0000000 Binary files a/src/assets/platform-logos/tmall.webp and /dev/null differ diff --git a/src/components/AnimatedPanel.tsx b/src/components/AnimatedPanel.tsx deleted file mode 100644 index 53e0df8..0000000 --- a/src/components/AnimatedPanel.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect, useRef, useState, type ReactNode } from "react"; - -interface AnimatedPanelProps { - open: boolean; - children: ReactNode; - className?: string; - /** Duration in ms for the exit animation before unmounting. */ - exitDuration?: number; -} - -export function AnimatedPanel({ open, children, className, exitDuration = 140 }: AnimatedPanelProps) { - const [mounted, setMounted] = useState(open); - const [visible, setVisible] = useState(open); - const timerRef = useRef(null); - - useEffect(() => { - if (open) { - if (timerRef.current) { - window.clearTimeout(timerRef.current); - timerRef.current = null; - } - setMounted(true); - requestAnimationFrame(() => { - requestAnimationFrame(() => { - setVisible(true); - }); - }); - } else { - setVisible(false); - timerRef.current = window.setTimeout(() => { - setMounted(false); - timerRef.current = null; - }, exitDuration); - } - }, [open, exitDuration]); - - useEffect(() => { - return () => { - if (timerRef.current) { - window.clearTimeout(timerRef.current); - } - }; - }, []); - - if (!mounted) return null; - - return ( -
- {children} -
- ); -} \ No newline at end of file diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx deleted file mode 100644 index a130343..0000000 --- a/src/components/AppShell.tsx +++ /dev/null @@ -1,541 +0,0 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import type { ReactNode } from "react"; -import { publicConfigClient, type WebPublicConfig } from "../api/publicConfigClient"; -import { toast } from "./toast/toastStore"; -import type { ServerConnectionHealth } from "../api/serverConnection"; -import { ossAssets } from "../data/ossAssets"; -import { canManageCommunityCases, canReviewCommunity } from "../features/community-review/communityPermissions"; -import type { WebNavItem, WebNotification, WebUsageSummary, WebUserSession, WebViewKey } from "../types"; -import NotificationCenter from "./NotificationCenter"; -import BetaApplicationModal from "./BetaApplicationModal"; -import { AnimatedPanel } from "./AnimatedPanel"; -import AdminMonitor from "./AdminMonitor"; -import CookieConsentBanner from "./CookieConsentBanner"; -import { loadRechargeModal, type RechargeModalComponent } from "./RechargeModal/loadRechargeModal"; -import { ShellIcon } from "./ShellIcon"; -import { loadDarkGreenTheme } from "../styles/loadDarkGreenTheme"; - -interface AppShellProps { - activeView: WebViewKey; - navItems: WebNavItem[]; - session: WebUserSession | null; - usage: WebUsageSummary; - notifications: WebNotification[]; - backendHealth: ServerConnectionHealth; - workspaceExpanded: boolean; - onSelectView: (view: WebViewKey) => void; - onLogout: () => void; - onOpenLogin: () => void; - onMarkNotificationRead?: (id: string, isRead?: boolean) => void; - onMarkAllNotificationsRead?: () => void; - children: ReactNode; -} - -const BRAND_LOGO_URL = ossAssets.brand.logo; -const TOOL_SURFACE_VIEW_SET = new Set([ - "workbench", - "canvas", - "more", - "scriptTokens", - "tokenUsage", - "ecommerceTemplates", - "sizeTemplate", - "imageWorkbench", - "resolutionUpscale", - "digitalHuman", - "dialogGenerator", - "avatarConsole", - "characterMix", -] as WebViewKey[]); -const PRIMARY_NAV_ORDER: WebViewKey[] = [ - "workbench", - "ecommerce", - "sizeTemplate", - "canvas", - "scriptTokens", - "tokenUsage", - "more", - "assets", - "community", -]; - -function formatBalance(cents: number): string { - const value = Math.max(0, cents) / 100; - return `${value.toFixed(2)} 积分`; -} - -function canReviewBetaApplications(session: WebUserSession | null): boolean { - const role = String(session?.user.role || "").trim().toLowerCase(); - const username = String(session?.user.username || "").trim().toLowerCase(); - return role === "admin" || username === "xqy1912"; -} - -function AppShell({ - activeView, - navItems, - session, - usage, - notifications, - backendHealth, - workspaceExpanded, - onSelectView, - onLogout, - onOpenLogin, - onMarkNotificationRead, - onMarkAllNotificationsRead, - children, -}: AppShellProps) { - const activePackage = session?.user.activePackages?.[0]; - const profileRef = useRef(null); - const submenuHideTimerRef = useRef(null); - const [profileOpen, setProfileOpen] = useState(false); - const [rechargeOpen, setRechargeOpen] = useState(false); - const [RechargeModal, setRechargeModal] = useState(null); - const [infoOpen, setInfoOpen] = useState(false); - const [betaOpen, setBetaOpen] = useState(false); - const infoRef = useRef(null); - const [openSubmenuKey, setOpenSubmenuKey] = useState(null); - const [publicConfig, setPublicConfig] = useState({}); - const prevActiveViewRef = useRef(activeView); - const [navJustActivated, setNavJustActivated] = useState(null); - const isAuthView = activeView === "login"; - const isImmersiveView = activeView === "agent" || activeView === "avatarConsole"; - const showFloatingNav = !isAuthView && !isImmersiveView && activeView !== "home"; - const showPageScrollActions = showFloatingNav && !TOOL_SURFACE_VIEW_SET.has(activeView); - - const visibleNavItems = useMemo( - () => { - const navItemByKey = new Map(navItems.map((item) => [item.key, item])); - return PRIMARY_NAV_ORDER - .map((key) => navItemByKey.get(key)) - .filter((item): item is WebNavItem => Boolean(item)); - }, - [navItems], - ); - - useEffect(() => { - if (activeView !== prevActiveViewRef.current) { - setNavJustActivated(activeView); - prevActiveViewRef.current = activeView; - const timer = window.setTimeout(() => setNavJustActivated(null), 320); - return () => window.clearTimeout(timer); - } - }, [activeView]); - - useEffect(() => { - if (typeof document === "undefined") { - return; - } - - void loadDarkGreenTheme(); - document.documentElement.dataset.theme = "dark"; - document.documentElement.dataset.uiTheme = "dark-green"; - document.documentElement.style.colorScheme = "dark"; - - const metaThemeColor = document.querySelector("meta[name='theme-color']"); - if (metaThemeColor) { - metaThemeColor.content = "#0d0d0f"; - } - }, []); - - useEffect(() => { - let cancelled = false; - publicConfigClient - .get() - .then((config) => { - if (!cancelled) setPublicConfig(config); - }) - .catch(() => { - if (!cancelled) setPublicConfig({}); - }); - - return () => { - cancelled = true; - }; - }, []); - - useEffect(() => { - if (!profileOpen) return; - - const handlePointerDown = (event: PointerEvent) => { - if (!profileRef.current?.contains(event.target as Node)) { - setProfileOpen(false); - } - }; - - document.addEventListener("pointerdown", handlePointerDown); - return () => document.removeEventListener("pointerdown", handlePointerDown); - }, [profileOpen]); - - useEffect(() => { - if (!infoOpen) return; - const handleInfoOutside = (event: PointerEvent) => { - if (!infoRef.current?.contains(event.target as Node)) { - setInfoOpen(false); - } - }; - document.addEventListener("pointerdown", handleInfoOutside); - return () => document.removeEventListener("pointerdown", handleInfoOutside); - }, [infoOpen]); - - useEffect(() => { - if (!session) { - setProfileOpen(false); - } - }, [session]); - - useEffect(() => { - return () => { - if (submenuHideTimerRef.current) { - window.clearTimeout(submenuHideTimerRef.current); - } - }; - }, []); - - useEffect(() => { - if (!rechargeOpen || RechargeModal) return; - - let cancelled = false; - void loadRechargeModal().then((component) => { - if (!cancelled) { - setRechargeModal(() => component); - } - }); - - return () => { - cancelled = true; - }; - }, [RechargeModal, rechargeOpen]); - - const showSubmenu = (key: WebViewKey) => { - if (submenuHideTimerRef.current) { - window.clearTimeout(submenuHideTimerRef.current); - submenuHideTimerRef.current = null; - } - setOpenSubmenuKey(key); - }; - - const scheduleHideSubmenu = () => { - if (submenuHideTimerRef.current) { - window.clearTimeout(submenuHideTimerRef.current); - } - submenuHideTimerRef.current = window.setTimeout(() => { - setOpenSubmenuKey(null); - submenuHideTimerRef.current = null; - }, 1500); - }; - - const scrollActivePage = (direction: "top" | "bottom") => { - if (typeof document === "undefined") return; - - const targets = [ - ...Array.from(document.querySelectorAll(".web-shell__page")), - ...Array.from( - document.querySelectorAll( - ".workbench-landing-page, .ecommerce-landing-page, .workspace-page-shell__content, .community-page", - ), - ), - document.scrollingElement as HTMLElement | null, - ].filter((target): target is HTMLElement => Boolean(target)); - - targets.forEach((target) => { - const top = direction === "top" ? 0 : target.scrollHeight; - target.scrollTo({ top, behavior: "smooth" }); - }); - }; - - const displayName = session?.user.displayName || session?.user.username || "预览创作者"; - const avatarLabel = displayName.trim().slice(0, 1).toUpperCase() || "创"; - const avatarUrl = session?.user.avatarUrl || null; - const isEnterpriseAccount = Boolean(session?.user.enterpriseId || session?.user.accountType === "enterprise"); - const displayedBalanceCents = - session && isEnterpriseAccount - ? (usage.enterpriseBalanceCents ?? session.user.enterpriseBalanceCents ?? usage.balanceCents) - : usage.balanceCents; - const displayedBalanceLabel = session ? formatBalance(displayedBalanceCents) : "0 积分"; - const showCommunityReview = canReviewCommunity(session); - const showCommunityCaseAdd = canManageCommunityCases(session); - const showBetaApplicationReview = canReviewBetaApplications(session); - - return ( -
-
- {showFloatingNav ? ( - - ) : null} - {showPageScrollActions ? ( -
- - -
- ) : null} - -
- {!isImmersiveView ? ( -
- -
- - {session && ( - onSelectView(view)} - onMarkRead={onMarkNotificationRead} - onMarkAllRead={onMarkAllNotificationsRead} - /> - )} -
- - -
-
备案信息
-
{publicConfig.icpRecord || "由服务器配置"}
-
公司地址
-
{publicConfig.companyAddress || "由服务器配置"}
-
联系电话
-
{publicConfig.contactPhone || "由服务器配置"}
-
- -
-
- -
- - -
- - {avatarUrl ? {displayName} : avatarLabel} - -
- {displayName} - {session ? session.user.role || "已登录" : "预览模式"} -
-
-
-
UID
-
{session?.user.id || "preview"}
-
{isEnterpriseAccount ? "企业积分" : "积分"}
-
{displayedBalanceLabel}
-
图片
-
{usage.imageUsed}
-
视频
-
{usage.videoUsed}
-
-
- {session?.source === "server" ? "服务器会话" : "预览会话"} - -
- - - {showCommunityReview ? ( - <> - - - ) : null} - {showBetaApplicationReview ? ( - - ) : null} - {showCommunityCaseAdd ? ( - <> - - - ) : null} -
-
-
-
- ) : null} -
{children}
-
-
- {session?.user.role === "admin" ? : null} - {rechargeOpen && RechargeModal ? ( - setRechargeOpen(false)} currentBalance={displayedBalanceCents} /> - ) : null} - setBetaOpen(false)} /> -
- ); -} - -export default AppShell; diff --git a/src/components/BeforeAfterCompare.tsx b/src/components/BeforeAfterCompare.tsx deleted file mode 100644 index 0a67142..0000000 --- a/src/components/BeforeAfterCompare.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useRef, useState, type CSSProperties } from "react"; - -interface BeforeAfterCompareProps { - sourceSrc: string; - resultSrc: string; - sourceLabel?: string; - resultLabel?: string; - sourceAlt?: string; - resultAlt?: string; - className?: string; - onSourceLoad?: (width: number, height: number) => void; -} - -const MIN_POSITION = 5; -const MAX_POSITION = 95; - -function clamp(value: number) { - return Math.min(MAX_POSITION, Math.max(MIN_POSITION, value)); -} - -export default function BeforeAfterCompare({ - sourceSrc, - resultSrc, - sourceLabel, - resultLabel, - sourceAlt = "原图", - resultAlt = "结果", - className = "", - onSourceLoad, -}: BeforeAfterCompareProps) { - const stageRef = useRef(null); - const [position, setPosition] = useState(50); - - const updatePosition = (clientX: number) => { - const stage = stageRef.current; - if (!stage) return; - const rect = stage.getBoundingClientRect(); - if (!rect.width) return; - setPosition(clamp(((clientX - rect.left) / rect.width) * 100)); - }; - - return ( -
-
- {sourceAlt} { - onSourceLoad?.(event.currentTarget.naturalWidth, event.currentTarget.naturalHeight); - }} - /> -
-
- {resultAlt} -
- {sourceLabel && ( -
{sourceLabel}
- )} - {resultLabel && ( -
{resultLabel}
- )} -
{ - if (event.key === "ArrowLeft") { - event.preventDefault(); - setPosition((current) => clamp(current - 2)); - } - if (event.key === "ArrowRight") { - event.preventDefault(); - setPosition((current) => clamp(current + 2)); - } - }} - onPointerDown={(event) => { - event.currentTarget.setPointerCapture(event.pointerId); - updatePosition(event.clientX); - }} - onPointerMove={(event) => { - if (!event.currentTarget.hasPointerCapture(event.pointerId)) return; - updatePosition(event.clientX); - }} - onPointerUp={(event) => { - if (event.currentTarget.hasPointerCapture(event.pointerId)) { - event.currentTarget.releasePointerCapture(event.pointerId); - } - }} - onPointerCancel={(event) => { - if (event.currentTarget.hasPointerCapture(event.pointerId)) { - event.currentTarget.releasePointerCapture(event.pointerId); - } - }} - > - -
-
- ); -} diff --git a/src/components/BetaApplicationModal.tsx b/src/components/BetaApplicationModal.tsx deleted file mode 100644 index 619082a..0000000 --- a/src/components/BetaApplicationModal.tsx +++ /dev/null @@ -1,349 +0,0 @@ -import { CloseOutlined, ExperimentOutlined } from "@ant-design/icons"; -import { useState } from "react"; -import { betaApplicationClient } from "../api/betaApplicationClient"; - -interface BetaApplicationModalProps { - open: boolean; - onClose: () => void; -} - -/* ── Form state ── */ -interface BetaFormData { - name: string; - email: string; - phone: string; - wechat: string; - industry: string; - company: string; - city: string; - aiTools: string; - aiDuration: string; - aiTrack: string; - aiDirection: string[]; - weeklyUsage: string; - feedbackWilling: string; - wantFeature: string[]; - selfStatement: string; - signature: string; - applicationDate: string; - agreeRules: boolean; -} - -const INITIAL_FORM: BetaFormData = { - name: "", - email: "", - phone: "", - wechat: "", - industry: "", - company: "", - city: "", - aiTools: "", - aiDuration: "", - aiTrack: "", - aiDirection: [], - weeklyUsage: "", - feedbackWilling: "", - wantFeature: [], - selfStatement: "", - signature: "", - applicationDate: "", - agreeRules: false, -}; - -/* ── Option groups (from the docx) ── */ -const AI_DURATION_OPTIONS = ["1年以内", "1-3年", "3-5年", "5年以上"]; -const AI_TRACK_OPTIONS = ["是,长期承接相关业务", "业余创作", "新手学习"]; -const AI_DIRECTION_OPTIONS = [ - "AI短剧批量制作", "漫剧剧情生成", "自媒体短视频", "电商图文及视频素材", - "MCN商业内容", "企业宣传视频", "个人兴趣创作", "其他", -]; -const WEEKLY_USAGE_OPTIONS = ["7次及以上", "1-3次", "空闲时间使用"]; -const FEEDBACK_OPTIONS = ["全力配合深度反馈", "简单体验留言", "仅使用不反馈"]; -const WANT_FEATURE_OPTIONS = [ - "一站式短剧漫剧完整AIGC工作流", "电商素材自动化创作流程", - "多模态智能中枢全能创作", "批量自动化创作流程", "全新未公开AI创作玩法", -]; - -/* ── Helper: single-select radio group ── */ -function RadioGroup({ - name, options, value, onChange, -}: { - name: string; - options: string[]; - value: string; - onChange: (v: string) => void; -}) { - return ( -
- {options.map((opt) => ( - - ))} -
- ); -} - -/* ── Helper: multi-select checkbox group ── */ -function CheckboxGroup({ - options, value, onChange, -}: { - options: string[]; - value: string[]; - onChange: (v: string[]) => void; -}) { - return ( -
- {options.map((opt) => ( - - ))} -
- ); -} - -/* ── Helper: text field ── */ -function TextField({ - label, value, onChange, placeholder, -}: { - label: string; - value: string; - onChange: (v: string) => void; - placeholder?: string; -}) { - return ( -
- {label} - onChange(e.target.value)} - placeholder={placeholder ?? "请填写"} - /> -
- ); -} - -const BetaApplicationModal = ({ open, onClose }: BetaApplicationModalProps) => { - const [form, setForm] = useState(INITIAL_FORM); - const [submitting, setSubmitting] = useState(false); - const [message, setMessage] = useState<{ tone: "success" | "error"; text: string } | null>(null); - - const update = (key: K, value: BetaFormData[K]) => { - setForm((prev) => ({ ...prev, [key]: value })); - setMessage(null); - }; - - const close = () => { - if (submitting) return; - onClose(); - }; - - const validate = () => { - if (!form.name.trim()) return "请填写姓名 / 常用昵称"; - if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) return "请填写用于接收内测码的有效邮箱"; - if (!form.phone.trim()) return "请填写联系手机号码"; - if (!form.wechat.trim()) return "请填写微信账号"; - if (!form.selfStatement.trim()) return "请填写申请自述"; - if (!form.signature.trim()) return "请填写申请人确认签字"; - if (!form.applicationDate.trim()) return "请填写申请日期"; - if (!form.agreeRules) return "请先阅读并同意内测规则"; - return null; - }; - - const submit = async () => { - if (submitting) return; - const validationError = validate(); - if (validationError) { - setMessage({ tone: "error", text: validationError }); - return; - } - - setSubmitting(true); - setMessage(null); - try { - await betaApplicationClient.submit({ - ...form, - name: form.name.trim(), - email: form.email.trim(), - phone: form.phone.trim(), - wechat: form.wechat.trim(), - industry: form.industry.trim(), - company: form.company.trim(), - city: form.city.trim(), - aiTools: form.aiTools.trim(), - aiDuration: form.aiDuration.trim(), - aiTrack: form.aiTrack.trim(), - weeklyUsage: form.weeklyUsage.trim(), - feedbackWilling: form.feedbackWilling.trim(), - selfStatement: form.selfStatement.trim(), - signature: form.signature.trim(), - applicationDate: form.applicationDate.trim(), - }); - setForm(INITIAL_FORM); - setMessage({ tone: "success", text: "申请已提交,请留意预留邮箱中的审核结果。" }); - } catch (error) { - setMessage({ tone: "error", text: error instanceof Error ? error.message : "提交内测申请失败" }); - } finally { - setSubmitting(false); - } - }; - - if (!open) return null; - - return ( -
- - - - {/* ── Body (scrollable document) ── */} -
- - {/* 一、个人基础信息 */} -
-

一、个人基础信息

-
- update("name", v)} /> - update("email", v)} placeholder="审核通过后内测码将发送到此邮箱" /> - update("phone", v)} /> - update("wechat", v)} /> - update("industry", v)} /> - update("company", v)} /> - update("city", v)} /> -
-
- - {/* 二、AI从业与使用经历 */} -
-

二、AI 从业与使用经历

-
- update("aiTools", v)} placeholder="例如:Midjourney / Stable Diffusion / ChatGPT 等" /> -
- AI 内容创作从业时长 - update("aiDuration", v)} /> -
-
- 是否深耕 AI 短剧、漫剧、数字视频、电商赛道 - update("aiTrack", v)} /> -
-
- 日常主要创作方向(可多选) - update("aiDirection", v)} /> -
-
-
- - {/* 三、内测使用意向调研 */} -
-

三、内测使用意向调研

-
-
- 每周可稳定登录使用内测平台次数 - update("weeklyUsage", v)} /> -
-
- 是否愿意积极反馈产品 BUG、优化建议、功能需求 - update("feedbackWilling", v)} /> -
-
- 本次最想体验 OmniAI 核心功能(可多选) - update("wantFeature", v)} /> -
-
-
- - {/* 四、申请自述 */} -
-

四、申请自述 (必填)

-

请简述自身 AI 创作优势、业务需求,以及加入本次封闭内测的理由:

-