From 0e54ff1f24c3f4489a14181c8116f478c5c4dfb5 Mon Sep 17 00:00:00 2001 From: shabahmd Date: Wed, 26 Aug 2026 03:46:11 +0500 Subject: [PATCH 1/3] feat: pnpm migration, local chat mode, English logs - Switch package manager from npm to pnpm (v11), add pnpm-workspace.yaml - Add enableTempChats config + local chat_mode support in middleware and request util - Translate all logger/console/throw/res Chinese strings to English across 29 files - Remove PM2, package-lock.json from gitignore; add pnpm-lock.yaml - Build frontend successfully with pnpm Co-authored-by: ByNara Model: agnes-2.0-flash --- .env.example | 2 +- .gitignore | 1 - package.json | 14 +- pnpm-lock.yaml | 3767 +++++++++++++++++++++++++++ pnpm-workspace.yaml | 5 + public/package.json | 1 + public/pnpm-lock.yaml | 26 + src/config/index.js | 2 + src/controllers/anthropic.js | 6 +- src/controllers/chat.image.video.js | 80 +- src/controllers/chat.js | 26 +- src/controllers/cli.chat.js | 10 +- src/middlewares/chat-middleware.js | 6 +- src/models/models-map.js | 2 +- src/routes/accounts.js | 34 +- src/routes/settings.js | 22 +- src/server.js | 22 +- src/start.js | 60 +- src/utils/account-rotator.js | 14 +- src/utils/account.js | 108 +- src/utils/chat-helpers.js | 4 +- src/utils/cli.manager.js | 20 +- src/utils/data-persistence.js | 46 +- src/utils/img-caches.js | 6 +- src/utils/logger.js | 8 +- src/utils/redis.js | 78 +- src/utils/request.js | 12 +- src/utils/setting.js | 28 +- src/utils/ssxmod-manager.js | 8 +- src/utils/token-manager.js | 26 +- src/utils/tools.js | 6 +- src/utils/upload.js | 70 +- 32 files changed, 4157 insertions(+), 363 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 public/pnpm-lock.yaml diff --git a/.env.example b/.env.example index ecf6e995..a55e3fdb 100644 --- a/.env.example +++ b/.env.example @@ -3,7 +3,7 @@ # 示例(8080:3000) 则访问 http://localhost:8080 # To change the exposed Docker port, modify the ports parameter # Example: (8080:3000) then access http://localhost:8080 -SERVICE_PORT=3000 +SERVICE_PORT=3423 # 监听地址(非必填) diff --git a/.gitignore b/.gitignore index 470fa533..3c71faea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ node_modules -package-lock.json .tmp/ mitm_mcp_traffic.db .env diff --git a/package.json b/package.json index c377a643..3c9e93bd 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,11 @@ "name": "qwen2api", "version": "2026.08.20.18.50", "main": "src/server.js", + "packageManager": "pnpm@11.22.0", "scripts": { "start": "node src/start.js", "dev": "nodemon src/server.js", - "test": "node --test tests/*.test.js", - "pm2": "pm2 start ecosystem.config.js", - "pm2:stop": "pm2 stop qwen2api", - "pm2:restart": "pm2 restart qwen2api", - "pm2:reload": "pm2 reload qwen2api", - "pm2:delete": "pm2 delete qwen2api", - "pm2:logs": "pm2 logs qwen2api", - "pm2:status": "pm2 status", - "pm2:monit": "pm2 monit" + "test": "node --test tests/*.test.js" }, "keywords": [], "author": "", @@ -25,7 +18,7 @@ "body-parser": "^1.20.3", "cors": "^2.8.5", "dotenv": "^16.4.7", - "express": "^4.21.2", + "express": "^4.22.2", "form-data": "^4.0.2", "https-proxy-agent": "^7.0.6", "ioredis": "^5.6.1", @@ -33,6 +26,7 @@ "mime-types": "^3.0.1", "multer": "^1.4.5-lts.1", "pm2": "^6.0.8", + "qwen2api": "link:/home/shabev/Documents/Code_Projects/Qwen2API", "tiktoken": "^1.0.21" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..e08c4a38 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3767 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + ali-oss: + specifier: ^6.22.0 + version: 6.23.0(supports-color@5.5.0) + axios: + specifier: ^1.11.0 + version: 1.19.0(debug@4.3.7(supports-color@5.5.0))(supports-color@5.5.0) + body-parser: + specifier: ^1.20.3 + version: 1.20.6(supports-color@5.5.0) + cors: + specifier: ^2.8.5 + version: 2.8.6 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + express: + specifier: ^4.22.2 + version: 4.22.2(supports-color@5.5.0) + form-data: + specifier: ^4.0.2 + version: 4.0.6 + https-proxy-agent: + specifier: ^7.0.6 + version: 7.0.6(supports-color@5.5.0) + ioredis: + specifier: ^5.6.1 + version: 5.11.1(supports-color@5.5.0) + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 + mime-types: + specifier: ^3.0.1 + version: 3.0.2 + multer: + specifier: ^1.4.5-lts.1 + version: 1.4.5-lts.2 + pm2: + specifier: ^6.0.8 + version: 6.0.14(supports-color@5.5.0) + qwen2api: + specifier: link:/home/shabev/Documents/Code_Projects/Qwen2API + version: 'link:' + tiktoken: + specifier: ^1.0.21 + version: 1.0.22 + devDependencies: + nodemon: + specifier: ^3.1.7 + version: 3.1.14 + + public: + dependencies: + axios: + specifier: ^1.8.4 + version: 1.19.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) + chart.js: + specifier: ^4.4.0 + version: 4.5.1 + vue: + specifier: ^3.4.29 + version: 3.5.41 + vue-chartjs: + specifier: ^5.3.0 + version: 5.3.4(chart.js@4.5.1)(vue@3.5.41) + vue-i18n: + specifier: ^11.1.3 + version: 11.4.9(vue@3.5.41) + vue-router: + specifier: ^4.5.0 + version: 4.6.4(vue@3.5.41) + devDependencies: + '@vitejs/plugin-vue': + specifier: ^5.0.5 + version: 5.2.4(vite@5.4.21)(vue@3.5.41) + autoprefixer: + specifier: ^10.4.21 + version: 10.5.4(postcss@8.5.26) + postcss: + specifier: ^8.5.3 + version: 8.5.26 + tailwindcss: + specifier: ^3.4.3 + version: 3.4.19 + vite: + specifier: ^5.2.8 + version: 5.4.21 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@intlify/core-base@11.4.9': + resolution: {integrity: sha512-qbGZHXBUwodVCxm6E/IXY0yVLGHuh0GDxnoeCZ5FB75Wq5koKHLrANw5cFhC8fT3WyrkDyvesRzMoFOWsLpPOw==} + engines: {node: '>= 22'} + + '@intlify/devtools-types@11.4.9': + resolution: {integrity: sha512-DFwTGShMQBK9ODzt+EKTRdoHdD65fTA/KbgxBq5gSEsbwYZKVFUKfHwnOYvZnZDL+h56swwXcP6jYRmQMOomvg==} + engines: {node: '>= 22'} + + '@intlify/message-compiler@11.4.9': + resolution: {integrity: sha512-4vlAQsttSX1NgyDOY3sXAEq7HiQPvy21YgBeQUc7Ylu66MwLYVvtqOYSYudDR/SyOahttXvezuNs52s+G104xA==} + engines: {node: '>= 22'} + + '@intlify/shared@11.4.9': + resolution: {integrity: sha512-4qMBu3D64EN5KbkAj9Mv3TFjiPHtKG/YNd5LU5bpb0DZMR+bKh9/uGNx2GovzxWn2Wnyh5uvEhbPR718EVSkCw==} + engines: {node: '>= 22'} + + '@ioredis/commands@1.10.0': + resolution: {integrity: sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@kurkle/color@0.3.4': + resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pm2/agent@2.1.1': + resolution: {integrity: sha512-0V9ckHWd/HSC8BgAbZSoq8KXUG81X97nSkAxmhKDhmF8vanyaoc1YXwc2KVkbWz82Rg4gjd2n9qiT3i7bdvGrQ==} + + '@pm2/blessed@0.1.81': + resolution: {integrity: sha512-ZcNHqQjMuNRcQ7Z1zJbFIQZO/BDKV3KbiTckWdfbUaYhj7uNmUwb+FbdDWSCkvxNr9dBJQwvV17o6QBkAvgO0g==} + engines: {node: '>= 0.8.0'} + hasBin: true + + '@pm2/io@6.1.0': + resolution: {integrity: sha512-IxHuYURa3+FQ6BKePlgChZkqABUKFYH6Bwbw7V/pWU1pP6iR1sCI26l7P9ThUEB385ruZn/tZS3CXDUF5IA1NQ==} + engines: {node: '>=6.0'} + + '@pm2/js-api@0.8.1': + resolution: {integrity: sha512-n9tDOz1ojyDOs05XthEXrLFVQYbbh2oAN19UakLPyEZDrUyEq05h8wIZU8+dNXBQY/KeFlWMLVA76nnX52ofRg==} + engines: {node: '>=4.0'} + + '@pm2/pm2-version-check@1.0.4': + resolution: {integrity: sha512-SXsM27SGH3yTWKc2fKR4SYNxsmnvuBQ9dd6QHtEWmiZ/VqaOYPAIlS8+vMcn27YLtAEBGvNRSh3TPNvtjZgfqA==} + + '@rollup/rollup-android-arm-eabi@4.62.5': + resolution: {integrity: sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.5': + resolution: {integrity: sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.5': + resolution: {integrity: sha512-bW7B8xMEq8n99Q3ieEcPRGuphurdZAaFzQc9Efyyw3FL6DZO6pMy9xhdN+kBoD7Sy05xNXSr4OyPPnpkYriS/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.5': + resolution: {integrity: sha512-YSwBS86QeHOGlrxJ1PSOIZSkzRL/JmKeunhc+lV6M1a6En8QuVCD/T/qIA0J4Gd2Y86RIOBYrLcOUtqGh9+/1w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.5': + resolution: {integrity: sha512-2fST8lILgl7cKbme/1KDdPCmbXbG+gqoV3bHp19L0ypX/3akYMBVdOunPleRCwonoLnXOZ/0F+Mt/v8POFmfcQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.5': + resolution: {integrity: sha512-cpIxQCP9J+EVad0a6LO1kY3ZGODlk80VlI+2I96B8xMcdHZ4pLVhfQ49JFpYqjPF91FFkQWftf57YlDcTiw9yQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + resolution: {integrity: sha512-r9fGh3eFs3e/udWh5ZjXQtxiYK/xoFxQaYR/cELxac/Udkl5Th+IsFm0CX3Kl9hmUH/we7EoMpjJgeQNnE0+IA==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + resolution: {integrity: sha512-xdvFdp7OM6KLJviJT2g/YuRSUjnZgGHk4RNgwIbN7X6cPugOucV60DdHXWzsBVCUdrGb6qSXnJQrrAKMmQuj3Q==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.5': + resolution: {integrity: sha512-rRqILAndyzHzP7T9NFQrq+4HFWNhqkqkKur7eiBpfLmz01PO0JKx5Vchu3YllE4YXI/Ftgq/szrDWg5GJ0mI8g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.5': + resolution: {integrity: sha512-Gf4X3qVMucayUvux6aXXPgXovocSFUC0rrffDuPI/S2nHhNMhjcZxsrAFYCOF350PRreW1XwzFj3CT/3bKsWCw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.5': + resolution: {integrity: sha512-+s5qA0TNM0qm8PK/a5gt/1Hpx+NV08uSuCncvhziIlQzT6AEV2fnUQo7eBtFTFO0nA9scauvoR2HusfXmQnO4w==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.5': + resolution: {integrity: sha512-ybb6QvWwWJCbBWqERpc8K3pYVGIrXlG8MEQ8IIuJY6Y9KdHQxoFoNyfkAOtKn1VHu3KuLidXvwrvGR1mEjeWCw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + resolution: {integrity: sha512-nZb1DtnOyhCmYvsC8A2CwOkopVg+IS1+fPUa7rMOAXtNw5+lLCLLPqd6XAiNrGtoQKsbvIBOwsHnBH/3wnb4HQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.5': + resolution: {integrity: sha512-yMbj63Sp89ryrXLWyz+sy+fYD2HpOnMCLGbe4Oa1smclFSUukdtD/BgdiHaAetJNb74URD8U4hM+qG5KVzMEkg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + resolution: {integrity: sha512-mhoan3OJw2kYV/e1jtIdmvUZgyBFeA6zGWsOswmR0Tg19TQbowZuR+JMLID6spbbBN7Zee2ejrgmy3+FxGrIdA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.5': + resolution: {integrity: sha512-5ZTLmjWbb1VZdjuyhe83K/8QO0/h11midQCBP+X5OYn32ra7eOBoM0ZqtaY4nkgNsYgmdVhMYPoyVPTjUpHf3w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.5': + resolution: {integrity: sha512-m53kG+br6PGxOTmgBEM2DHSDs9RVjsyEbUwjJPJGTFm1grWOG8EKJggDCTb60unD4Tjby8fi7/m9XfkEWasVWg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.5': + resolution: {integrity: sha512-6RHPJR1g/uvdYU8uXBnfq3nlqyZCP82Fr6NHgfGoaIeSh0YEqnX/x6uA9MmJJbnSH7swqX4F+CkGdUF+6doiQA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.5': + resolution: {integrity: sha512-xs+OXQtEXgpXT0DmA5+U3qnRZHdCST/5HRQxS8wSPZTUZN/EMWeHuSIod32LQklTBZBV9DyfncKBQ8n5V3eFdw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.5': + resolution: {integrity: sha512-e7hD+sl3s+mcLQDZ8pbudBVsdG6r5yN4w3LqG2TJ8sQHDpblWj5lrJs/3m01Cvlxbt4x13zu5thLjgypgtkYzw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.5': + resolution: {integrity: sha512-GiyJaCf+WpMub/17aPcKk27QMl5W6f+KhdPTjlFOn5akH5Wa/DCM9Stdx5cDfmasyKB08MqpVQ1uJE2RkkpbXg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.5': + resolution: {integrity: sha512-+OQ8U2DdoEfXl8T4Fb18AjmEwbXMerKDKCL8yCPAYhKCEEKoul7rkbeGCBFCbAlaGaa7pmtRTpkAJM2LE/i5FA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.5': + resolution: {integrity: sha512-KanvAZrPKbDBFwrgiU9yEVpQoox9QPV1WZOXX7HudJQY+eSlu82CtWxDU8WtuRRvtN5EGkLczkd6Y6DTcvm9wA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.5': + resolution: {integrity: sha512-1aC3UEWTtRl3RK3VpDJ/Tqk1XI4SLTmXIthAq6wRWo8XiSXJNd+VprJM4/1P4+i6HIaFEFlVi9sTTziniD2tOQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.5': + resolution: {integrity: sha512-/gDJaRs4gl0NPIwqCz+6PkpmhhjRAD2j6P4rSNHBzUkO3naEx2mIU0pRle1vUNRQ7mE/+8OOeXLTv/J56FKiQg==} + cpu: [x64] + os: [win32] + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@vitejs/plugin-vue@5.2.4': + resolution: {integrity: sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==} + engines: {node: ^18.0.0 || >=20.0.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.41': + resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==} + + '@vue/compiler-dom@3.5.41': + resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==} + + '@vue/compiler-sfc@3.5.41': + resolution: {integrity: sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==} + + '@vue/compiler-ssr@3.5.41': + resolution: {integrity: sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/reactivity@3.5.41': + resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==} + + '@vue/runtime-core@3.5.41': + resolution: {integrity: sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==} + + '@vue/runtime-dom@3.5.41': + resolution: {integrity: sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==} + + '@vue/server-renderer@3.5.41': + resolution: {integrity: sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==} + + '@vue/shared@3.5.41': + resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + address@1.2.2: + resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} + engines: {node: '>= 10.0.0'} + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@3.5.3: + resolution: {integrity: sha512-yqXL+k5rr8+ZRpOAntkaaRgWgE5o8ESAj5DyRmVTCSoZxXmqemb9Dd7T4i5UzwuERdLAJUy6XzR9zFVuf0kzkw==} + engines: {node: '>= 4.0.0'} + + ali-oss@6.23.0: + resolution: {integrity: sha512-FipRmyd16Pr/tEey/YaaQ/24Pc3HEpLM9S1DRakEuXlSLXNIJnu1oJtHM53eVYpvW3dXapSjrip3xylZUTIZVQ==} + engines: {node: '>=8'} + + amp-message@0.1.2: + resolution: {integrity: sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg==} + + amp@0.3.1: + resolution: {integrity: sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansis@4.0.0-node10: + resolution: {integrity: sha512-BRrU0Bo1X9dFGw6KgGz6hWrqQuOlVEDOzkb0QSLZY9sXHqA7pNj7yHPVJRz7y/rj4EOJ3d/D5uxH+ee9leYgsg==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + async@2.6.4: + resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.5.4: + resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + axios@1.19.0: + resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.11.19: + resolution: {integrity: sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==} + engines: {node: '>=6.0.0'} + hasBin: true + + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} + engines: {node: '>=10.0.0'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bodec@0.1.0: + resolution: {integrity: sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ==} + + body-parser@1.20.6: + resolution: {integrity: sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bowser@1.9.4: + resolution: {integrity: sha512-9IdMmj2KjigRq6oWhmwv1W36pDuA4STQZ8q6YO9um+x07xgYNCD3Oou+WP/3L1HNz7iqythGet3/p4wvc8AAwQ==} + + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.8: + resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + builtin-status-codes@3.0.0: + resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + + charm@0.1.2: + resolution: {integrity: sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==} + + chart.js@4.5.1: + resolution: {integrity: sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==} + engines: {pnpm: '>=8'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + cli-tableau@2.0.1: + resolution: {integrity: sha512-he+WTicka9cl0Fg/y+YyxcN6/bfQ/1O3QmgxRXDhABKqLzvoOSM4fMzp39uMyLBulAFuywD2N7UaoQE7WaADxQ==} + engines: {node: '>=8.10.0'} + + cluster-key-slot@1.1.1: + resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} + engines: {node: '>=0.10.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.15.1: + resolution: {integrity: sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + copy-to@2.0.1: + resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + croner@4.1.97: + resolution: {integrity: sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + culvert@0.1.2: + resolution: {integrity: sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg==} + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + dateformat@2.2.0: + resolution: {integrity: sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==} + + dayjs@1.11.15: + resolution: {integrity: sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==} + + dayjs@1.8.36: + resolution: {integrity: sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + default-user-agent@1.0.0: + resolution: {integrity: sha512-bDF7bg6OSNcSwFWPu4zYKpVkJZQYVrAANMYB8bc9Szem1D0yKdm4sa/rOCs2aC9+2GMqQ7KnwtZRvDhmLF0dXw==} + engines: {node: '>= 0.10.0'} + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + digest-header@1.1.0: + resolution: {integrity: sha512-glXVh42vz40yZb9Cq2oMOt70FIoWiv+vxNvdKdU8CwjLad25qHM3trLxhl9bVjdr6WaslIXhWpn0NO8T/67Qjg==} + engines: {node: '>= 8.0.0'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.413: + resolution: {integrity: sha512-F1XPKvt7HVfly5WND90ec16nFsdr4g5x/cVUP3EqjeyXynupabGDqpMa84wwvuYGDnldXLBz6DLXyZXWO9TPvw==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + end-or-error@1.0.1: + resolution: {integrity: sha512-OclLMSug+k2A0JKuf494im25ANRBVW8qsjmwbgX7lQ8P82H21PQ1PWkoYwb9y5yMBS69BPlwtzdIFClo3+7kOQ==} + engines: {node: '>= 0.11.14'} + + enquirer@2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter2@5.0.1: + resolution: {integrity: sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==} + + eventemitter2@6.4.9: + resolution: {integrity: sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==} + + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} + engines: {node: '>= 0.10.0'} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extrareqp2@1.0.0: + resolution: {integrity: sha512-Gum0g1QYb6wpPJCVypWP3bbIuaibcFiJcpuPM10YSXp/tzqi84x9PJageob+eN4xVRIOto4wjSGNLyMD54D2xA==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fclone@1.0.11: + resolution: {integrity: sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + formstream@1.5.2: + resolution: {integrity: sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-ready@1.0.0: + resolution: {integrity: sha512-mFXCZPJIlcYcth+N8267+mghfYN9h3EhsDa6JSnbA3Wrhh/XFpuowviFcsDeYZtKspQyWyJqfs4O6P8CHeTwzw==} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + git-node-fs@1.0.0: + resolution: {integrity: sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ==} + peerDependencies: + js-git: ^0.7.8 + peerDependenciesMeta: + js-git: + optional: true + + git-sha1@0.1.2: + resolution: {integrity: sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + ioredis@5.11.1: + resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} + engines: {node: '>=12.22.0'} + + ip-address@10.5.0: + resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-class-hotfix@0.0.6: + resolution: {integrity: sha512-0n+pzCC6ICtVr/WXnN2f03TK/3BfXY7me4cjCAqT8TYXEl0+JBRoqBo94JJHXcyDSLUeWbNX8Fvy5g5RJdAstQ==} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-type-of@1.4.0: + resolution: {integrity: sha512-EddYllaovi5ysMLMEN7yzHEKh8A850cZ7pykrY1aNRQGn/CDjRDE9qEWbIdt7xGEVJmjBXzU/fNnC4ABTm8tEQ==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isstream@0.1.2: + resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-base64@2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + + js-git@0.7.8: + resolution: {integrity: sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + jstoxml@2.2.9: + resolution: {integrity: sha512-OYWlK0j+roh+eyaMROlNbS5cd5R25Y+IUpdl7cNdB8HNrkgwQzIS7L9MegxOiWNBj9dQhA/yAxiMwCC5mwNoBw==} + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@1.4.5-lts.2: + resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==} + engines: {node: '>= 6.0.0'} + deprecated: Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version. + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + needle@2.4.0: + resolution: {integrity: sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==} + engines: {node: '>= 4.4.x'} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} + engines: {node: '>= 0.4.0'} + + node-hex@1.0.1: + resolution: {integrity: sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==} + engines: {node: '>=8.0.0'} + + node-releases@2.0.53: + resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} + engines: {node: '>=18'} + + nodemon@3.1.14: + resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} + engines: {node: '>=10'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + os-name@1.0.3: + resolution: {integrity: sha512-f5estLO2KN8vgtTRaILIgEGBoBrMnZ3JQ7W9TMZCnOIGwHe8TRGSpcagnWDo+Dfhd/z08k9Xe75hvciJJ8Qaew==} + engines: {node: '>=0.10.0'} + hasBin: true + + osx-release@1.1.0: + resolution: {integrity: sha512-ixCMMwnVxyHFQLQnINhmIpWqXIfS2YOXchwQrk+OFzmo6nDjQ0E4KXAyyUh0T0MZgV4bUhkRrAbVqlE4yLVq4A==} + engines: {node: '>=0.10.0'} + hasBin: true + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + + pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + pidusage@2.0.21: + resolution: {integrity: sha512-cv3xAQos+pugVX+BfXpHsbyz/dLzX+lr44zNMsYiGxUw+kV5sgQCIcLd1z+0vq+KyC7dJ+/ts2PsfgWfSC3WXA==} + engines: {node: '>=8'} + + pidusage@3.0.2: + resolution: {integrity: sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==} + engines: {node: '>=10'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + platform@1.3.6: + resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} + + pm2-axon-rpc@0.7.1: + resolution: {integrity: sha512-FbLvW60w+vEyvMjP/xom2UPhUN/2bVpdtLfKJeYM3gwzYhoTEEChCOICfFzxkxuoEleOlnpjie+n1nue91bDQw==} + engines: {node: '>=5'} + + pm2-axon@4.0.1: + resolution: {integrity: sha512-kES/PeSLS8orT8dR5jMlNl+Yu4Ty3nbvZRmaAtROuVm9nYYGiaoXqqKQqQYzWQzMYWUKHMQTvBlirjE5GIIxqg==} + engines: {node: '>=5'} + + pm2-deploy@1.0.2: + resolution: {integrity: sha512-YJx6RXKrVrWaphEYf++EdOOx9EH18vM8RSZN/P1Y+NokTKqYAca/ejXwVLyiEpNju4HPZEk3Y2uZouwMqUlcgg==} + engines: {node: '>=4.0.0'} + + pm2-multimeter@0.1.2: + resolution: {integrity: sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA==} + + pm2-sysmonit@1.2.8: + resolution: {integrity: sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA==} + + pm2@6.0.14: + resolution: {integrity: sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA==} + engines: {node: '>=16.0.0'} + hasBin: true + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.4: + resolution: {integrity: sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + promptly@2.2.0: + resolution: {integrity: sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + proxy-agent@6.4.0: + resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + read-cache@1.0.2: + resolution: {integrity: sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==} + + read@1.0.7: + resolution: {integrity: sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==} + engines: {node: '>=0.8'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + redis-errors@1.2.0: + resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} + engines: {node: '>=4'} + + redis-parser@3.0.0: + resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} + engines: {node: '>=4'} + + require-in-the-middle@5.2.0: + resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} + engines: {node: '>=6'} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.62.5: + resolution: {integrity: sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + run-series@1.1.9: + resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.6.1: + resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} + engines: {node: '>=11.0.0'} + + sdk-base@2.0.1: + resolution: {integrity: sha512-eeG26wRwhtwYuKGCDM3LixCaxY27Pa/5lK4rLKhQa7HBjJ3U3Y+f81MMZQRsDw/8SC2Dao/83yJTXJ8aULuN8Q==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.1.2: + resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} + + standard-as-callback@2.1.0: + resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stream-http@2.8.2: + resolution: {integrity: sha512-QllfrBhqF1DPcz46WxKTs6Mz1Bpc+8Qm6vbqOpVav5odAXwbyzwnEczoWqtxrsmlO+cJqtPrp/8gWKWjaKLLlA==} + + stream-wormhole@1.1.0: + resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==} + engines: {node: '>=4.0.0'} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + systeminformation@5.33.2: + resolution: {integrity: sha512-T0w9cJPXe0AVMOukHTBNMrqU9mf3LdRR6dxLdnNbM9LdKEcH5yWT5FEN7ljvgh4cjliLy7pICtViQtVeYqaZxg==} + engines: {node: '>=10.0.0'} + os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] + hasBin: true + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tiktoken@1.0.22: + resolution: {integrity: sha512-PKvy1rVF1RibfF3JlXBSP0Jrcw2uq3yXdgcEXtKTYn3QJ/cBRBHDnrJ5jHky+MENZ6DIPwNUGWpkVx+7joCpNA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + to-arraybuffer@1.0.1: + resolution: {integrity: sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@1.9.3: + resolution: {integrity: sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tv4@1.3.0: + resolution: {integrity: sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==} + engines: {node: '>= 0.8.0'} + + tx2@1.0.5: + resolution: {integrity: sha512-sJ24w0y03Md/bxzK4FU8J8JveYYUbSs2FViLJ2D/8bytSiyPRbuE3DyL/9UKYXTZlV3yXq0L8GLlhobTnekCVg==} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + + unescape@1.0.1: + resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==} + engines: {node: '>=0.10.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.3.1: + resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + urllib@2.44.1: + resolution: {integrity: sha512-vreOVvFizoiIz5NK9IYMgUknkriHHBVccn2VFfJhgKz6O2qwm0SgjFk4OpXFRDXpdrTx8EzM1DB0/pejrqXwPA==} + engines: {node: '>= 0.10.0'} + peerDependencies: + proxy-agent: ^5.0.0 + peerDependenciesMeta: + proxy-agent: + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utility@1.18.0: + resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==} + engines: {node: '>= 0.12.0'} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vizion@2.2.1: + resolution: {integrity: sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww==} + engines: {node: '>=4.0'} + + vue-chartjs@5.3.4: + resolution: {integrity: sha512-x3Fqob8RQvrTdssfi9ecsCzEkFOd8JPmNwSkSQzdfKj/uBsRJs/Y88cZcZIEcPsTVfMGwMo4MOoihoDG2DoE/g==} + peerDependencies: + chart.js: ^4.1.1 + vue: ^3.0.0-0 || ^2.7.0 + + vue-i18n@11.4.9: + resolution: {integrity: sha512-SJoEYcmt+g8IW1Ro9zuF6GyYEi/2t4kYlg0y7bUPKAyciR4Zhg6f+RTxgEZ9H2o9LrxJdcr50OH2OkGweRzRiA==} + engines: {node: '>= 22'} + peerDependencies: + vue: ^3.0.0 + + vue-router@4.6.4: + resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} + peerDependencies: + vue: ^3.5.0 + + vue@3.5.41: + resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + win-release@1.1.1: + resolution: {integrity: sha512-iCRnKVvGxOQdsKhcQId2PXV1vV3J/sDPXKA4Oe9+Eti2nb2ESEsYHRYls/UjoUW3bIc5ZDO8dTH50A/5iVN+bw==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + +dependencies: + express: + specifier: ^4.21.2 + version: 4.22.2 + axios: + specifier: ^1.11.0 + version: 1.19.0 + dotenv: + specifier: ^16.4.7 + version: 16.6.1 + cors: + specifier: ^2.8.5 + version: 2.8.6 + body-parser: + specifier: ^1.20.3 + version: 1.20.6 + form-data: + specifier: ^4.0.2 + version: 4.0.6 + https-proxy-agent: + specifier: ^7.0.6 + version: 7.0.6 + ioredis: + specifier: ^5.6.1 + version: 5.11.1 + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 + mime-types: + specifier: ^3.0.1 + version: 3.0.1 + multer: + specifier: ^1.4.5-lts.1 + version: 1.4.5-lts.2 + tiktoken: + specifier: ^1.0.21 + version: 1.0.21 + ali-oss: + specifier: ^6.22.0 + version: 6.23.0 + pm2: + specifier: ^6.0.8 + version: 6.0.14 + nodemon: + specifier: ^3.1.7 + version: 3.1.7 + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@intlify/core-base@11.4.9': + dependencies: + '@intlify/devtools-types': 11.4.9 + '@intlify/message-compiler': 11.4.9 + '@intlify/shared': 11.4.9 + + '@intlify/devtools-types@11.4.9': + dependencies: + '@intlify/core-base': 11.4.9 + '@intlify/shared': 11.4.9 + + '@intlify/message-compiler@11.4.9': + dependencies: + '@intlify/shared': 11.4.9 + source-map-js: 1.2.1 + + '@intlify/shared@11.4.9': {} + + '@ioredis/commands@1.10.0': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@kurkle/color@0.3.4': {} + + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@pm2/agent@2.1.1(supports-color@5.5.0)': + dependencies: + async: 3.2.6 + chalk: 3.0.0 + dayjs: 1.8.36 + debug: 4.3.7(supports-color@5.5.0) + eventemitter2: 5.0.1 + fast-json-patch: 3.1.1 + fclone: 1.0.11 + pm2-axon: 4.0.1(supports-color@5.5.0) + pm2-axon-rpc: 0.7.1(supports-color@5.5.0) + proxy-agent: 6.4.0(supports-color@5.5.0) + semver: 7.5.4 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@pm2/blessed@0.1.81': {} + + '@pm2/io@6.1.0(supports-color@5.5.0)': + dependencies: + async: 2.6.4 + debug: 4.3.7(supports-color@5.5.0) + eventemitter2: 6.4.9 + require-in-the-middle: 5.2.0(supports-color@5.5.0) + semver: 7.5.4 + shimmer: 1.2.1 + signal-exit: 3.0.7 + tslib: 1.9.3 + transitivePeerDependencies: + - supports-color + + '@pm2/js-api@0.8.1(supports-color@5.5.0)': + dependencies: + async: 2.6.4 + debug: 4.3.7(supports-color@5.5.0) + eventemitter2: 6.4.9 + extrareqp2: 1.0.0(debug@4.3.7(supports-color@5.5.0)) + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@pm2/pm2-version-check@1.0.4(supports-color@5.5.0)': + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + '@rollup/rollup-android-arm-eabi@4.62.5': + optional: true + + '@rollup/rollup-android-arm64@4.62.5': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.5': + optional: true + + '@rollup/rollup-darwin-x64@4.62.5': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.5': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.5': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.5': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.5': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.5': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.5': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.5': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.5': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.5': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.5': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.5': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.5': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.5': + optional: true + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@types/estree@1.0.9': {} + + '@vitejs/plugin-vue@5.2.4(vite@5.4.21)(vue@3.5.41)': + dependencies: + vite: 5.4.21 + vue: 3.5.41 + + '@vue/compiler-core@3.5.41': + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.41 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.41': + dependencies: + '@vue/compiler-core': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/compiler-sfc@3.5.41': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.41 + '@vue/compiler-dom': 3.5.41 + '@vue/compiler-ssr': 3.5.41 + '@vue/shared': 3.5.41 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.26 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.41': + dependencies: + '@vue/compiler-dom': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/devtools-api@6.6.4': {} + + '@vue/reactivity@3.5.41': + dependencies: + '@vue/shared': 3.5.41 + + '@vue/runtime-core@3.5.41': + dependencies: + '@vue/reactivity': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/runtime-dom@3.5.41': + dependencies: + '@vue/reactivity': 3.5.41 + '@vue/runtime-core': 3.5.41 + '@vue/shared': 3.5.41 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.41': + dependencies: + '@vue/compiler-ssr': 3.5.41 + '@vue/runtime-dom': 3.5.41 + '@vue/shared': 3.5.41 + + '@vue/shared@3.5.41': {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + address@1.2.2: {} + + agent-base@6.0.2(supports-color@5.5.0): + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + agent-base@6.0.2(supports-color@7.2.0): + dependencies: + debug: 4.4.3(supports-color@7.2.0) + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + agentkeepalive@3.5.3: + dependencies: + humanize-ms: 1.2.1 + + ali-oss@6.23.0(supports-color@5.5.0): + dependencies: + address: 1.2.2 + agentkeepalive: 3.5.3 + bowser: 1.9.4 + copy-to: 2.0.1 + dateformat: 2.2.0 + debug: 4.4.3(supports-color@5.5.0) + destroy: 1.2.0 + end-or-error: 1.0.1 + get-ready: 1.0.0 + humanize-ms: 1.2.1 + is-type-of: 1.4.0 + js-base64: 2.6.4 + jstoxml: 2.2.9 + lodash: 4.18.1 + merge-descriptors: 1.0.3 + mime: 2.6.0 + platform: 1.3.6 + pump: 3.0.4 + qs: 6.15.3 + sdk-base: 2.0.1 + stream-http: 2.8.2 + stream-wormhole: 1.1.0 + urllib: 2.44.1 + utility: 1.18.0 + xml2js: 0.6.2 + transitivePeerDependencies: + - proxy-agent + - supports-color + + amp-message@0.1.2: + dependencies: + amp: 0.3.1 + + amp@0.3.1: {} + + ansi-colors@4.1.3: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansis@4.0.0-node10: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + + append-field@1.0.0: {} + + arg@5.0.2: {} + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + async@2.6.4: + dependencies: + lodash: 4.18.1 + + async@3.2.6: {} + + asynckit@0.4.0: {} + + autoprefixer@10.5.4(postcss@8.5.26): + dependencies: + browserslist: 4.28.8 + caniuse-lite: 1.0.30001810 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.26 + postcss-value-parser: 4.2.0 + + axios@1.19.0(debug@4.3.7(supports-color@5.5.0))(supports-color@5.5.0): + dependencies: + follow-redirects: 1.16.0(debug@4.3.7(supports-color@5.5.0)) + form-data: 4.0.6 + https-proxy-agent: 5.0.1(supports-color@5.5.0) + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + axios@1.19.0(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0): + dependencies: + follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0)) + form-data: 4.0.6 + https-proxy-agent: 5.0.1(supports-color@7.2.0) + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.11.19: {} + + basic-ftp@5.3.1: {} + + binary-extensions@2.3.0: {} + + bodec@0.1.0: {} + + body-parser@1.20.6(supports-color@5.5.0): + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9(supports-color@5.5.0) + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bowser@1.9.4: {} + + brace-expansion@5.0.9: + dependencies: + balanced-match: 4.0.4 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.8: + dependencies: + baseline-browser-mapping: 2.11.19 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.413 + node-releases: 2.0.53 + update-browserslist-db: 1.3.1(browserslist@4.28.8) + + buffer-from@1.1.2: {} + + builtin-status-codes@3.0.0: {} + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001810: {} + + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + charm@0.1.2: {} + + chart.js@4.5.1: + dependencies: + '@kurkle/color': 0.3.4 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + cli-tableau@2.0.1: + dependencies: + chalk: 3.0.0 + + cluster-key-slot@1.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.15.1: {} + + commander@4.1.1: {} + + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + copy-to@2.0.1: {} + + core-util-is@1.0.3: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + croner@4.1.97: {} + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + culvert@0.1.2: {} + + data-uri-to-buffer@6.0.2: {} + + dateformat@2.2.0: {} + + dayjs@1.11.15: {} + + dayjs@1.8.36: {} + + debug@2.6.9(supports-color@5.5.0): + dependencies: + ms: 2.0.0 + optionalDependencies: + supports-color: 5.5.0 + + debug@3.2.7(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + debug@4.3.7(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + debug@4.4.3(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + debug@4.4.3(supports-color@7.2.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 + + default-user-agent@1.0.0: + dependencies: + os-name: 1.0.3 + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + delayed-stream@1.0.0: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + didyoumean@1.2.2: {} + + digest-header@1.1.0: {} + + dlv@1.1.3: {} + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.413: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + end-or-error@1.0.1: {} + + enquirer@2.3.6: + dependencies: + ansi-colors: 4.1.3 + + entities@7.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + estree-walker@2.0.2: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter2@5.0.1: {} + + eventemitter2@6.4.9: {} + + express@4.22.2(supports-color@5.5.0): + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.6(supports-color@5.5.0) + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9(supports-color@5.5.0) + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2(supports-color@5.5.0) + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.13 + proxy-addr: 2.0.7 + qs: 6.15.3 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2(supports-color@5.5.0) + serve-static: 1.16.3(supports-color@5.5.0) + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extrareqp2@1.0.0(debug@4.3.7(supports-color@5.5.0)): + dependencies: + follow-redirects: 1.16.0(debug@4.3.7(supports-color@5.5.0)) + transitivePeerDependencies: + - debug + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-patch@3.1.1: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fclone@1.0.11: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.2(supports-color@5.5.0): + dependencies: + debug: 2.6.9(supports-color@5.5.0) + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + follow-redirects@1.16.0(debug@4.3.7(supports-color@5.5.0)): + optionalDependencies: + debug: 4.3.7(supports-color@5.5.0) + + follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)): + optionalDependencies: + debug: 4.4.3(supports-color@7.2.0) + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + formstream@1.5.2: + dependencies: + destroy: 1.2.0 + mime: 2.6.0 + node-hex: 1.0.1 + pause-stream: 0.0.11 + + forwarded@0.2.0: {} + + fraction.js@5.3.4: {} + + fresh@0.5.2: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-ready@1.0.0: {} + + get-uri@6.0.5(supports-color@5.5.0): + dependencies: + basic-ftp: 5.3.1 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + git-node-fs@1.0.0(js-git@0.7.8): + optionalDependencies: + js-git: 0.7.8 + + git-sha1@0.1.2: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + gopd@1.2.0: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2(supports-color@5.5.0): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1(supports-color@5.5.0): + dependencies: + agent-base: 6.0.2(supports-color@5.5.0) + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@5.0.1(supports-color@7.2.0): + dependencies: + agent-base: 6.0.2(supports-color@7.2.0) + debug: 4.4.3(supports-color@7.2.0) + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6(supports-color@5.5.0): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore-by-default@1.0.1: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ioredis@5.11.1(supports-color@5.5.0): + dependencies: + '@ioredis/commands': 1.10.0 + cluster-key-slot: 1.1.1 + debug: 4.4.3(supports-color@5.5.0) + denque: 2.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + ip-address@10.5.0: {} + + ipaddr.js@1.9.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-class-hotfix@0.0.6: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-extendable@0.1.1: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-type-of@1.4.0: + dependencies: + core-util-is: 1.0.3 + is-class-hotfix: 0.0.6 + isstream: 0.1.2 + + isarray@1.0.0: {} + + isstream@0.1.2: {} + + jiti@1.21.7: {} + + js-base64@2.6.4: {} + + js-git@0.7.8: + dependencies: + bodec: 0.1.0 + culvert: 0.1.2 + git-sha1: 0.1.2 + pako: 0.2.9 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-stringify-safe@5.0.1: + optional: true + + jstoxml@2.2.9: {} + + jwt-decode@4.0.0: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lodash@4.18.1: {} + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@7.18.3: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + + mime@2.6.0: {} + + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.9 + + minimist@1.2.8: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + module-details-from-path@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multer@1.4.5-lts.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + mute-stream@0.0.8: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.18: {} + + needle@2.4.0(supports-color@5.5.0): + dependencies: + debug: 3.2.7(supports-color@5.5.0) + iconv-lite: 0.4.24 + sax: 1.6.1 + transitivePeerDependencies: + - supports-color + + negotiator@0.6.3: {} + + netmask@2.1.1: {} + + node-hex@1.0.1: {} + + node-releases@2.0.53: {} + + nodemon@3.1.14: + dependencies: + chokidar: 3.6.0 + debug: 4.4.3(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 10.2.6 + pstree.remy: 1.1.8 + semver: 7.8.5 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + normalize-path@3.0.0: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + os-name@1.0.3: + dependencies: + osx-release: 1.1.0 + win-release: 1.1.1 + + osx-release@1.1.0: + dependencies: + minimist: 1.2.8 + + pac-proxy-agent@7.2.0(supports-color@5.5.0): + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + get-uri: 6.0.5(supports-color@5.5.0) + http-proxy-agent: 7.0.2(supports-color@5.5.0) + https-proxy-agent: 7.0.6(supports-color@5.5.0) + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.1.1 + + pako@0.2.9: {} + + parseurl@1.3.3: {} + + path-parse@1.0.7: {} + + path-to-regexp@0.1.13: {} + + pause-stream@0.0.11: + dependencies: + through: 2.3.8 + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + picomatch@4.0.7: {} + + pidusage@2.0.21: + dependencies: + safe-buffer: 5.2.1 + optional: true + + pidusage@3.0.2: + dependencies: + safe-buffer: 5.2.1 + + pirates@4.0.7: {} + + platform@1.3.6: {} + + pm2-axon-rpc@0.7.1(supports-color@5.5.0): + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + pm2-axon@4.0.1(supports-color@5.5.0): + dependencies: + amp: 0.3.1 + amp-message: 0.1.2 + debug: 4.4.3(supports-color@5.5.0) + escape-string-regexp: 4.0.0 + transitivePeerDependencies: + - supports-color + + pm2-deploy@1.0.2: + dependencies: + run-series: 1.1.9 + tv4: 1.3.0 + + pm2-multimeter@0.1.2: + dependencies: + charm: 0.1.2 + + pm2-sysmonit@1.2.8(supports-color@5.5.0): + dependencies: + async: 3.2.6 + debug: 4.4.3(supports-color@5.5.0) + pidusage: 2.0.21 + systeminformation: 5.33.2 + tx2: 1.0.5 + transitivePeerDependencies: + - supports-color + optional: true + + pm2@6.0.14(supports-color@5.5.0): + dependencies: + '@pm2/agent': 2.1.1(supports-color@5.5.0) + '@pm2/blessed': 0.1.81 + '@pm2/io': 6.1.0(supports-color@5.5.0) + '@pm2/js-api': 0.8.1(supports-color@5.5.0) + '@pm2/pm2-version-check': 1.0.4(supports-color@5.5.0) + ansis: 4.0.0-node10 + async: 3.2.6 + chokidar: 3.6.0 + cli-tableau: 2.0.1 + commander: 2.15.1 + croner: 4.1.97 + dayjs: 1.11.15 + debug: 4.4.3(supports-color@5.5.0) + enquirer: 2.3.6 + eventemitter2: 5.0.1 + fclone: 1.0.11 + js-yaml: 4.1.1 + mkdirp: 1.0.4 + needle: 2.4.0(supports-color@5.5.0) + pidusage: 3.0.2 + pm2-axon: 4.0.1(supports-color@5.5.0) + pm2-axon-rpc: 0.7.1(supports-color@5.5.0) + pm2-deploy: 1.0.2 + pm2-multimeter: 0.1.2 + promptly: 2.2.0 + semver: 7.7.2 + source-map-support: 0.5.21 + sprintf-js: 1.1.2 + vizion: 2.2.1 + optionalDependencies: + pm2-sysmonit: 1.2.8(supports-color@5.5.0) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + postcss-import@15.1.0(postcss@8.5.26): + dependencies: + postcss: 8.5.26 + postcss-value-parser: 4.2.0 + read-cache: 1.0.2 + resolve: 1.22.12 + + postcss-js@4.1.0(postcss@8.5.26): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.26 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.26): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.26 + + postcss-nested@6.2.0(postcss@8.5.26): + dependencies: + postcss: 8.5.26 + postcss-selector-parser: 6.1.4 + + postcss-selector-parser@6.1.4: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + process-nextick-args@2.0.1: {} + + promptly@2.2.0: + dependencies: + read: 1.0.7 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + proxy-agent@6.4.0(supports-color@5.5.0): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + http-proxy-agent: 7.0.2(supports-color@5.5.0) + https-proxy-agent: 7.0.6(supports-color@5.5.0) + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0(supports-color@5.5.0) + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + proxy-from-env@2.1.0: {} + + pstree.remy@1.1.8: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + read-cache@1.0.2: {} + + read@1.0.7: + dependencies: + mute-stream: 0.0.8 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + + redis-errors@1.2.0: {} + + redis-parser@3.0.0: + dependencies: + redis-errors: 1.2.0 + + require-in-the-middle@5.2.0(supports-color@5.5.0): + dependencies: + debug: 4.4.3(supports-color@5.5.0) + module-details-from-path: 1.0.4 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.62.5: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.5 + '@rollup/rollup-android-arm64': 4.62.5 + '@rollup/rollup-darwin-arm64': 4.62.5 + '@rollup/rollup-darwin-x64': 4.62.5 + '@rollup/rollup-freebsd-arm64': 4.62.5 + '@rollup/rollup-freebsd-x64': 4.62.5 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.5 + '@rollup/rollup-linux-arm-musleabihf': 4.62.5 + '@rollup/rollup-linux-arm64-gnu': 4.62.5 + '@rollup/rollup-linux-arm64-musl': 4.62.5 + '@rollup/rollup-linux-loong64-gnu': 4.62.5 + '@rollup/rollup-linux-loong64-musl': 4.62.5 + '@rollup/rollup-linux-ppc64-gnu': 4.62.5 + '@rollup/rollup-linux-ppc64-musl': 4.62.5 + '@rollup/rollup-linux-riscv64-gnu': 4.62.5 + '@rollup/rollup-linux-riscv64-musl': 4.62.5 + '@rollup/rollup-linux-s390x-gnu': 4.62.5 + '@rollup/rollup-linux-x64-gnu': 4.62.5 + '@rollup/rollup-linux-x64-musl': 4.62.5 + '@rollup/rollup-openbsd-x64': 4.62.5 + '@rollup/rollup-openharmony-arm64': 4.62.5 + '@rollup/rollup-win32-arm64-msvc': 4.62.5 + '@rollup/rollup-win32-ia32-msvc': 4.62.5 + '@rollup/rollup-win32-x64-gnu': 4.62.5 + '@rollup/rollup-win32-x64-msvc': 4.62.5 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + run-series@1.1.9: {} + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sax@1.6.1: {} + + sdk-base@2.0.1: + dependencies: + get-ready: 1.0.0 + + semver@5.7.2: {} + + semver@7.5.4: + dependencies: + lru-cache: 6.0.0 + + semver@7.7.2: {} + + semver@7.8.5: {} + + send@0.19.2(supports-color@5.5.0): + dependencies: + debug: 2.6.9(supports-color@5.5.0) + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3(supports-color@5.5.0): + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shimmer@1.2.1: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.8.5 + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5(supports-color@5.5.0): + dependencies: + agent-base: 7.1.4 + debug: 4.4.3(supports-color@5.5.0) + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.5.0 + smart-buffer: 4.2.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.1.2: {} + + standard-as-callback@2.1.0: {} + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + stream-http@2.8.2: + dependencies: + builtin-status-codes: 3.0.0 + inherits: 2.0.4 + readable-stream: 2.3.8 + to-arraybuffer: 1.0.1 + xtend: 4.0.2 + + stream-wormhole@1.1.0: {} + + streamsearch@1.1.0: {} + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.17 + ts-interface-checker: 0.1.13 + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + systeminformation@5.33.2: + optional: true + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.26 + postcss-import: 15.1.0(postcss@8.5.26) + postcss-js: 4.1.0(postcss@8.5.26) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.26) + postcss-nested: 6.2.0(postcss@8.5.26) + postcss-selector-parser: 6.1.4 + resolve: 1.22.12 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through@2.3.8: {} + + tiktoken@1.0.22: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + to-arraybuffer@1.0.1: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + touch@3.1.1: {} + + ts-interface-checker@0.1.13: {} + + tslib@1.9.3: {} + + tslib@2.8.1: {} + + tv4@1.3.0: {} + + tx2@1.0.5: + dependencies: + json-stringify-safe: 5.0.1 + optional: true + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typedarray@0.0.6: {} + + undefsafe@2.0.5: {} + + unescape@1.0.1: + dependencies: + extend-shallow: 2.0.1 + + unpipe@1.0.0: {} + + update-browserslist-db@1.3.1(browserslist@4.28.8): + dependencies: + browserslist: 4.28.8 + escalade: 3.2.0 + picocolors: 1.1.1 + + urllib@2.44.1: + dependencies: + any-promise: 1.3.0 + content-type: 1.0.5 + default-user-agent: 1.0.0 + digest-header: 1.1.0 + ee-first: 1.1.1 + formstream: 1.5.2 + humanize-ms: 1.2.1 + iconv-lite: 0.6.3 + pump: 3.0.4 + qs: 6.15.3 + statuses: 1.5.0 + utility: 1.18.0 + + util-deprecate@1.0.2: {} + + utility@1.18.0: + dependencies: + copy-to: 2.0.1 + escape-html: 1.0.3 + mkdirp: 0.5.6 + mz: 2.7.0 + unescape: 1.0.1 + + utils-merge@1.0.1: {} + + vary@1.1.2: {} + + vite@5.4.21: + dependencies: + esbuild: 0.21.5 + postcss: 8.5.26 + rollup: 4.62.5 + optionalDependencies: + fsevents: 2.3.3 + + vizion@2.2.1: + dependencies: + async: 2.6.4 + git-node-fs: 1.0.0(js-git@0.7.8) + ini: 1.3.8 + js-git: 0.7.8 + + vue-chartjs@5.3.4(chart.js@4.5.1)(vue@3.5.41): + dependencies: + chart.js: 4.5.1 + vue: 3.5.41 + + vue-i18n@11.4.9(vue@3.5.41): + dependencies: + '@intlify/core-base': 11.4.9 + '@intlify/devtools-types': 11.4.9 + '@intlify/shared': 11.4.9 + '@vue/devtools-api': 6.6.4 + vue: 3.5.41 + + vue-router@4.6.4(vue@3.5.41): + dependencies: + '@vue/devtools-api': 6.6.4 + vue: 3.5.41 + + vue@3.5.41: + dependencies: + '@vue/compiler-dom': 3.5.41 + '@vue/compiler-sfc': 3.5.41 + '@vue/runtime-dom': 3.5.41 + '@vue/server-renderer': 3.5.41 + '@vue/shared': 3.5.41 + + win-release@1.1.1: + dependencies: + semver: 5.7.2 + + wrappy@1.0.2: {} + + ws@7.5.13: {} + + ws@8.21.3: {} + + xml2js@0.6.2: + dependencies: + sax: 1.6.1 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + + xtend@4.0.2: {} + + yallist@4.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..30c46f2d --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +packages: + - '.' + - 'public' +onlyBuiltDependencies: + - esbuild diff --git a/public/package.json b/public/package.json index dfd8c34e..7d303920 100644 --- a/public/package.json +++ b/public/package.json @@ -3,6 +3,7 @@ "private": true, "version": "0.0.0", "type": "module", + "packageManager": "pnpm@11.22.0", "scripts": { "dev": "vite --port 6868", "build": "vite build", diff --git a/public/pnpm-lock.yaml b/public/pnpm-lock.yaml new file mode 100644 index 00000000..c5caa5bd --- /dev/null +++ b/public/pnpm-lock.yaml @@ -0,0 +1,26 @@ +lockfileVersion: '9.0' +settings: + auto-install-peers: true + exclude-links-from-lockfile: false + +dependencies: + axios: + specifier: ^1.8.4 + version: 1.8.4 + chart.js: + specifier: ^4.4.0 + version: 4.4.0 + vue: + specifier: ^3.4.29 + version: 3.4.29 + vue-chartjs: + specifier: ^5.3.0 + version: 5.3.0 + vue-i18n: + specifier: ^11.1.3 + version: 11.1.3 + vue-router: + specifier: ^4.5.0 + version: 4.5.0 + +packages: [] diff --git a/src/config/index.js b/src/config/index.js index eb1ff5e0..f6076168 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -59,6 +59,8 @@ const config = { 6, Math.max(2, parseInt(process.env.AGENT_TURN_MAX_ATTEMPTS, 10) || 3) ), + // 是否启用临时本地聊天模式(chat_mode=local);可被单个请求的 chat_mode 字段覆盖 + enableTempChats: process.env.ENABLE_TEMP_CHATS === 'true', // chat.qwen.ai 的 WAF 会在 JSON 请求体接近 128 KiB 时返回 captcha。 // 提前把 Agent 全量历史外置成文本文档,给协议头和当前回合留出安全余量。 agentContextFileThresholdBytes: Math.max( diff --git a/src/controllers/anthropic.js b/src/controllers/anthropic.js index 0087986c..36f1410c 100644 --- a/src/controllers/anthropic.js +++ b/src/controllers/anthropic.js @@ -642,7 +642,7 @@ const handleAnthropicStream = async (res, ctx, upstream) => { upstreamEventCount = retryResult.eventCount; } } catch (e) { - logger.error('Anthropic 流式重试失败', 'ANTHROPIC', '', e); + logger.error('Anthropic stream retry failed', 'ANTHROPIC', '', e); if (e.publicMessage) throw e; } } @@ -865,7 +865,7 @@ const handleAnthropicNonStream = async (res, ctx, upstream) => { toolErrors = [...parsedRetry.errors, ...nativeToolAccumulator.getErrors()]; } } catch (e) { - logger.error('Anthropic 非流式重试失败', 'ANTHROPIC', '', e); + logger.error('Anthropic non-stream retry failed', 'ANTHROPIC', '', e); if (e.publicMessage) throw e; } } @@ -989,7 +989,7 @@ const handleAnthropicMessages = async (req, res) => { await handleAnthropicNonStream(res, ctx, upstreamResp.response); } } catch (error) { - logger.error('Anthropic Messages 处理错误', 'ANTHROPIC', '', error); + logger.error('Anthropic Messages processing error', 'ANTHROPIC', '', error); if (!res.headersSent) { res.status(500).json({ type: 'error', diff --git a/src/controllers/chat.image.video.js b/src/controllers/chat.image.video.js index 77c58a44..67e445e0 100644 --- a/src/controllers/chat.image.video.js +++ b/src/controllers/chat.image.video.js @@ -111,7 +111,7 @@ const parseUpstreamImageError = (data) => { const errorData = payload.data if (errorData.code === 'RateLimited') { const waitHours = errorData.num - logger.error(`图片/视频生成额度已用尽,需等待约 ${waitHours || '未知'} 小时`, 'CHAT', '', { + logger.error(`Image/video generation quota exhausted, waiting approximately ${waitHours || 'unknown'} hours`, 'CHAT', '', { parsed_error: errorData, raw_response_body: rawPayload }) @@ -123,7 +123,7 @@ const parseUpstreamImageError = (data) => { } } - logger.error('请求上游服务时出现错误', 'CHAT', '', { + logger.error('Error occurred when requesting upstream service', 'CHAT', '', { parsed_error: errorData, raw_response_body: rawPayload }) @@ -749,7 +749,7 @@ const buildInternalMediaItem = (mediaType, mediaURL) => { */ const uploadMultipartMediaFile = async (file, mediaType) => { if (!file?.buffer) { - throw new Error('上传文件内容为空') + throw new Error('Upload file content is empty') } const fallbackExtension = mediaType === 'video' ? 'mp4' : 'png' @@ -758,7 +758,7 @@ const uploadMultipartMediaFile = async (file, mediaType) => { const uploadResult = await uploadFileToQwenOss(file.buffer, originalFilename, uploadAccount ? uploadAccount.token : null, uploadAccount) if (!uploadResult || uploadResult.status !== 200) { - throw new Error('文件上传失败') + throw new Error('File upload failed') } return buildInternalMediaItem(mediaType, uploadResult.file_url) @@ -792,7 +792,7 @@ const normalizeInlineMediaItem = async (value, mediaType) => { const uploadResult = await uploadFileToQwenOss(Buffer.from(base64Content, 'base64'), `upload.${fileExtension}`, uploadAccount ? uploadAccount.token : null, uploadAccount) if (!uploadResult || uploadResult.status !== 200) { - throw new Error('文件上传失败') + throw new Error('File upload failed') } return buildInternalMediaItem(mediaType, uploadResult.file_url) @@ -1055,7 +1055,7 @@ const getChatDetail = async (chatID, token) => { const responseData = await axios.get(`${chatBaseUrl}/api/v2/chats/${chatID}`, requestConfig) return responseData.data || null } catch (error) { - logger.error(`获取聊天详情失败 (${chatID})`, 'CHAT', '', buildAxiosErrorLog(error)) + logger.error(`Chat detail fetch failed (${chatID})`, 'CHAT', '', buildAxiosErrorLog(error)) return null } } @@ -1134,7 +1134,7 @@ const resolveImageResultContentUrl = async (responseData, chatID, token) => { let contentUrl = upstreamContentUrl if (!contentUrl && chatID) { - logger.info(`图片上游未直接返回链接,尝试从聊天详情补取,chat_id=${chatID} responseIDs=${JSON.stringify(responseIDs)}`, 'CHAT') + logger.info(`Image upstream did not directly return link, trying to get from chat details, chat_id=${chatID} responseIDs=${JSON.stringify(responseIDs)}`, 'CHAT') for (let attempt = 1; attempt <= 5; attempt++) { const chatDetail = await getChatDetail(chatID, token) @@ -1149,8 +1149,8 @@ const resolveImageResultContentUrl = async (responseData, chatID, token) => { } if (!contentUrl) { - logger.warn(`图片上游响应未解析出图片链接,responseIDs=${JSON.stringify(responseIDs)} preview=${rawPreview}`, 'CHAT') - throw new Error('上游未返回图片链接') + logger.warn(`Image upstream response did not parse image link, responseIDs=${JSON.stringify(responseIDs)} preview=${rawPreview}`, 'CHAT') + throw new Error('upstream did not return image link') } return contentUrl @@ -1177,7 +1177,7 @@ const resolveVideoResultContentUrl = async (responseStream, token, chatID) => { let resolvedTaskCandidates = [...videoTaskCandidates] if (!resolvedContentUrl && resolvedTaskCandidates.length === 0 && chatID) { - logger.info(`视频上游未直接返回任务信息,尝试从聊天详情补取,chat_id=${chatID} responseIDs=${JSON.stringify(responseIDs)}`, 'CHAT') + logger.info(`Video upstream did not directly return task info, trying to get from chat details, chat_id=${chatID} responseIDs=${JSON.stringify(responseIDs)}`, 'CHAT') for (let attempt = 1; attempt <= 5; attempt++) { const chatDetail = await getChatDetail(chatID, token) @@ -1206,17 +1206,17 @@ const resolveVideoResultContentUrl = async (responseStream, token, chatID) => { } if (resolvedTaskCandidates.length === 0) { - logger.warn(`视频上游响应未解析出任务信息,contentUrl=${resolvedContentUrl || '空'} candidates=${JSON.stringify(resolvedTaskCandidates)} responseIDs=${JSON.stringify(responseIDs)} preview=${rawPreview}`, 'CHAT') - throw new Error('上游未返回视频任务 ID 或视频链接') + logger.warn(`Video upstream response did not parse task info, contentUrl=${resolvedContentUrl || 'empty'} candidates=${JSON.stringify(resolvedTaskCandidates)} responseIDs=${JSON.stringify(responseIDs)} preview=${rawPreview}`, 'CHAT') + throw new Error('upstream did not return video task ID or video link') } - logger.info(`视频任务候选ID: ${JSON.stringify(resolvedTaskCandidates)}`, 'CHAT') + logger.info(`Video task candidate IDs: ${JSON.stringify(resolvedTaskCandidates)}`, 'CHAT') const maxAttempts = 60 const delay = 20 * 1000 for (const taskCandidate of resolvedTaskCandidates) { - logger.info(`开始轮询视频任务ID: ${taskCandidate}`, 'CHAT') + logger.info(`Starting to poll video task ID: ${taskCandidate}`, 'CHAT') for (let i = 0; i < maxAttempts; i++) { const content = await getVideoTaskStatus(taskCandidate, token) @@ -1228,7 +1228,7 @@ const resolveVideoResultContentUrl = async (responseStream, token, chatID) => { } } - logger.error(`视频任务 ${JSON.stringify(resolvedTaskCandidates)} 轮询超时`, 'CHAT') + logger.error(`Video task ${JSON.stringify(resolvedTaskCandidates)} polling timeout`, 'CHAT') throw { status: 504, error: '视频生成超时,请稍后再试' @@ -1269,7 +1269,7 @@ const generateImageVideoResult = async (payload) => { const chatID = await generateChatID(token, model, account, chat_type) if (!chatID) { - throw new Error('生成 chat_id 失败') + throw new Error('Failed to generate chat_id') } reqBody.chat_id = chatID @@ -1364,15 +1364,15 @@ const generateImageVideoResult = async (payload) => { const proxyAgent = getProxyAgent(account) const cookieHeader = buildUpstreamCookieHeader(token) - logger.info('发送图片视频请求', 'CHAT') - logger.info(`选择图片: ${selectedImageList[selectedImageList.length - 1] || '未选择图片,切换生成图/视频模式'}`, 'CHAT') - logger.info(`使用提示: ${reqBody.messages[0].content}`, 'CHAT') + logger.info('Sending image/video request', 'CHAT') + logger.info(`Selected image: ${selectedImageList[selectedImageList.length - 1] || 'no image selected, switching to generate image/video mode'}`, 'CHAT') + logger.info(`Using prompt: ${reqBody.messages[0].content}`, 'CHAT') const newChatType = reqBody.messages[0].chat_type const upstreamStream = newChatType === 't2i' || newChatType === 'image_edit' reqBody.stream = upstreamStream - logger.info(`图片视频流策略: upstream=${upstreamStream} downstream=${payload.stream === true}`, 'CHAT') + logger.info(`Image/video streaming strategy: upstream=${upstreamStream} downstream=${payload.stream === true}`, 'CHAT') const requestConfig = { headers: { @@ -1412,17 +1412,17 @@ const generateImageVideoResult = async (payload) => { const inlineUpstreamError = parseUpstreamImageError(responseData.data) if (attempt < maxUpstreamAttempts && isRetryableUpstreamError(inlineUpstreamError)) { - logger.warn(`图片/视频请求上游返回业务错误包,准备第 ${attempt + 1} 次重试,请求ID: ${inlineUpstreamError.request_id || '未知'}`, 'CHAT') + logger.warn(`Image/video request upstream returned business error, preparing attempt ${attempt + 1} retry, request ID: ${inlineUpstreamError.request_id || 'unknown'}`, 'CHAT') await sleep(800) continue } break } catch (error) { - logger.error('图片/视频请求失败', 'CHAT', '', buildAxiosErrorLog(error)) + logger.error('Image/video request failed', 'CHAT', '', buildAxiosErrorLog(error)) const upstreamError = parseUpstreamImageError(error.response?.data) if (attempt < maxUpstreamAttempts && isRetryableUpstreamError(upstreamError)) { - logger.warn(`图片/视频请求上游返回瞬时内部错误,准备第 ${attempt + 1} 次重试`, 'CHAT') + logger.warn(`Image/video request upstream returned transient internal error, preparing attempt ${attempt + 1} retry`, 'CHAT') await sleep(800) continue } @@ -1451,9 +1451,9 @@ const generateImageVideoResult = async (payload) => { } } - throw new Error('不支持的图片/视频类型') + throw new Error('Unsupported image/video type') } catch (error) { - logger.error('图片/视频主流程异常', 'CHAT', '', buildAxiosErrorLog(error)) + logger.error('Image/video main process error', 'CHAT', '', buildAxiosErrorLog(error)) if (error?.error) { throw error @@ -1502,7 +1502,7 @@ const handleImageVideoCompletion = async (req, res) => { clearInterval(keepAliveTimer) } - logger.error('图片视频资源处理错误', 'CHAT', '', error) + logger.error('Image/video resource processing error', 'CHAT', '', error) if (downstreamStream) { return returnResponse(res, req.body.model, error?.error || error?.message || 'Service error, please try again later', true) @@ -1527,7 +1527,7 @@ const returnResponse = (res, model, content, stream) => { setResponseHeaders(res, stream) } - logger.info(`返回响应: ${content}`, 'CHAT') + logger.info(`Returning response: ${content}`, 'CHAT') if (stream) { const responseID = `chatcmpl-${new Date().getTime()}` @@ -1622,7 +1622,7 @@ const handleOpenAIImagesGeneration = async (req, res) => { data: [imageData] }) } catch (error) { - logger.error('OpenAI 图片生成端点处理失败', 'CHAT', '', error) + logger.error('OpenAI image generation endpoint failed', 'CHAT', '', error) return sendOpenAIErrorResponse(res, error) } } @@ -1671,7 +1671,7 @@ const handleOpenAIImagesEdit = async (req, res) => { data: [imageData] }) } catch (error) { - logger.error('OpenAI 图片编辑端点处理失败', 'CHAT', '', error) + logger.error('OpenAI image edit endpoint failed', 'CHAT', '', error) return sendOpenAIErrorResponse(res, error) } } @@ -1718,7 +1718,7 @@ const handleOpenAIVideoGeneration = async (req, res) => { ] }) } catch (error) { - logger.error('OpenAI 视频生成端点处理失败', 'CHAT', '', error) + logger.error('OpenAI video generation endpoint failed', 'CHAT', '', error) return sendOpenAIErrorResponse(res, error) } } @@ -1762,7 +1762,7 @@ const handleVideoCompletion = async (res, responseStream, token, model, downstre let resolvedTaskCandidates = [...videoTaskCandidates] if (!resolvedContentUrl && resolvedTaskCandidates.length === 0 && chatID) { - logger.info(`视频上游未直接返回任务信息,尝试从聊天详情补取,chat_id=${chatID} responseIDs=${JSON.stringify(responseIDs)}`, 'CHAT') + logger.info(`Video upstream did not directly return task info, trying to get from chat details, chat_id=${chatID} responseIDs=${JSON.stringify(responseIDs)}`, 'CHAT') for (let attempt = 1; attempt <= 5; attempt++) { const chatDetail = await getChatDetail(chatID, token) @@ -1795,17 +1795,17 @@ const handleVideoCompletion = async (res, responseStream, token, model, downstre } if (resolvedTaskCandidates.length === 0) { - logger.warn(`视频上游响应未解析出任务信息,contentUrl=${resolvedContentUrl || '空'} candidates=${JSON.stringify(resolvedTaskCandidates)} responseIDs=${JSON.stringify(responseIDs)} preview=${rawPreview}`, 'CHAT') - throw new Error('上游未返回视频任务 ID 或视频链接') + logger.warn(`Video upstream response did not parse task info, contentUrl=${resolvedContentUrl || 'empty'} candidates=${JSON.stringify(resolvedTaskCandidates)} responseIDs=${JSON.stringify(responseIDs)} preview=${rawPreview}`, 'CHAT') + throw new Error('upstream did not return video task ID or video link') } - logger.info(`视频任务候选ID: ${JSON.stringify(resolvedTaskCandidates)}`, 'CHAT') + logger.info(`Video task candidate IDs: ${JSON.stringify(resolvedTaskCandidates)}`, 'CHAT') const maxAttempts = 60 const delay = 20 * 1000 for (const taskCandidate of resolvedTaskCandidates) { - logger.info(`开始轮询视频任务ID: ${taskCandidate}`, 'CHAT') + logger.info(`Starting to poll video task ID: ${taskCandidate}`, 'CHAT') for (let i = 0; i < maxAttempts; i++) { const content = await getVideoTaskStatus(taskCandidate, token) @@ -1821,7 +1821,7 @@ const handleVideoCompletion = async (res, responseStream, token, model, downstre } } - logger.error(`视频任务 ${JSON.stringify(resolvedTaskCandidates)} 轮询超时`, 'CHAT') + logger.error(`Video task ${JSON.stringify(resolvedTaskCandidates)} polling timeout`, 'CHAT') if (keepAliveTimer) { clearInterval(keepAliveTimer) } @@ -1836,7 +1836,7 @@ const handleVideoCompletion = async (res, responseStream, token, model, downstre clearInterval(keepAliveTimer) } - logger.error('获取视频任务状态失败', 'CHAT', '', error) + logger.error('Failed to get video task status', 'CHAT', '', error) const errorMessage = error.response?.data?.data?.code || error.message || '可能该帐号今日生成次数已用完' @@ -1874,13 +1874,13 @@ const getVideoTaskStatus = async (videoTaskID, token) => { if (response_data.data?.task_status == "success") { const contentUrl = extractResourceUrlFromPayload(response_data.data) - logger.info('获取视频任务状态成功', 'CHAT', contentUrl || response_data.data?.content) + logger.info('Successfully got video task status', 'CHAT', contentUrl || response_data.data?.content) return contentUrl } - logger.info(`获取视频任务 ${videoTaskID} 状态: ${response_data.data?.task_status}`, 'CHAT') + logger.info(`Got video task ${videoTaskID} status: ${response_data.data?.task_status}`, 'CHAT') return null } catch (error) { - logger.error(`查询视频任务状态失败 (${videoTaskID})`, 'CHAT', '', buildAxiosErrorLog(error)) + logger.error(`Failed to query video task status (${videoTaskID})`, 'CHAT', '', buildAxiosErrorLog(error)) return null } } diff --git a/src/controllers/chat.js b/src/controllers/chat.js index 8427f9d1..68bcea95 100644 --- a/src/controllers/chat.js +++ b/src/controllers/chat.js @@ -63,7 +63,7 @@ const setResponseHeaders = (res, stream) => { }) } } catch (e) { - logger.error('处理聊天请求时发生错误', 'CHAT', '', e) + logger.error('Error processing chat request', 'CHAT', '', e) } } @@ -326,7 +326,7 @@ const handleOpenAIAgentStream = async ( options.agent_processing_heartbeat_ms ) } catch (error) { - logger.error('OpenAI Agent 回合处理失败', 'AGENT', '', error) + logger.error('OpenAI Agent turn processing failed', 'AGENT', '', error) writeOpenAIHttpError(res, { status: 502, message: error.publicMessage || '上游 Agent 回合处理失败', @@ -432,7 +432,7 @@ const handleOpenAIAgentNonStream = async ( options.agent_processing_heartbeat_ms ) } catch (error) { - logger.error('OpenAI 非流式 Agent 回合处理失败', 'AGENT', '', error) + logger.error('OpenAI non-stream Agent turn processing failed', 'AGENT', '', error) writeOpenAIHttpError(res, { status: 502, message: error.publicMessage || '上游 Agent 回合处理失败', @@ -789,7 +789,7 @@ const handleStreamResponse = async (res, response, enable_thinking, enable_web_s try { await processSSEPayload(frame.data) } catch (error) { - logger.error('流式数据处理错误', 'CHAT', '', error) + logger.error('Streaming data processing error', 'CHAT', '', error) throw error } }) @@ -841,7 +841,7 @@ const handleStreamResponse = async (res, response, enable_thinking, enable_web_s await pipeUpstream(retryResp.response) } } catch (e) { - logger.error('Agent 补偿重试失败', 'CHAT', '', e) + logger.error('Agent compensation retry failed', 'CHAT', '', e) if (e.publicMessage) throw e } } @@ -904,9 +904,9 @@ const handleStreamResponse = async (res, response, enable_thinking, enable_web_s // 计算最终的token使用量 if (totalTokens.prompt_tokens === 0 && totalTokens.completion_tokens === 0) { totalTokens = createUsageObject(requestBody?.messages || promptText, completionContent, null) - logger.info(`流式使用tiktoken计算 - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') + logger.info(`Streaming using tiktoken calculation - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') } else { - logger.info(`流式使用上游真实Token - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') + logger.info(`Streaming using upstream real token - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') } totalTokens.prompt_tokens = Math.max(0, totalTokens.prompt_tokens || 0) @@ -942,7 +942,7 @@ const handleStreamResponse = async (res, response, enable_thinking, enable_web_s res.write(`data: [DONE]\n\n`) res.end() } catch (error) { - logger.error('聊天处理错误', 'CHAT', '', error) + logger.error('Chat processing error', 'CHAT', '', error) if (res.headersSent) { if (!res.writableEnded) { writeOpenAIStreamError( @@ -1207,7 +1207,7 @@ const handleNonStreamResponse = async (res, response, enable_thinking, enable_we ] } } catch (e) { - logger.error('Agent 补偿重试失败', 'CHAT', '', e) + logger.error('Agent compensation retry failed', 'CHAT', '', e) if (e.publicMessage) throw e } } @@ -1261,9 +1261,9 @@ const handleNonStreamResponse = async (res, response, enable_thinking, enable_we // 计算最终的token使用量(推理内容计入 completion,与 DeepSeek 一致;旧版 fullReasoning 为空) if (totalTokens.prompt_tokens === 0 && totalTokens.completion_tokens === 0) { totalTokens = createUsageObject(requestBody?.messages || promptText, fullReasoning + fullContent, null) - logger.info(`非流式使用tiktoken计算 - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') + logger.info(`Non-streaming using tiktoken calculation - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') } else { - logger.info(`非流式使用上游真实Token - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') + logger.info(`Non-streaming using upstream real token - Prompt: ${totalTokens.prompt_tokens}, Completion: ${totalTokens.completion_tokens}, Total: ${totalTokens.total_tokens}`, 'CHAT') } totalTokens.prompt_tokens = Math.max(0, totalTokens.prompt_tokens || 0) @@ -1297,7 +1297,7 @@ const handleNonStreamResponse = async (res, response, enable_thinking, enable_we } res.json(bodyTemplate) } catch (error) { - logger.error('非流式聊天处理错误', 'CHAT', '', error) + logger.error('Non-streaming Chat processing error', 'CHAT', '', error) if (!res.headersSent) { res.status(502).json({ error: { @@ -1362,7 +1362,7 @@ const handleChatCompletion = async (req, res) => { } } catch (error) { - logger.error('聊天处理错误', 'CHAT', '', error) + logger.error('Chat processing error', 'CHAT', '', error) res.status(500) .json({ error: "Invalid token, request failed" diff --git a/src/controllers/cli.chat.js b/src/controllers/cli.chat.js index f63a37f3..7aa65b42 100644 --- a/src/controllers/cli.chat.js +++ b/src/controllers/cli.chat.js @@ -289,7 +289,7 @@ const handleCliChatCompletion = async (req, res) => { const isStream = body.stream === true // 打印当前使用的账号邮箱 - logger.info(`CLI请求使用账号[${req.account.email}]开始处理`, 'CLI', '🚀') + logger.info(`CLI request using account [${req.account.email}] starting processing`, 'CLI', '🚀') // 无论成功失败都增加请求计数 req.account.cli_info.request_number++ @@ -348,7 +348,7 @@ const handleCliChatCompletion = async (req, res) => { // 检查响应状态 if (response.status !== 200) { const errorDetails = await normalizeCliErrorDetails(response.data) - logger.error(`CLI请求使用账号[${req.account.email}]转发失败 - 状态码: ${response.status} - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI', '❌', { + logger.error(`CLI request using account [${req.account.email}] forwarding failed - status code: ${response.status} - current request count: ${req.account.cli_info.request_number}`, 'CLI', '❌', { status: response.status, statusText: response.statusText, requestBody: body, @@ -402,18 +402,18 @@ const handleCliChatCompletion = async (req, res) => { } if (!streamResult.sawDone) res.write('data: [DONE]\n\n') - logger.success(`CLI请求使用账号[${req.account.email}]转发成功 (流式) - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI') + logger.success(`CLI request using account [${req.account.email}] forwarding succeeded (streaming) - current request count: ${req.account.cli_info.request_number}`, 'CLI') attributeCliUsage(req.account.email, cliUsage) res.end() } else { // 处理JSON响应 const cliUsage = response.data?.usage res.json(formatCliJsonResponse(response.data, body.model)) - logger.success(`CLI请求使用账号[${req.account.email}]转发成功 (JSON) - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI') + logger.success(`CLI request using account [${req.account.email}] forwarding succeeded (JSON) - current request count: ${req.account.cli_info.request_number}`, 'CLI') attributeCliUsage(req.account.email, cliUsage) } } catch (error) { - logger.error(`CLI请求使用账号[${req.account.email}]处理异常 - 当前请求数: ${req.account.cli_info.request_number}`, 'CLI', '💥', { + logger.error(`CLI request using account [${req.account.email}] processing exception - current request count: ${req.account.cli_info.request_number}`, 'CLI', '💥', { requestBody: body, ...(await buildCliAxiosErrorLog(error)) }) diff --git a/src/middlewares/chat-middleware.js b/src/middlewares/chat-middleware.js index a3824fda..d6af9d9d 100644 --- a/src/middlewares/chat-middleware.js +++ b/src/middlewares/chat-middleware.js @@ -66,7 +66,7 @@ const processRequestBody = async (req, res, next) => { incremental_output: true, chat_id: null, // 由 sendChatRequest 填充 chatId: null, - chat_mode: 'normal', + chat_mode: req.body.chat_mode === 'local' ? 'local' : (config.enableTempChats ? 'local' : 'normal'), model: await parserModel(model), parent_id: null, parentId: null, @@ -190,11 +190,11 @@ const processRequestBody = async (req, res, next) => { next() } catch (e) { - logger.error('处理请求体时发生错误', 'MIDDLEWARE', '', e) + logger.error('Error processing request body', 'MIDDLEWARE', '', e) res.status(500) .json({ status: 500, - message: "在处理请求体时发生错误 ~ ~ ~" + message: "在Error processing request body ~ ~ ~" }) } } diff --git a/src/models/models-map.js b/src/models/models-map.js index 84f76c0a..23c9a3a1 100644 --- a/src/models/models-map.js +++ b/src/models/models-map.js @@ -71,7 +71,7 @@ const getLatestModels = async (force = false) => { fetchPromise = null return cachedModels }).catch(error => { - logger.error(`获取模型列表失败: ${error.message}`, 'MODEL') + logger.error(`Failed to get model list: ${error.message}`, 'MODEL') fetchPromise = null // 刷新失败时回退到旧缓存,避免返回空列表;重置时间戳以免每个请求都重试 if (cachedModels) { diff --git a/src/routes/accounts.js b/src/routes/accounts.js index ce451c04..fa154a85 100644 --- a/src/routes/accounts.js +++ b/src/routes/accounts.js @@ -190,13 +190,13 @@ const processBatchAccountItem = async (task, account) => { try { const authToken = await accountManager.login(email, password, proxy) if (!authToken) { - throw new Error('登录失败') + throw new Error('Login failed') } const decoded = JwtDecode(authToken) const saved = await accountManager.addAccountWithToken(email, password, authToken, decoded.exp, proxy) if (!saved) { - throw new Error('保存失败') + throw new Error('Save failed') } task.success++ @@ -217,7 +217,7 @@ const processBatchAccountItem = async (task, account) => { message: error.message || '登录失败' }) - logger.error(`批量登录账号失败: ${email}`, 'ACCOUNT', '', error) + logger.error(`Batch login failed: ${email}`, 'ACCOUNT', '', error) } finally { task.processed++ task.completed++ @@ -260,7 +260,7 @@ const runBatchAccountTask = async (task, newAccounts) => { task.status = 'failed' task.finishedAt = Date.now() task.message = error.message || '批量添加执行失败' - logger.error('批量创建账号失败', 'ACCOUNT', '', error) + logger.error('Batch create account failed', 'ACCOUNT', '', error) scheduleBatchTaskCleanup(task.id) return task } @@ -317,7 +317,7 @@ router.get('/getAllAccounts', adminKeyVerify, async (req, res) => { data: accounts }) } catch (error) { - logger.error('获取账号列表失败', 'ACCOUNT', '', error) + logger.error('Failed to get account list', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -372,7 +372,7 @@ router.post('/setAccount', adminKeyVerify, async (req, res) => { res.status(500).json({ error: '账号创建失败' }) } } catch (error) { - logger.error('创建账号失败', 'ACCOUNT', '', error) + logger.error('Failed to create account', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -398,12 +398,12 @@ router.delete('/deleteAccount', adminKeyVerify, async (req, res) => { const success = await deleteAccount(email) if (success) { - res.json({ message: '账号删除成功' }) + res.json({ message: 'Account deleted successfully' }) } else { res.status(500).json({ error: '账号删除失败' }) } } catch (error) { - logger.error('删除账号失败', 'ACCOUNT', '', error) + logger.error('Failed to delete account', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -466,7 +466,7 @@ router.post('/setAccounts', adminKeyVerify, async (req, res) => { ...getBatchTaskSnapshot(task) }) } catch (error) { - logger.error('批量创建账号失败', 'ACCOUNT', '', error) + logger.error('Batch create account failed', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -529,7 +529,7 @@ router.post('/updateAccountProxy', adminKeyVerify, async (req, res) => { res.status(500).json({ error: '账号代理更新失败' }) } } catch (error) { - logger.error('更新账号代理失败', 'ACCOUNT', '', error) + logger.error('Failed to update account proxy', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -567,7 +567,7 @@ router.post('/refreshAccount', adminKeyVerify, async (req, res) => { res.status(500).json({ error: '账号令牌刷新失败' }) } } catch (error) { - logger.error('刷新账号令牌失败', 'ACCOUNT', '', error) + logger.error('Failed to refresh account token', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -592,7 +592,7 @@ router.post('/refreshAllAccounts', adminKeyVerify, async (req, res) => { thresholdHours: thresholdHours }) } catch (error) { - logger.error('批量刷新账号令牌失败', 'ACCOUNT', '', error) + logger.error('Batch refresh account token failed', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -614,7 +614,7 @@ router.post('/forceRefreshAllAccounts', adminKeyVerify, async (req, res) => { totalAccounts: accountManager.getAllAccountKeys().length }) } catch (error) { - logger.error('强制刷新账号令牌失败', 'ACCOUNT', '', error) + logger.error('Force refresh account token failed', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -663,7 +663,7 @@ router.get('/accountStats', adminKeyVerify, async (req, res) => { cliQuotaLimit }) } catch (error) { - logger.error('获取账户 stats 失败', 'ACCOUNT', '', error) + logger.error('Failed to get account stats', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -733,7 +733,7 @@ router.get('/statsHistory', adminKeyVerify, async (req, res) => { res.json({ today, accounts }) } catch (error) { - logger.error('获取 statsHistory 失败', 'ACCOUNT', '', error) + logger.error('Failed to get statsHistory', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) @@ -762,11 +762,11 @@ if (process.env.ENABLE_STATS_DEBUG_ARCHIVE === 'true') { if (error && error.message && error.message.includes('not initialized')) { return res.status(503).json({ error: 'account manager not initialized, try again' }) } - logger.error('debug/archiveYesterday 失败', 'ACCOUNT', '', error) + logger.error('debug/archiveYesterday failed', 'ACCOUNT', '', error) res.status(500).json({ error: error.message }) } }) - logger.info('已启用 debug/archiveYesterday 端点 (ENABLE_STATS_DEBUG_ARCHIVE=true)', 'ACCOUNT') + logger.info('debug/archiveYesterday endpoint enabled (ENABLE_STATS_DEBUG_ARCHIVE=true)', 'ACCOUNT') } module.exports = router diff --git a/src/routes/settings.js b/src/routes/settings.js index 18e00458..e98c2267 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -48,9 +48,9 @@ router.post('/addRegularKey', adminKeyVerify, async (req, res) => { const persisted = await dataPersistence.saveSettings({ apiKeys: config.apiKeys }) - res.json({ message: 'API Key添加成功', persisted }) + res.json({ message: 'API Key added successfully', persisted }) } catch (error) { - logger.error('添加API Key失败', 'CONFIG', '', error) + logger.error('Failed to add API Key', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -78,9 +78,9 @@ router.post('/deleteRegularKey', adminKeyVerify, async (req, res) => { const persisted = await dataPersistence.saveSettings({ apiKeys: config.apiKeys }) - res.json({ message: 'API Key删除成功', persisted }) + res.json({ message: 'API Key deleted successfully', persisted }) } catch (error) { - logger.error('删除API Key失败', 'CONFIG', '', error) + logger.error('Failed to delete API Key', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -107,7 +107,7 @@ router.post('/setAutoRefresh', adminKeyVerify, async (req, res) => { message: '自动刷新设置更新成功' }) } catch (error) { - logger.error('更新自动刷新设置失败', 'CONFIG', '', error) + logger.error('Failed to update auto-refresh settings', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -127,7 +127,7 @@ router.post('/setBatchLoginConcurrency', adminKeyVerify, async (req, res) => { message: '批量登录并发数更新成功' }) } catch (error) { - logger.error('更新批量登录并发数失败', 'CONFIG', '', error) + logger.error('Failed to update batch login concurrency', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -146,7 +146,7 @@ router.post('/setOutThink', adminKeyVerify, async (req, res) => { message: '思考输出设置更新成功' }) } catch (error) { - logger.error('更新思考输出设置失败', 'CONFIG', '', error) + logger.error('Failed to update thinking output settings', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -165,7 +165,7 @@ router.post('/setLegacyReasoning', adminKeyVerify, async (req, res) => { message: '推理格式设置更新成功' }) } catch (error) { - logger.error('更新推理格式设置失败', 'CONFIG', '', error) + logger.error('Failed to update reasoning format settings', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -184,7 +184,7 @@ router.post('/search-info-mode', adminKeyVerify, async (req, res) => { message: '搜索信息模式更新成功' }) } catch (error) { - logger.error('更新搜索信息模式失败', 'CONFIG', '', error) + logger.error('Failed to update search info mode', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -224,7 +224,7 @@ router.post('/setRetryConfig', adminKeyVerify, async (req, res) => { persisted }) } catch (error) { - logger.error('更新聊天 retry 配置失败', 'CONFIG', '', error) + logger.error('Failed to update chat retry config', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) @@ -243,7 +243,7 @@ router.post('/simple-model-map', adminKeyVerify, async (req, res) => { message: '简化模型映射设置更新成功' }) } catch (error) { - logger.error('更新简化模型映射设置失败', 'CONFIG', '', error) + logger.error('Failed to update simplified model mapping settings', 'CONFIG', '', error) res.status(500).json({ error: error.message }) } }) diff --git a/src/server.js b/src/server.js index 61b1295f..17cc9f63 100644 --- a/src/server.js +++ b/src/server.js @@ -43,16 +43,16 @@ app.use(express.static(path.join(__dirname, '../public/dist'))) app.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../public/dist/index.html'), (err) => { if (err) { - logger.error('管理页面加载失败', 'SERVER', '', err) - res.status(500).send('服务器内部错误') + logger.error('Failed to load admin page', 'SERVER', '', err) + res.status(500).send('Internal server error') } }) }) // 处理错误中间件(必须放在所有路由之后) app.use((err, req, res, next) => { - logger.error('服务器内部错误', 'SERVER', '', err) - res.status(500).send('服务器内部错误') + logger.error('Internal server error', 'SERVER', '', err) + res.status(500).send('Internal server error') }) @@ -86,22 +86,22 @@ const applyPersistedSettings = async () => { config.adminKey = persisted.apiKeys[0]; } } catch (err) { - logger.warn('加载持久化设置失败, 使用 env/默认值', 'CONFIG', '', err.message) + logger.warn('Failed to load persisted settings, using env/default values', 'CONFIG', '', err.message) } } const startServer = () => { if (config.listenAddress) { app.listen(config.listenPort, config.listenAddress, () => { - logger.server('服务器启动成功', 'SERVER', serverInfo) - logger.info('开源地址: https://github.com/Rfym21/Qwen2API', 'INFO') - logger.info('电报群聊: https://t.me/nodejs_project', 'INFO') + logger.server('Server started successfully', 'SERVER', serverInfo) + logger.info('GitHub: https://github.com/Rfym21/Qwen2API', 'INFO') + logger.info('Telegram group: https://t.me/nodejs_project', 'INFO') }) } else { app.listen(config.listenPort, () => { - logger.server('服务器启动成功', 'SERVER', serverInfo) - logger.info('开源地址: https://github.com/Rfym21/Qwen2API', 'INFO') - logger.info('电报群聊: https://t.me/nodejs_project', 'INFO') + logger.server('Server started successfully', 'SERVER', serverInfo) + logger.info('GitHub: https://github.com/Rfym21/Qwen2API', 'INFO') + logger.info('Telegram group: https://t.me/nodejs_project', 'INFO') }) } } diff --git a/src/start.js b/src/start.js index 518af3ad..9fc534e7 100644 --- a/src/start.js +++ b/src/start.js @@ -25,88 +25,88 @@ if (PM2_INSTANCES === 'max') { // 限制进程数不能超过CPU核心数 if (instances > cpuCores) { - logger.warn(`配置的进程数(${instances})超过CPU核心数(${cpuCores}),自动调整为${cpuCores}`, 'AUTO') + logger.warn(`Configured processes(${instances}) exceeds CPU cores(${cpuCores}), auto-adjusting to ${cpuCores}`, 'AUTO') instances = cpuCores } -logger.info('🚀 Qwen2API 智能启动', 'AUTO') -logger.info(`CPU核心数: ${cpuCores}`, 'AUTO') -logger.info(`配置的进程数: ${PM2_INSTANCES}`, 'AUTO') -logger.info(`实际启动进程数: ${instances}`, 'AUTO') -logger.info(`服务端口: ${SERVICE_PORT}`, 'AUTO') +logger.info('🚀 Qwen2API Smart Boot', 'AUTO') +logger.info(`CPU cores: ${cpuCores}`, 'AUTO') +logger.info(`Configured processes: ${PM2_INSTANCES}`, 'AUTO') +logger.info(`Actual started processes: ${instances}`, 'AUTO') +logger.info(`Service port: ${SERVICE_PORT}`, 'AUTO') // 智能判断启动方式 if (instances === 1) { - logger.info('📦 使用单进程模式启动', 'AUTO') + logger.info('📦 Starting in single-process mode', 'AUTO') // 直接启动服务器 require('./server.js') } else { // 检查是否通过PM2启动 if (process.env.PM2_USAGE || process.env.pm_id !== undefined) { - logger.info(`PM2进程启动 - 进程ID: ${process.pid}, 工作进程ID: ${process.env.pm_id || 'unknown'}`, 'PM2') + logger.info(`PM2 process started - PID: ${process.pid}, Worker PID: ${process.env.pm_id || 'unknown'}`, 'PM2') require('./server.js') } else if (cluster.isMaster) { - logger.info(`🔥 使用Node.js集群模式启动 (${instances}个进程)`, 'AUTO') + logger.info(`🔥 Starting with Node.js cluster mode (${instances} processes)`, 'AUTO') - logger.info(`启动主进程 - PID: ${process.pid}`, 'CLUSTER') - logger.info(`运行环境: ${NODE_ENV}`, 'CLUSTER') + logger.info(`Starting master process - PID: ${process.pid}`, 'CLUSTER') + logger.info(`Runtime environment: ${NODE_ENV}`, 'CLUSTER') - // 创建工作进程 + // Create worker processes for (let i = 0; i < instances; i++) { const worker = cluster.fork() - logger.info(`启动工作进程 ${i + 1}/${instances} - PID: ${worker.process.pid}`, 'CLUSTER') + logger.info(`Starting worker process ${i + 1}/${instances} - PID: ${worker.process.pid}`, 'CLUSTER') } - // 监听工作进程退出 + // Listen for worker exit cluster.on('exit', (worker, code, signal) => { - logger.error(`工作进程 ${worker.process.pid} 已退出 - 退出码: ${code}, 信号: ${signal}`, 'CLUSTER') + logger.error(`Worker process ${worker.process.pid} exited - code: ${code}, signal: ${signal}`, 'CLUSTER') - // 自动重启工作进程 + // Auto-restart worker if (!worker.exitedAfterDisconnect) { - logger.info('正在重启工作进程...', 'CLUSTER') + logger.info('Restarting worker process...', 'CLUSTER') const newWorker = cluster.fork() - logger.info(`新工作进程已启动 - PID: ${newWorker.process.pid}`, 'CLUSTER') + logger.info(`New worker process started - PID: ${newWorker.process.pid}`, 'CLUSTER') } }) - // 监听工作进程在线 + // Listen for worker online cluster.on('online', (worker) => { - logger.info(`工作进程 ${worker.process.pid} 已上线`, 'CLUSTER') + logger.info(`Worker process ${worker.process.pid} is online`, 'CLUSTER') }) - // 监听工作进程断开连接 + // Listen for worker disconnect cluster.on('disconnect', (worker) => { - logger.warn(`工作进程 ${worker.process.pid} 已断开连接`, 'CLUSTER') + logger.warn(`Worker process ${worker.process.pid} disconnected`, 'CLUSTER') }) - // 优雅关闭处理 + // Graceful shutdown process.on('SIGTERM', () => { - logger.info('收到SIGTERM信号,正在优雅关闭...', 'CLUSTER') + logger.info('Received SIGTERM, shutting down gracefully...', 'CLUSTER') cluster.disconnect(() => { process.exit(0) }) }) process.on('SIGINT', () => { - logger.info('收到SIGINT信号,正在优雅关闭...', 'CLUSTER') + logger.info('Received SIGINT, shutting down gracefully...', 'CLUSTER') cluster.disconnect(() => { process.exit(0) }) }) } else { - // 工作进程逻辑 - logger.info(`工作进程启动 - PID: ${process.pid}`, 'WORKER') + // Worker process logic + logger.info(`Worker process started - PID: ${process.pid}`, 'WORKER') require('./server.js') - // 工作进程优雅关闭处理 + // Worker graceful shutdown process.on('SIGTERM', () => { - logger.info(`工作进程 ${process.pid} 收到SIGTERM信号,正在关闭...`, 'WORKER') + logger.info(`Worker process ${process.pid} received SIGTERM, closing...`, 'WORKER') process.exit(0) }) process.on('SIGINT', () => { - logger.info(`工作进程 ${process.pid} 收到SIGINT信号,正在关闭...`, 'WORKER') + logger.info(`Worker process ${process.pid} received SIGINT, closing...`, 'WORKER') process.exit(0) }) } diff --git a/src/utils/account-rotator.js b/src/utils/account-rotator.js index 40d393f6..811ae088 100644 --- a/src/utils/account-rotator.js +++ b/src/utils/account-rotator.js @@ -23,8 +23,8 @@ class AccountRotator { */ setAccounts(accounts) { if (!Array.isArray(accounts)) { - logger.error('账户列表必须是数组', 'ACCOUNT') - throw new Error('账户列表必须是数组') + logger.error('Account list must be an array', 'ACCOUNT') + throw new Error('Account list must be an array') } this.accounts = [...accounts] @@ -40,13 +40,13 @@ class AccountRotator { */ getNextAccount() { if (this.accounts.length === 0) { - logger.error('没有可用的账户', 'ACCOUNT') + logger.error('No available accounts', 'ACCOUNT') return null } const availableAccounts = this._getAvailableAccounts() if (availableAccounts.length === 0) { - logger.warn('所有账户都不可用,使用轮询策略', 'ACCOUNT') + logger.warn('All accounts unavailable, using round-robin strategy', 'ACCOUNT') return this._getAccountByRoundRobin() } @@ -74,12 +74,12 @@ class AccountRotator { getAccountByEmail(email) { const account = this.accounts.find(acc => acc.email === email) if (!account) { - logger.error(`未找到邮箱为 ${email} 的账户`, 'ACCOUNT') + logger.error(`Account with email not found: ${email} 's account`, 'ACCOUNT') return null } if (!this._isAccountAvailable(account)) { - logger.warn(`账户 ${email} 当前不可用`, 'ACCOUNT') + logger.warn(`Account ${email} is currently unavailable`, 'ACCOUNT') return null } @@ -117,7 +117,7 @@ class AccountRotator { // 达到阈值的瞬间标记 cooldown 起点(独立于 lastUsedTimes,CLI-only 失败也正确) if (nextFailures >= this.maxFailures && !this.cooldownStartedAt.has(email)) { this.cooldownStartedAt.set(email, Date.now()) - logger.warn(`账户 ${email} 失败次数达到上限,将进入冷却期`, 'ACCOUNT') + logger.warn(`Account ${email} failure count reached limit, entering cooldown`, 'ACCOUNT') } } diff --git a/src/utils/account.js b/src/utils/account.js index 64cb4765..cfee3f2b 100644 --- a/src/utils/account.js +++ b/src/utils/account.js @@ -120,11 +120,11 @@ class Account { this.isInitialized = true this.initError = null - logger.success(`账户管理器初始化完成,共加载 ${this.accountTokens.length} 个账户`, 'ACCOUNT') + logger.success(`Account manager initialized, loaded ${this.accountTokens.length} accounts`, 'ACCOUNT') } catch (error) { this.isInitialized = false this.initError = error - logger.error('账户管理器初始化失败', 'ACCOUNT', '', error) + logger.error('Account manager init failed', 'ACCOUNT', '', error) } } @@ -171,29 +171,29 @@ class Account { // 为所有账户启动 CLI 初始化,确保没有 CLI 额度的账号被正确标记为 unsupported if (this.accountTokens.length > 0) { if (config.cliEnabled) { - logger.info(`后台初始化所有 ${this.accountTokens.length} 个账户的 CLI`, 'ACCOUNT') + logger.info(`Background init all ${this.accountTokens.length} accounts CLI`, 'ACCOUNT') Promise.allSettled( this.accountTokens.map(account => this._initializeCliAccount(account)) ).then(() => { const cliReady = this.accountTokens.filter(a => a.cli_info).length const cliUnsupported = this.accountTokens.filter(a => a.cli_unavailable_reason === 'unsupported').length - logger.success(`CLI 初始化完成: ${cliReady} 个可用, ${cliUnsupported} 个不支持`, 'CLI') + logger.success(`CLI init complete: ${cliReady} available, ${cliUnsupported} unsupported`, 'CLI') }) } else { // CLI 关闭时也要标记账户:cli_unavailable_reason 为空的账户会被 // cli-support.js 判定为 cli_pending,管理面板会一直显示「CLI 初始化中」。 // 该字段不落盘(见 data-persistence 的字段白名单),所以每次启动都要重新标记。 this.accountTokens.forEach(account => this._markCliDisabled(account)) - logger.info(`CLI 已关闭,${this.accountTokens.length} 个账户标记为 disabled`, 'CLI') + logger.info(`CLI disabled, ${this.accountTokens.length} accounts marked as disabled`, 'CLI') } } // 设置cli定时器 每天00:00:00刷新请求次数 this._setupDailyResetTimer() - logger.success(`成功加载 ${this.accountTokens.length} 个账户`, 'ACCOUNT') + logger.success(`Successfully loaded ${this.accountTokens.length} accounts`, 'ACCOUNT') } catch (error) { - logger.error('加载账户令牌失败', 'ACCOUNT', '', error) + logger.error('Failed to load account tokens', 'ACCOUNT', '', error) this.accountTokens = [] this.accountRotator.setAccounts(this.accountTokens) throw error @@ -263,25 +263,25 @@ class Account { account.cli_info.access_token = refreshToken.access_token account.cli_info.refresh_token = refreshToken.refresh_token account.cli_info.expiry_date = refreshToken.expiry_date - logger.info(`CLI账户 ${account.email} 令牌刷新成功`, 'CLI') + logger.info(`CLI account ${account.email} token refresh successful`, 'CLI') } } catch (error) { - logger.error(`CLI账户 ${account.email} 令牌刷新失败`, 'CLI', '', error) + logger.error(`CLI account ${account.email} token refresh failed`, 'CLI', '', error) } // 每2小时刷新一次 }, 1000 * 60 * 60 * 2), request_number: 0 } - logger.success(`CLI账户 ${account.email} 初始化成功`, 'CLI') + logger.success(`CLI account ${account.email} init successful`, 'CLI') } else { account.cli_info = null account.cli_unavailable_reason = 'unsupported' - logger.error(`CLI账户 ${account.email} 初始化失败:无效的响应数据`, 'CLI', '', cliAccount) + logger.error(`CLI account ${account.email} init failed: invalid response data`, 'CLI', '', cliAccount) } } catch (error) { account.cli_info = null account.cli_unavailable_reason = 'unsupported' - logger.error(`CLI账户 ${account.email} 初始化失败`, 'CLI', '', error) + logger.error(`CLI account ${account.email} init failed`, 'CLI', '', error) } } @@ -290,14 +290,14 @@ class Account { * @private */ _setupDailyResetTimer() { - logger.info('设置每日 00:00 重置定时器(CLI 请求次数 + daily stats)', 'CLI') + logger.info('Set daily 00:00 reset timer (CLI request count + daily stats)', 'CLI') // 计算到下一天00:00:00的毫秒数 const now = new Date() const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0) const timeDiff = tomorrow.getTime() - now.getTime() - logger.info(`距离下次重置还有 ${Math.round(timeDiff / 1000 / 60)} 分钟`, 'CLI') + logger.info(`Time until next reset: ${Math.round(timeDiff / 1000 / 60)} minutes`, 'CLI') // 首次执行使用setTimeout this.cliRequestNumberInterval = setTimeout(() => { @@ -375,7 +375,7 @@ class Account { try { await this.dataPersistence.saveAllAccounts(this.accountTokens) } catch (error) { - logger.error('每日重置后 persist 失败', 'ACCOUNT', '', error) + logger.error('Persist failed after daily reset', 'ACCOUNT', '', error) } } @@ -426,7 +426,7 @@ class Account { validAccounts.push(account) } else if (account.email && account.password) { // 尝试重新登录 - logger.info(`令牌无效,尝试重新登录: ${account.email}`, 'TOKEN', '🔄') + logger.info(`Token invalid, retrying login: ${account.email}`, 'TOKEN', '🔄') const newToken = await this.tokenManager.login(account.email, account.password, account) if (newToken) { const decoded = this.tokenManager.validateToken(newToken) @@ -450,11 +450,11 @@ class Account { */ async autoRefreshTokens(thresholdHours = 24) { if (!this.isInitialized) { - logger.warn('账户管理器尚未初始化,跳过自动刷新', 'TOKEN') + logger.warn('Account manager not initialized, skipping auto-refresh', 'TOKEN') return 0 } - logger.info('开始自动刷新令牌...', 'TOKEN', '🔄') + logger.info('Starting auto token refresh...', 'TOKEN', '🔄') // 获取需要刷新的账户 const needsRefresh = this.accountTokens.filter(account => @@ -462,11 +462,11 @@ class Account { ) if (needsRefresh.length === 0) { - logger.info('没有需要刷新的令牌', 'TOKEN') + logger.info('No tokens need refreshing', 'TOKEN') return 0 } - logger.info(`发现 ${needsRefresh.length} 个令牌需要刷新`, 'TOKEN') + logger.info(`Found ${needsRefresh.length} tokens needing refresh`, 'TOKEN') let successCount = 0 let failedCount = 0 @@ -494,17 +494,17 @@ class Account { this.accountRotator.resetFailures(account.email) successCount++ - logger.info(`账户 ${account.email} 令牌刷新并保存成功 (${successCount}/${needsRefresh.length})`, 'TOKEN', '✅') + logger.info(`Account ${account.email} token refresh and save successful (${successCount}/${needsRefresh.length})`, 'TOKEN', '✅') } else { // 记录失败的账户 this.accountRotator.recordFailure(account.email) failedCount++ - logger.error(`账户 ${account.email} 令牌刷新失败 (${failedCount} 个失败)`, 'TOKEN', '❌') + logger.error(`Account ${account.email} token refresh failed (${failedCount} failures)`, 'TOKEN', '❌') } } catch (error) { this.accountRotator.recordFailure(account.email) failedCount++ - logger.error(`账户 ${account.email} 刷新过程中出错`, 'TOKEN', '', error) + logger.error(`Account ${account.email} error during refresh`, 'TOKEN', '', error) } // 添加延迟避免请求过于频繁 @@ -514,7 +514,7 @@ class Account { // 更新轮询器 this.accountRotator.setAccounts(this.accountTokens) - logger.success(`令牌刷新完成: 成功 ${successCount} 个,失败 ${failedCount} 个`, 'TOKEN') + logger.success(`Token refresh complete: succeeded ${successCount} , failed ${failedCount}`, 'TOKEN') return successCount } @@ -524,18 +524,18 @@ class Account { */ getAccount() { if (!this.isInitialized) { - logger.warn('账户管理器尚未初始化完成', 'ACCOUNT') + logger.warn('Account manager not fully initialized', 'ACCOUNT') return null } if (this.accountTokens.length === 0) { - logger.error('没有可用的账户令牌', 'ACCOUNT') + logger.error('No available account tokens', 'ACCOUNT') return null } const account = this.accountRotator.getNextAccount() if (!account) { - logger.error('所有账户令牌都不可用', 'ACCOUNT') + logger.error('All account tokens unavailable', 'ACCOUNT') } return account @@ -594,7 +594,7 @@ class Account { }) } } catch (error) { - logger.error('保存更新后的账户数据失败', 'ACCOUNT', '', error) + logger.error('Failed to save updated account data', 'ACCOUNT', '', error) } } @@ -606,7 +606,7 @@ class Account { async refreshAccountToken(email) { const account = this.accountTokens.find(acc => acc.email === email) if (!account) { - logger.error(`未找到邮箱为 ${email} 的账户`, 'ACCOUNT') + logger.error(`Account with email ${email} not found`, 'ACCOUNT') return false } @@ -779,7 +779,7 @@ class Account { try { this.dataPersistence.saveAccountStats(email, account.stats) } catch (error) { - logger.error(`accumulateStats persist 调度失败 (${email})`, 'STATS', '', error) + logger.error(`accumulateStats persist scheduling failed (${email})`, 'STATS', '', error) } } @@ -803,20 +803,20 @@ class Account { // 检查账户是否已存在 const existingAccount = this.accountTokens.find(acc => acc.email === email) if (existingAccount) { - logger.warn(`账户 ${email} 已存在`, 'ACCOUNT') + logger.warn(`Account ${email} already exists`, 'ACCOUNT') return false } // 尝试登录获取令牌 const token = await this.tokenManager.login(email, password, proxy ? { proxy } : undefined) if (!token) { - logger.error(`账户 ${email} 登录失败,无法添加`, 'ACCOUNT') + logger.error(`Account ${email} login failed, cannot add`, 'ACCOUNT') return false } const decoded = this.tokenManager.validateToken(token) if (!decoded) { - logger.error(`账户 ${email} 令牌无效,无法添加`, 'ACCOUNT') + logger.error(`Account ${email} token invalid, cannot add`, 'ACCOUNT') return false } @@ -838,7 +838,7 @@ class Account { if (!saved) { this.accountTokens.splice(insertedIndex, 1) this.accountRotator.setAccounts(this.accountTokens) - logger.error(`账户 ${email} 持久化失败,已回滚内存数据`, 'ACCOUNT') + logger.error(`Account ${email} persistence failed, rolled back in-memory data`, 'ACCOUNT') return false } @@ -847,13 +847,13 @@ class Account { // 后台初始化 CLI this._initializeCliAccount(newAccount).catch(err => { - logger.error(`新账户 CLI 初始化失败: ${email}`, 'ACCOUNT', '', err) + logger.error(`New Account CLI init failed: ${email}`, 'ACCOUNT', '', err) }) - logger.success(`成功添加账户: ${email}`, 'ACCOUNT') + logger.success(`Successfully added account: ${email}`, 'ACCOUNT') return true } catch (error) { - logger.error(`添加账户失败 (${email})`, 'ACCOUNT', '', error) + logger.error(`Failed to add account (${email})`, 'ACCOUNT', '', error) return false } } @@ -872,7 +872,7 @@ class Account { // 检查账户是否已存在 const existingAccount = this.accountTokens.find(acc => acc.email === email) if (existingAccount) { - logger.warn(`账户 ${email} 已存在`, 'ACCOUNT') + logger.warn(`Account ${email} already exists`, 'ACCOUNT') return false } @@ -894,7 +894,7 @@ class Account { if (!saved) { this.accountTokens.splice(insertedIndex, 1) this.accountRotator.setAccounts(this.accountTokens) - logger.error(`账户 ${email} 持久化失败,已回滚内存数据`, 'ACCOUNT') + logger.error(`Account ${email} persistence failed, rolled back in-memory data`, 'ACCOUNT') return false } @@ -903,13 +903,13 @@ class Account { // 后台初始化 CLI this._initializeCliAccount(newAccount).catch(err => { - logger.error(`新账户 CLI 初始化失败: ${email}`, 'ACCOUNT', '', err) + logger.error(`New Account CLI init failed: ${email}`, 'ACCOUNT', '', err) }) - logger.success(`成功添加账户: ${email}`, 'ACCOUNT') + logger.success(`Successfully added account: ${email}`, 'ACCOUNT') return true } catch (error) { - logger.error(`添加账户失败 (${email})`, 'ACCOUNT', '', error) + logger.error(`Failed to add account (${email})`, 'ACCOUNT', '', error) return false } } @@ -925,7 +925,7 @@ class Account { try { const account = this.accountTokens.find(acc => acc.email === email) if (!account) { - logger.warn(`账户 ${email} 不存在`, 'ACCOUNT') + logger.warn(`Account ${email} does not exist`, 'ACCOUNT') return false } @@ -933,7 +933,7 @@ class Account { const newProxy = (typeof proxy === 'string' && proxy.trim()) ? proxy.trim() : null if (oldProxy === newProxy) { - logger.info(`账户 ${email} 代理未变化,无需更新`, 'ACCOUNT') + logger.info(`Account ${email} proxy unchanged, no update needed`, 'ACCOUNT') return true } @@ -947,7 +947,7 @@ class Account { }) if (!saved) { account.proxy = oldProxy - logger.error(`账户 ${email} 代理持久化失败,已回滚内存数据`, 'ACCOUNT') + logger.error(`Account ${email} proxy persistence failed, rolled back in-memory data`, 'ACCOUNT') return false } @@ -959,10 +959,10 @@ class Account { invalidateProxyAgent(oldProxy) } - logger.success(`账户 ${email} 代理更新成功 (${oldProxy || '无'} → ${newProxy || '无'})`, 'ACCOUNT') + logger.success(`Account ${email} proxy update successful (${oldProxy || 'none'} -> ${newProxy || 'none'})`, 'ACCOUNT') return true } catch (error) { - logger.error(`更新账户 ${email} 代理失败`, 'ACCOUNT', '', error) + logger.error(`Updating account ${email} proxy update failed`, 'ACCOUNT', '', error) return false } } @@ -976,7 +976,7 @@ class Account { try { const index = this.accountTokens.findIndex(acc => acc.email === email) if (index === -1) { - logger.warn(`账户 ${email} 不存在`, 'ACCOUNT') + logger.warn(`Account ${email} does not exist`, 'ACCOUNT') return false } @@ -986,10 +986,10 @@ class Account { // 更新轮询器 this.accountRotator.setAccounts(this.accountTokens) - logger.success(`成功移除账户: ${email}`, 'ACCOUNT') + logger.success(`Successfully removed account: ${email}`, 'ACCOUNT') return true } catch (error) { - logger.error(`移除账户失败 (${email})`, 'ACCOUNT', '', error) + logger.error(`Failed to remove account (${email})`, 'ACCOUNT', '', error) return false } } @@ -1016,7 +1016,7 @@ class Account { */ async initializeCliForAccount(account) { if (!account) { - logger.error('账户对象不能为空', 'CLI') + logger.error('Account object cannot be empty', 'CLI') return false } @@ -1024,7 +1024,7 @@ class Account { await this._initializeCliAccount(account) return true } catch (error) { - logger.error(`为账户 ${account.email} 初始化CLI失败`, 'CLI', '', error) + logger.error(`Initializing CLI for account ${account.email} initializing CLI failed`, 'CLI', '', error) return false } } @@ -1068,13 +1068,13 @@ class Account { }) this.accountRotator.reset() - logger.info('账户管理器已清理资源', 'ACCOUNT', '🧹') + logger.info('Account manager resources cleaned up', 'ACCOUNT', '🧹') } } if (!(process.env.API_KEY || config.apiKey)) { - logger.error('请务必设置 API_KEY 环境变量', 'CONFIG', '⚙️') + logger.error('Please set API_KEY environment variable', 'CONFIG', '⚙️') process.exit(1) } diff --git a/src/utils/chat-helpers.js b/src/utils/chat-helpers.js index e7055dda..bb0700d7 100644 --- a/src/utils/chat-helpers.js +++ b/src/utils/chat-helpers.js @@ -175,7 +175,7 @@ const normalizeMediaContentItem = async (item, imgCacheManager) => { return buildNormalizedMediaItem(mediaType, uploadResult.file_url) } catch (error) { - logger.error(`${mediaType === 'video' ? '视频' : '图片'}上传失败`, 'UPLOAD', '', error) + logger.error(`${mediaType === 'video' ? 'video' : 'image'} upload failed`, 'UPLOAD', '', error) return null } } @@ -404,7 +404,7 @@ const parserMessages = async (messages, thinking_config, chat_type) => { } } catch (e) { - logger.error('消息解析失败', 'PARSER', '', e) + logger.error('Message parsing failed', 'PARSER', '', e) return [ { "role": "user", diff --git a/src/utils/cli.manager.js b/src/utils/cli.manager.js index 67656dbf..0b1ba250 100644 --- a/src/utils/cli.manager.js +++ b/src/utils/cli.manager.js @@ -100,7 +100,7 @@ class CliAuthManager { } } else { const responseBody = await this.readResponseBody(response) - logger.error('CLI设备授权初始化失败', 'CLI', '', { + logger.error('CLI device authorization init failed', 'CLI', '', { status: response.status, statusText: response.statusText, body: responseBody @@ -108,7 +108,7 @@ class CliAuthManager { throw new Error('device_flow_failed') } } catch (error) { - logger.error('CLI设备授权流程异常', 'CLI', '', { + logger.error('CLI device authorization flow error', 'CLI', '', { url: `${chatBaseUrl}/api/v1/oauth2/device/code`, message: error.message }) @@ -155,7 +155,7 @@ class CliAuthManager { return true } else { const responseBody = await this.readResponseBody(response) - logger.error('CLI设备授权确认失败', 'CLI', '', { + logger.error('CLI device authorization confirmation failed', 'CLI', '', { status: response.status, statusText: response.statusText, body: responseBody @@ -163,7 +163,7 @@ class CliAuthManager { throw new Error('authorize_failed') } } catch (error) { - logger.error('CLI设备授权确认异常', 'CLI', '', { + logger.error('CLI device authorization confirmation error', 'CLI', '', { url: `${chatBaseUrl}/api/v2/oauth2/authorize`, message: error.message }) @@ -216,14 +216,14 @@ class CliAuthManager { } if (!credentials.access_token || !credentials.refresh_token || !credentials.expiry_date) { - logger.error('CLI轮询令牌成功但返回数据不完整', 'CLI', '', tokenData) + logger.error('CLI poll token succeeded but returned incomplete data', 'CLI', '', tokenData) } return credentials } const responseBody = await this.readResponseBody(response) - logger.warn(`CLI轮询令牌未完成 (${attempt + 1}/${maxAttempts})`, 'CLI', '', { + logger.warn(`CLI poll token incomplete (${attempt + 1}/${maxAttempts})`, 'CLI', '', { status: response.status, statusText: response.statusText, body: responseBody @@ -234,7 +234,7 @@ class CliAuthManager { } catch (error) { // 等待5秒, 然后继续轮询 await new Promise(resolve => setTimeout(resolve, pollInterval)) - logger.error(`CLI轮询令牌异常 (${attempt + 1}/${maxAttempts})`, 'CLI', '', { + logger.error(`CLI poll token exception (${attempt + 1}/${maxAttempts})`, 'CLI', '', { url: `${chatBaseUrl}/api/v1/oauth2/token`, message: error.message }) @@ -259,7 +259,7 @@ class CliAuthManager { async initCliAccount(access_token, account) { const deviceFlow = await this.initiateDeviceFlow(account) if (!deviceFlow.status) { - logger.error('CLI账户初始化失败:设备授权流程未成功启动', 'CLI') + logger.error('CLI account init failed: device auth flow not started', 'CLI') return { status: false, access_token: null, @@ -269,7 +269,7 @@ class CliAuthManager { } if (!await this.authorizeLogin(deviceFlow.user_code, access_token, account)) { - logger.error('CLI账户初始化失败:设备授权确认未通过', 'CLI', '', { + logger.error('CLI account init failed: device auth confirmation not passed', 'CLI', '', { user_code: deviceFlow.user_code }) return { @@ -282,7 +282,7 @@ class CliAuthManager { const cliToken = await this.pollForToken(deviceFlow.device_code, deviceFlow.code_verifier, account) if (!cliToken.access_token || !cliToken.refresh_token || !cliToken.expiry_date) { - logger.error('CLI账户初始化失败:轮询令牌返回数据不完整', 'CLI', '', cliToken) + logger.error('CLI account init failed: poll token returned incomplete data', 'CLI', '', cliToken) } return cliToken } diff --git a/src/utils/data-persistence.js b/src/utils/data-persistence.js index a2c4c6c4..6f990b56 100644 --- a/src/utils/data-persistence.js +++ b/src/utils/data-persistence.js @@ -62,11 +62,11 @@ class DataPersistence { case 'none': return await this._loadFromEnv() default: - logger.error(`不支持的数据保存模式: ${config.dataSaveMode}`, 'DATA') - throw new Error(`不支持的数据保存模式: ${config.dataSaveMode}`) + logger.error(`Unsupported data save mode: ${config.dataSaveMode}`, 'DATA') + throw new Error(`Unsupported data save mode: ${config.dataSaveMode}`) } } catch (error) { - logger.error('加载账户数据失败', 'DATA', '', error) + logger.error('Failed to load account data', 'DATA', '', error) throw error } } @@ -85,14 +85,14 @@ class DataPersistence { case 'file': return await this._saveToFile(email, accountData) case 'none': - logger.warn('环境变量模式不支持保存账户数据', 'DATA') + logger.warn('Environment variable mode does not support saving account data', 'DATA') return false default: - logger.error(`不支持的数据保存模式: ${config.dataSaveMode}`, 'DATA') - throw new Error(`不支持的数据保存模式: ${config.dataSaveMode}`) + logger.error(`Unsupported data save mode: ${config.dataSaveMode}`, 'DATA') + throw new Error(`Unsupported data save mode: ${config.dataSaveMode}`) } } catch (error) { - logger.error(`保存账户数据失败 (${email})`, 'DATA', '', error) + logger.error(`Failed to save account data (${email})`, 'DATA', '', error) return false } } @@ -112,11 +112,11 @@ class DataPersistence { case 'none': return {} default: - logger.error(`不支持的数据保存模式: ${config.dataSaveMode}`, 'DATA') + logger.error(`Unsupported data save mode: ${config.dataSaveMode}`, 'DATA') return {} } } catch (error) { - logger.error('加载运行时设置失败', 'DATA', '', error) + logger.error('Failed to load runtime settings', 'DATA', '', error) return {} } } @@ -134,14 +134,14 @@ class DataPersistence { case 'file': return await this._saveSettingsToFile(partial) case 'none': - logger.warn('环境变量模式不支持保存运行时设置', 'DATA') + logger.warn('Environment variable mode does not support saving runtime settings', 'DATA') return false default: - logger.error(`不支持的数据保存模式: ${config.dataSaveMode}`, 'DATA') + logger.error(`Unsupported data save mode: ${config.dataSaveMode}`, 'DATA') return false } } catch (error) { - logger.error('保存运行时设置失败', 'DATA', '', error) + logger.error('Failed to save runtime settings', 'DATA', '', error) return false } } @@ -175,14 +175,14 @@ class DataPersistence { case 'file': return await this._saveAllToFile(accounts) case 'none': - logger.warn('环境变量模式不支持保存账户数据', 'DATA') + logger.warn('Environment variable mode does not support saving account data', 'DATA') return false default: - logger.error(`不支持的数据保存模式: ${config.dataSaveMode}`, 'DATA') - throw new Error(`不支持的数据保存模式: ${config.dataSaveMode}`) + logger.error(`Unsupported data save mode: ${config.dataSaveMode}`, 'DATA') + throw new Error(`Unsupported data save mode: ${config.dataSaveMode}`) } } catch (error) { - logger.error('批量保存账户数据失败', 'DATA', '', error) + logger.error('Failed to batch save account data', 'DATA', '', error) return false } } @@ -342,7 +342,7 @@ class DataPersistence { try { await this.saveAccount(email, { stats: payload }) } catch (error) { - logger.error(`stats 持久化失败 (${email})`, 'STATS', '', error) + logger.error(`stats persistence failed (${email})`, 'STATS', '', error) } }, STATS_PERSIST_DEBOUNCE_MS) @@ -402,7 +402,7 @@ class DataPersistence { '损坏文件已原样保留,请人工修复后重启服务', 'DATA' ) - throw new Error(`数据文件 ${this.dataFilePath} 损坏且无可用备份: ${parseError.message}`) + throw new Error(`Data file ${this.dataFilePath} is corrupted and no backup available: ${parseError.message}`) } // 先给损坏文件留证(copy 而非 rename——避免中途崩溃导致 data.json 缺失),再用备份覆盖 @@ -411,12 +411,12 @@ class DataPersistence { try { await fs.copyFile(this.dataFilePath, quarantinePath) } catch (copyError) { - logger.warn(`损坏文件留证失败: ${quarantinePath}`, 'DATA', '', copyError) + logger.warn(`Failed to quarantine corrupted file: ${quarantinePath}`, 'DATA', '', copyError) } await this._writeFileAtomic(this.dataFilePath, backupContent) logger.warn( - `已用备份恢复数据文件(${(backupData.accounts || []).length} 个账户);` + + `已用备份恢复数据文件(${(backupData.accounts || []).length} accounts);` + `损坏副本: ${quarantinePath}`, 'DATA' ) @@ -506,7 +506,7 @@ class DataPersistence { try { await this._writeFileAtomic(this.backupFilePath, content) } catch (error) { - logger.warn('备份文件写入失败(不影响主数据文件)', 'DATA', '', error) + logger.warn('Backup file write failed (does not affect main data file)', 'DATA', '', error) } } @@ -518,7 +518,7 @@ class DataPersistence { try { await fs.access(this.dataFilePath) } catch (error) { - logger.info('数据文件不存在,正在创建默认文件...', 'FILE', '📁') + logger.info('Data file does not exist, creating default file...', 'FILE', '📁') // 确保目录存在 const dirPath = path.dirname(this.dataFilePath) @@ -532,7 +532,7 @@ class DataPersistence { } await this._writeFileAtomic(this.dataFilePath, JSON.stringify(defaultData, null, 2)) - logger.success('默认数据文件创建成功', 'FILE') + logger.success('Default data file created successfully', 'FILE') } } } diff --git a/src/utils/img-caches.js b/src/utils/img-caches.js index f902def7..45a9aea1 100644 --- a/src/utils/img-caches.js +++ b/src/utils/img-caches.js @@ -17,7 +17,7 @@ class imgCacheManager { return fs.existsSync(cachePath) } } catch (e) { - logger.error('缓存检查失败', 'CACHE', '', e) + logger.error('Cache check failed', 'CACHE', '', e) return false } } @@ -41,7 +41,7 @@ class imgCacheManager { } } catch (e) { - logger.error('添加缓存失败', 'CACHE', '', e) + logger.error('Failed to add cache', 'CACHE', '', e) return false } } @@ -71,7 +71,7 @@ class imgCacheManager { } } } catch (e) { - logger.error('获取缓存失败', 'CACHE', '', e) + logger.error('Failed to get cache', 'CACHE', '', e) return { status: 500, url: null diff --git a/src/utils/logger.js b/src/utils/logger.js index e90a79aa..6ce3228b 100644 --- a/src/utils/logger.js +++ b/src/utils/logger.js @@ -91,7 +91,7 @@ class Logger { fs.mkdirSync(this.options.logDir, { recursive: true }) } } catch (error) { - console.error('创建日志目录失败:', error.message) + console.error('Failed to create log directory:', error.message) } } @@ -171,7 +171,7 @@ class Logger { fs.appendFileSync(logFile, logEntry, 'utf8') } catch (error) { - console.error('写入日志文件失败:', error.message) + console.error('Failed to write log file:', error.message) } } @@ -196,7 +196,7 @@ class Logger { this.cleanOldLogFiles() } } catch (error) { - console.error('日志文件轮转失败:', error.message) + console.error('Log file rotation failed:', error.message) } } @@ -223,7 +223,7 @@ class Logger { }) } } catch (error) { - console.error('清理旧日志文件失败:', error.message) + console.error('Failed to clean old log files:', error.message) } } diff --git a/src/utils/redis.js b/src/utils/redis.js index 63f55554..e4a2f224 100644 --- a/src/utils/redis.js +++ b/src/utils/redis.js @@ -55,12 +55,12 @@ const createRedisConfig = () => ({ // 重试策略 retryStrategy(times) { if (times > REDIS_CONFIG.maxRetries) { - logger.error(`Redis连接重试次数超限: ${times}`, 'REDIS') + logger.error(`Redis connection retry count exceeded: ${times}`, 'REDIS') return null } const delay = Math.min(100 * Math.pow(2, times), 3000) - logger.info(`Redis重试连接: ${times}, 延迟: ${delay}ms`, 'REDIS', '🔄') + logger.info(`Redis retrying connection: ${times}, delay: ${delay}ms`, 'REDIS', '🔄') return delay }, @@ -83,11 +83,11 @@ const verifyRedisCommandChannel = async (client) => { try { const pong = await client.ping() if (pong !== 'PONG') { - throw new Error(`PING 返回异常: ${pong}`) + throw new Error(`PING returned anomaly: ${pong}`) } if (attempt > 1) { - logger.info(`Redis命令通道在第 ${attempt} 次校验时恢复正常`, 'REDIS', '✅') + logger.info(`Redis command channel recovered on attempt ${attempt}`, 'REDIS', '✅') } return @@ -98,12 +98,12 @@ const verifyRedisCommandChannel = async (client) => { break } - logger.warn(`Redis命令通道校验失败,第 ${attempt} 次后准备重试: ${error.message}`, 'REDIS') + logger.warn(`Redis command channel check failed, retrying after ${attempt} attempt(s): ${error.message}`, 'REDIS') await new Promise(resolve => setTimeout(resolve, REDIS_VERIFY_RETRY_DELAY)) } } - throw new Error(`Redis命令通道不可用: ${lastError ? lastError.message : '未知错误'}`) + throw new Error(`Redis command channel unavailable: ${lastError ? lastError.message : 'unknown error'}`) } /** @@ -175,7 +175,7 @@ const updateActivity = () => { // 设置新的空闲定时器 idleTimer = setTimeout(() => { if (redis && Date.now() - lastActivity > IDLE_TIMEOUT) { - logger.info('Redis连接空闲超时,断开连接', 'REDIS', '🔌') + logger.info('Redis connection idle timeout, closing connection', 'REDIS', '🔌') disconnectRedis() } }, IDLE_TIMEOUT) @@ -187,22 +187,22 @@ const updateActivity = () => { */ const bindRedisEvents = (client) => { client.on('connect', () => { - logger.success('Redis连接建立', 'REDIS') + logger.success('Redis connection established', 'REDIS') }) client.on('ready', () => { - logger.success('Redis准备就绪', 'REDIS') + logger.success('Redis is ready', 'REDIS') if (redis === client) { updateActivity() } }) client.on('error', (err) => { - logger.error('Redis连接错误', 'REDIS', '', err) + logger.error('Redis connection error', 'REDIS', '', err) }) client.on('close', () => { - logger.info('Redis连接关闭', 'REDIS', '🔌') + logger.info('Redis connection closed', 'REDIS', '🔌') if (redis === client) { redis = null clearIdleTimer() @@ -210,7 +210,7 @@ const bindRedisEvents = (client) => { }) client.on('end', () => { - logger.info('Redis连接结束', 'REDIS', '🔌') + logger.info('Redis connection ended', 'REDIS', '🔌') if (redis === client) { redis = null clearIdleTimer() @@ -218,7 +218,7 @@ const bindRedisEvents = (client) => { }) client.on('reconnecting', (delay) => { - logger.info(`Redis重新连接中...延迟: ${delay}ms`, 'REDIS', '🔄') + logger.info(`Redis reconnecting... delay: ${delay}ms`, 'REDIS', '🔄') }) } @@ -257,7 +257,7 @@ const connectRedis = async () => { let newRedis = null try { - logger.info('建立Redis连接...', 'REDIS', '🔌') + logger.info('Establishing Redis connection...', 'REDIS', '🔌') newRedis = new Redis(config.redisURL, createRedisConfig()) redis = newRedis @@ -279,7 +279,7 @@ const connectRedis = async () => { } } - logger.error('Redis连接失败', 'REDIS', '', error) + logger.error('Redis connection failed', 'REDIS', '', error) throw error } finally { isConnecting = false @@ -301,9 +301,9 @@ const disconnectRedis = async () => { try { currentRedis.disconnect() - logger.info('Redis连接已断开', 'REDIS', '🔌') + logger.info('Redis disconnected', 'REDIS', '🔌') } catch (error) { - logger.error('断开Redis连接时出错', 'REDIS', '', error) + logger.error('Error disconnecting Redis', 'REDIS', '', error) } finally { if (redis === currentRedis) { redis = null @@ -320,8 +320,8 @@ const disconnectRedis = async () => { */ const ensureConnection = async () => { if (config.dataSaveMode !== 'redis') { - logger.error('当前数据保存模式不是Redis', 'REDIS') - throw new Error('当前数据保存模式不是Redis') + logger.error('Current data save mode is not Redis', 'REDIS') + throw new Error('Current data save mode is not Redis') } if (!redis || redis.status !== 'ready') { @@ -329,7 +329,7 @@ const ensureConnection = async () => { } if (Date.now() - lastActivity > STALE_CONNECTION_THRESHOLD) { - logger.info('Redis连接空闲时间过长,主动重建连接', 'REDIS', '🔄') + logger.info('Redis connection idle time too long, proactively rebuilding connection', 'REDIS', '🔄') await disconnectRedis() return await connectRedis() } @@ -357,7 +357,7 @@ const getAllAccounts = async () => { } while (cursor !== '0') if (!keys.length) { - logger.info('没有找到任何账户', 'REDIS', '✅') + logger.info('No accounts found', 'REDIS', '✅') return [] } @@ -369,7 +369,7 @@ const getAllAccounts = async () => { const results = await pipeline.exec() if (!results) { - logger.error('获取账户数据失败', 'REDIS') + logger.error('Failed to get account data', 'REDIS') return [] } @@ -377,11 +377,11 @@ const getAllAccounts = async () => { // result格式为[err, value] const [err, accountData] = result if (err) { - logger.error(`获取账户 ${keys[index]} 数据失败`, 'REDIS', '', err) + logger.error(`Failed to get data for account ${keys[index]}`, 'REDIS', '', err) return null } if (!accountData || Object.keys(accountData).length === 0) { - logger.error(`账户 ${keys[index]} 数据为空`, 'REDIS') + logger.error(`Account ${keys[index]} data is empty`, 'REDIS') return null } // stats 以 JSON 字符串存储于 HSET——malformed/missing 返回 undefined,由上层 ensureStats 补默认 @@ -390,7 +390,7 @@ const getAllAccounts = async () => { try { stats = JSON.parse(accountData.stats) } catch (parseError) { - logger.warn(`账户 ${keys[index]} stats JSON 解析失败,使用默认值: ${parseError.message}`, 'REDIS') + logger.warn(`Account ${keys[index]} stats JSON parsing failed, using default: ${parseError.message}`, 'REDIS') stats = undefined } } @@ -400,7 +400,7 @@ const getAllAccounts = async () => { try { statsHistory = JSON.parse(accountData.statsHistory) } catch (parseError) { - logger.warn(`账户 ${keys[index]} statsHistory JSON 解析失败,使用默认值: ${parseError.message}`, 'REDIS') + logger.warn(`Account ${keys[index]} statsHistory JSON parsing failed, using default: ${parseError.message}`, 'REDIS') statsHistory = undefined } } @@ -415,10 +415,10 @@ const getAllAccounts = async () => { } }).filter(Boolean) // 过滤掉null值 - logger.success(`获取所有账户成功,共 ${accounts.length} 个账户`, 'REDIS') + logger.success(`Successfully fetched all accounts, total ${accounts.length} accounts`, 'REDIS') return accounts } catch (err) { - logger.error('获取账户时出错', 'REDIS', '', err) + logger.error('Error getting accounts', 'REDIS', '', err) throw err } } @@ -446,16 +446,16 @@ const setAccount = async (key, value) => { if (statsHistory !== undefined) payload.statsHistory = JSON.stringify(statsHistory) if (Object.keys(payload).length === 0) { - logger.warn(`账户 ${key} setAccount 收到空 payload,跳过写入`, 'REDIS') + logger.warn(`Account ${key} setAccount received empty payload, skipping write`, 'REDIS') return true } await client.hset(`user:${key}`, payload) - logger.success(`账户 ${key} 设置成功`, 'REDIS') + logger.success(`Account ${key} set successfully`, 'REDIS') return true } catch (err) { - logger.error(`设置账户 ${key} 失败`, 'REDIS', '', err) + logger.error(`Failed to set account ${key}`, 'REDIS', '', err) return false } } @@ -471,14 +471,14 @@ const deleteAccount = async (key) => { const result = await client.del(`user:${key}`) if (result > 0) { - logger.success(`账户 ${key} 删除成功`, 'REDIS') + logger.success(`Account ${key} deleted successfully`, 'REDIS') return true } else { - logger.warn(`账户 ${key} 不存在`, 'REDIS') + logger.warn(`Account ${key} does not exist`, 'REDIS') return false } } catch (err) { - logger.error(`删除账户 ${key} 失败`, 'REDIS', '', err) + logger.error(`Failed to delete account ${key}`, 'REDIS', '', err) return false } } @@ -495,7 +495,7 @@ const getSettings = async () => { const data = await client.hgetall(SETTINGS_KEY) return JSON.parse(data.json) } catch (err) { - logger.error('获取运行时设置失败', 'REDIS', '', err) + logger.error('Failed to get runtime settings', 'REDIS', '', err) return {} } } @@ -514,7 +514,7 @@ const setSettings = async (partial) => { await client.hset(SETTINGS_KEY, stringified) return true } catch (err) { - logger.error('保存运行时设置失败', 'REDIS', '', err) + logger.error('Failed to save runtime settings', 'REDIS', '', err) return false } } @@ -531,10 +531,10 @@ const checkKeyExists = async (key = 'headers') => { const exists = await client.exists(key) const result = exists === 1 - logger.info(`键 "${key}" ${result ? '存在' : '不存在'}`, 'REDIS', result ? '✅' : '❌') + logger.info(`Key "${key}" ${result ? 'exists' : 'does not exist'}`, 'REDIS', result ? '✅' : '❌') return result } catch (err) { - logger.error(`检查键 "${key}" 时出错`, 'REDIS', '', err) + logger.error(`Error checking key "${key}"`, 'REDIS', '', err) return false } } @@ -557,7 +557,7 @@ const getConnectionStatus = () => { * 手动断开连接(用于应用关闭时清理) */ const cleanup = async () => { - logger.info('清理Redis连接...', 'REDIS', '🧹') + logger.info('Cleaning up Redis connection...', 'REDIS', '🧹') await disconnectRedis() } diff --git a/src/utils/request.js b/src/utils/request.js index 92d37b1b..dcec1a9f 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -300,7 +300,7 @@ const externalizeOversizedAgentContext = async ( try { file = await uploader(originalContent, currentToken, currentAccount, options) } catch (error) { - logger.error('Agent 长上下文附件上传/解析失败,回退到最近上下文', 'REQUEST', '', error) + logger.error('Agent long-context attachment upload/parse failed, falling back to recent context', 'REQUEST', '', error) const fallbackMessage = replaceMessageTextContent( message, compactAgentContextFallback(originalContent, options.livePromptBytes) @@ -428,9 +428,9 @@ const sendChatRequest = async (body, options = {}) => { ) const payload = contextResult.payload if (contextResult.externalized) { - logger.info(`Agent 上下文已外置为 Qwen 文档(原请求 ${contextResult.serializedBytes} bytes)`, 'REQUEST', '📎') + logger.info(`Agent context exported as Qwen doc (original request ${contextResult.serializedBytes} bytes)`, 'REQUEST', '📎') } else if (contextResult.compacted) { - logger.warn(`Agent 上下文附件失败,已保留最近上下文(原请求 ${contextResult.serializedBytes} bytes)`, 'REQUEST') + logger.warn(`Agent context attachment failed, kept recent context (original request ${contextResult.serializedBytes} bytes)`, 'REQUEST') } const maxRetries = Math.max(0, parseInt(config.chatRetryCount, 10) || 0) @@ -499,11 +499,11 @@ const sendChatRequest = async (body, options = {}) => { } else { // HTTP 4xx/5xx (上游主动拒绝, 账户有效) — 仅刷新 warn 指示, 不影响 cooldown const status = lastError.response?.status - logger.error('发送聊天请求失败', 'REQUEST', '', lastError.message) + logger.error('Failed to send chat request', 'REQUEST', '', lastError.message) accountManager.recordAccountError(currentAccount.email, status) } } else if (lastError) { - logger.error('发送聊天请求失败', 'REQUEST', '', lastError.message) + logger.error('Failed to send chat request', 'REQUEST', '', lastError.message) } return { @@ -570,7 +570,7 @@ const generateChatID = async (currentToken, model, account, chatType = 't2t') => return response_data.data?.data?.id || null } catch (error) { - logger.error('生成chat_id失败', 'CHAT', '', error.message) + logger.error('Failed to generate chat_id', 'CHAT', '', error.message) return null } } diff --git a/src/utils/setting.js b/src/utils/setting.js index 8e523745..b75630c7 100644 --- a/src/utils/setting.js +++ b/src/utils/setting.js @@ -19,7 +19,7 @@ const saveAccounts = async (email, password, token, expires, proxy = null) => { try { // 参数验证 if (!email || !password) { - logger.error('保存账户失败: 邮箱和密码不能为空', 'SETTING') + logger.error('Failed to save account: email and password cannot be empty', 'SETTING') return false } @@ -27,14 +27,14 @@ const saveAccounts = async (email, password, token, expires, proxy = null) => { const success = await accountManager.addAccount(email, password, proxy) if (success) { - logger.success(`账户 ${email} 保存成功`, 'SETTING') + logger.success(`Account ${email} saved successfully`, 'SETTING') return true } else { - logger.error(`账户 ${email} 保存失败`, 'SETTING') + logger.error(`Failed to save account ${email}`, 'SETTING') return false } } catch (error) { - logger.error(`保存账户 ${email} 时发生错误`, 'SETTING', '', error) + logger.error(`Error while saving account ${email}`, 'SETTING', '', error) return false } } @@ -48,7 +48,7 @@ const deleteAccount = async (email) => { try { // 参数验证 if (!email) { - logger.error('删除账户失败: 邮箱不能为空', 'SETTING') + logger.error('Failed to delete account: email cannot be empty', 'SETTING') return false } @@ -56,14 +56,14 @@ const deleteAccount = async (email) => { const success = await accountManager.removeAccount(email) if (success) { - logger.success(`账户 ${email} 删除成功`, 'SETTING') + logger.success(`Account ${email} deleted successfully`, 'SETTING') return true } else { - logger.error(`账户 ${email} 删除失败`, 'SETTING') + logger.error(`Failed to delete account ${email}`, 'SETTING') return false } } catch (error) { - logger.error(`删除账户 ${email} 时发生错误`, 'SETTING', '', error) + logger.error(`Error while deleting account ${email}`, 'SETTING', '', error) return false } } @@ -76,7 +76,7 @@ const getAllAccounts = () => { try { return accountManager.getAllAccountKeys() } catch (error) { - logger.error('获取账户列表时发生错误', 'SETTING', '', error) + logger.error('Error while getting account list', 'SETTING', '', error) return [] } } @@ -89,7 +89,7 @@ const getAccountHealth = () => { try { return accountManager.getHealthStats() } catch (error) { - logger.error('获取账户健康状态时发生错误', 'SETTING', '', error) + logger.error('Error while getting account health status', 'SETTING', '', error) return { accounts: { total: 0, valid: 0, expired: 0, expiringSoon: 0, invalid: 0 }, rotation: { total: 0, available: 0, inCooldown: 0 }, @@ -106,21 +106,21 @@ const getAccountHealth = () => { const refreshAccountToken = async (email) => { try { if (!email) { - logger.error('刷新令牌失败: 邮箱不能为空', 'SETTING') + logger.error('Token refresh failed: email cannot be empty', 'SETTING') return false } const success = await accountManager.refreshAccountToken(email) if (success) { - logger.success(`账户 ${email} 令牌刷新成功`, 'SETTING') + logger.success(`Account ${email} token refreshed successfully`, 'SETTING') return true } else { - logger.error(`账户 ${email} 令牌刷新失败`, 'SETTING') + logger.error(`Account ${email} token refresh failed`, 'SETTING') return false } } catch (error) { - logger.error(`刷新账户 ${email} 令牌时发生错误`, 'SETTING', '', error) + logger.error(`Error while refreshing token for account ${email}`, 'SETTING', '', error) return false } } diff --git a/src/utils/ssxmod-manager.js b/src/utils/ssxmod-manager.js index 06326aa5..c5fdba88 100644 --- a/src/utils/ssxmod-manager.js +++ b/src/utils/ssxmod-manager.js @@ -30,9 +30,9 @@ function refreshCookies() { ssxmod_itna2: result.ssxmod_itna2, timestamp: result.timestamp }; - logger.info(`SSXMOD Cookie 已刷新`, 'SSXMOD'); + logger.info(`SSXMOD Cookie refreshed`, 'SSXMOD'); } catch (error) { - logger.error('SSXMOD Cookie 刷新失败', 'SSXMOD', '', error.message); + logger.error('SSXMOD Cookie refresh failed', 'SSXMOD', '', error.message); } } @@ -50,7 +50,7 @@ function initSsxmodManager() { } refreshTimer = setInterval(refreshCookies, REFRESH_INTERVAL); - logger.info(`SSXMOD 管理器已启动,刷新间隔: ${REFRESH_INTERVAL / 1000 / 60} 分钟`, 'SSXMOD'); + logger.info(`SSXMOD manager started, refresh interval: ${REFRESH_INTERVAL / 1000 / 60} minutes`, 'SSXMOD'); } /** @@ -84,7 +84,7 @@ function stopRefresh() { if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; - logger.info('SSXMOD 定时刷新已停止', 'SSXMOD'); + logger.info('SSXMOD periodic refresh stopped', 'SSXMOD'); } } diff --git a/src/utils/token-manager.js b/src/utils/token-manager.js index 839ee692..dbe66681 100644 --- a/src/utils/token-manager.js +++ b/src/utils/token-manager.js @@ -49,19 +49,19 @@ class TokenManager { }, requestConfig) if (response.data && response.data.token) { - logger.success(`${email} 登录成功:${response.data.token}`, 'AUTH') + logger.success(`${email} login successful: ${response.data.token}`, 'AUTH') return response.data.token } else { - logger.error(`${email} 登录响应缺少令牌`, 'AUTH') + logger.error(`${email} login response missing token`, 'AUTH') return null } } catch (error) { if (error.response) { - logger.error(`${email} 登录失败 (${error.response.status})`, 'AUTH', '', error) + logger.error(`${email} login failed (${error.response.status})`, 'AUTH', '', error) } else if (error.request) { - logger.error(`${email} 登录失败: 网络请求超时或无响应`, 'AUTH') + logger.error(`${email} login failed: network timeout or no response`, 'AUTH') } else { - logger.error(`${email} 登录失败`, 'AUTH', '', error) + logger.error(`${email} login failed`, 'AUTH', '', error) } return null } @@ -88,7 +88,7 @@ class TokenManager { return decoded } catch (error) { - logger.error('令牌验证失败', 'TOKEN', '', error) + logger.error('Token validation failed', 'TOKEN', '', error) return null } } @@ -136,7 +136,7 @@ class TokenManager { const decoded = this.validateToken(newToken) if (!decoded) { - logger.error(`刷新后的令牌无效: ${account.email}`, 'TOKEN') + logger.error(`Refreshed token is invalid: ${account.email}`, 'TOKEN') return null } @@ -147,11 +147,11 @@ class TokenManager { } const remainingHours = this.getTokenRemainingHours(newToken) - logger.success(`令牌刷新成功: ${account.email} (有效期: ${remainingHours}小时)`, 'TOKEN') + logger.success(`Token refresh successful: ${account.email} (expires in: ${remainingHours} hours)`, 'TOKEN') return updatedAccount } catch (error) { - logger.error(`刷新令牌失败 (${account.email})`, 'TOKEN', '', error) + logger.error(`Token refresh failed (${account.email})`, 'TOKEN', '', error) return null } } @@ -169,11 +169,11 @@ class TokenManager { ) if (needsRefresh.length === 0) { - logger.info('没有需要刷新的令牌', 'TOKEN') + logger.info('No tokens need refreshing', 'TOKEN') return { refreshed: [], failed: [] } } - logger.info(`发现 ${needsRefresh.length} 个令牌需要刷新`, 'TOKEN') + logger.info(`Found ${needsRefresh.length} tokens needing refresh`, 'TOKEN') const refreshed = [] const failed = [] @@ -190,7 +190,7 @@ class TokenManager { try { await onEachRefresh(updatedAccount, i + 1, needsRefresh.length) } catch (error) { - logger.error(`刷新回调函数执行失败 (${account.email})`, 'TOKEN', '', error) + logger.error(`Refresh callback execution failed (${account.email})`, 'TOKEN', '', error) } } } else { @@ -201,7 +201,7 @@ class TokenManager { await this._delay(1000) } - logger.success(`令牌刷新完成: 成功 ${refreshed.length} 个,失败 ${failed.length} 个`, 'TOKEN') + logger.success(`Token refresh complete: ${refreshed.length} succeeded, ${failed.length} failed`, 'TOKEN') return { refreshed, failed } } diff --git a/src/utils/tools.js b/src/utils/tools.js index b58ec59c..83363d8e 100644 --- a/src/utils/tools.js +++ b/src/utils/tools.js @@ -18,8 +18,8 @@ const sleep = async (ms) => { const sha256Encrypt = (text) => { if (typeof text !== 'string') { - logger.error('输入必须是字符串类型', 'TOOLS') - throw new Error('输入必须是字符串类型') + logger.error('Input must be a string', 'TOOLS') + throw new Error('Input must be a string') } const hash = crypto.createHash('sha256') hash.update(text, 'utf-8') @@ -31,7 +31,7 @@ const JwtDecode = (token) => { const decoded = jwtDecode(token, { complete: true }) return decoded } catch (error) { - logger.error('解析JWT失败', 'JWT', '', error) + logger.error('Failed to parse JWT', 'JWT', '', error) return null } } diff --git a/src/utils/upload.js b/src/utils/upload.js index 068f0166..8997efa4 100644 --- a/src/utils/upload.js +++ b/src/utils/upload.js @@ -86,13 +86,13 @@ const requestStsToken = async (filename, filesize, filetypeSimple, authToken, re try { // 参数验证 if (!filename || !authToken) { - logger.error('文件名和认证Token不能为空', 'UPLOAD') - throw new Error('文件名和认证Token不能为空') + logger.error('Filename and auth token cannot be empty', 'UPLOAD') + throw new Error('Filename and auth token cannot be empty') } if (!validateFileSize(filesize)) { - logger.error(`文件大小超出限制,最大允许 ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`, 'UPLOAD') - throw new Error(`文件大小超出限制,最大允许 ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`) + logger.error(`File size exceeds limit, max allowed ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`, 'UPLOAD') + throw new Error(`File size exceeds limit, max allowed ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`) } const requestId = generateUUID() @@ -123,13 +123,13 @@ const requestStsToken = async (filename, filesize, filetypeSimple, authToken, re requestConfig.proxy = false } - logger.info(`请求STS Token: ${filename} (${filesize} bytes, ${filetypeSimple})`, 'UPLOAD', '🎫') + logger.info(`Requesting STS Token: ${filename} (${filesize} bytes, ${filetypeSimple})`, 'UPLOAD', '🎫') const response = await axios.post(UPLOAD_CONFIG.stsTokenUrl, payload, requestConfig) if (response.status === 200 && response.data) { if (response.data.success === false) { - throw new Error(response.data?.data?.message || response.data?.message || 'STS Token 请求被上游拒绝') + throw new Error(response.data?.data?.message || response.data?.message || 'STS Token request rejected by upstream') } // 同时兼容旧版直接字段与 FE 0.2.81 的 {success,data} 包装。 const stsData = unwrapApiData(response) @@ -157,24 +157,24 @@ const requestStsToken = async (filename, filesize, filetypeSimple, authToken, re const missingFileInfo = requiredFileInfo.filter(key => !fileInfo[key]) if (missingCredentials.length > 0 || missingFileInfo.length > 0) { - logger.error(`STS响应数据不完整: 缺少 ${[...missingCredentials, ...missingFileInfo].join(', ')}`, 'UPLOAD') - throw new Error(`STS响应数据不完整: 缺少 ${[...missingCredentials, ...missingFileInfo].join(', ')}`) + logger.error(`STS response incomplete: missing ${[...missingCredentials, ...missingFileInfo].join(', ')}`, 'UPLOAD') + throw new Error(`STS response incomplete: missing ${[...missingCredentials, ...missingFileInfo].join(', ')}`) } - logger.success('STS Token获取成功', 'UPLOAD') + logger.success('STS Token obtained successfully', 'UPLOAD') return { credentials, file_info: fileInfo } } else { - logger.error(`获取STS Token失败,状态码: ${response.status}`, 'UPLOAD') - throw new Error(`获取STS Token失败,状态码: ${response.status}`) + logger.error(`Failed to get STS Token, status code: ${response.status}`, 'UPLOAD') + throw new Error(`Failed to get STS Token, status code: ${response.status}`) } } catch (error) { - logger.error(`请求STS Token失败 (重试: ${retryCount})`, 'UPLOAD', '', error) + logger.error(`Failed to request STS Token (retry: ${retryCount})`, 'UPLOAD', '', error) // 403错误特殊处理 if (error.response?.status === 403) { - logger.error('403 Forbidden错误,可能是Token权限问题', 'UPLOAD') - logger.error('认证失败,请检查Token权限', 'UPLOAD') - throw new Error('认证失败,请检查Token权限') + logger.error('403 Forbidden, possible token permission issue', 'UPLOAD') + logger.error('Auth failed, please check token permissions', 'UPLOAD') + throw new Error('Auth failed, please check token permissions') } // 重试逻辑 @@ -183,7 +183,7 @@ const requestStsToken = async (filename, filesize, filetypeSimple, authToken, re error.response?.status >= 500)) { const delayMs = UPLOAD_CONFIG.retryDelay * Math.pow(2, retryCount) - logger.warn(`等待 ${delayMs}ms 后重试...`, 'UPLOAD', '⏳') + logger.warn(`Retrying in ${delayMs}ms...`, 'UPLOAD', '⏳') await delay(delayMs) return requestStsToken(filename, filesize, filetypeSimple, authToken, retryCount + 1, account) @@ -206,8 +206,8 @@ const uploadToOssWithSts = async (fileBuffer, stsCredentials, ossInfo, fileConte try { // 参数验证 if (!fileBuffer || !stsCredentials || !ossInfo) { - logger.error('缺少必要的上传参数', 'UPLOAD') - throw new Error('缺少必要的上传参数') + logger.error('Missing required upload parameters', 'UPLOAD') + throw new Error('Missing required upload parameters') } const client = new OSS({ @@ -220,7 +220,7 @@ const uploadToOssWithSts = async (fileBuffer, stsCredentials, ossInfo, fileConte timeout: UPLOAD_CONFIG.timeout }) - logger.info(`上传文件到OSS: ${ossInfo.path} (${fileBuffer.length} bytes)`, 'UPLOAD', '📤') + logger.info(`Uploading file to OSS: ${ossInfo.path} (${fileBuffer.length} bytes)`, 'UPLOAD', '📤') const result = await client.put(ossInfo.path, fileBuffer, { headers: { @@ -229,19 +229,19 @@ const uploadToOssWithSts = async (fileBuffer, stsCredentials, ossInfo, fileConte }) if (result.res && result.res.status === 200) { - logger.success('文件上传到OSS成功', 'UPLOAD') + logger.success('File uploaded to OSS successfully', 'UPLOAD') return { success: true, result } } else { - logger.error(`OSS上传失败,状态码: ${result.res?.status || 'unknown'}`, 'UPLOAD') - throw new Error(`OSS上传失败,状态码: ${result.res?.status || 'unknown'}`) + logger.error(`OSS upload failed, status code: ${result.res?.status || 'unknown'}`, 'UPLOAD') + throw new Error(`OSS upload failed, status code: ${result.res?.status || 'unknown'}`) } } catch (error) { - logger.error(`OSS上传失败 (重试: ${retryCount})`, 'UPLOAD', '', error) + logger.error(`OSS upload failed (retry: ${retryCount})`, 'UPLOAD', '', error) // 重试逻辑 if (retryCount < UPLOAD_CONFIG.maxRetries) { const delayMs = UPLOAD_CONFIG.retryDelay * Math.pow(2, retryCount) - logger.warn(`等待 ${delayMs}ms 后重试OSS上传...`, 'UPLOAD', '⏳') + logger.warn(`Retrying in ${delayMs}ms... (OSS retry)`, 'UPLOAD', '⏳') await delay(delayMs) return uploadToOssWithSts(fileBuffer, stsCredentials, ossInfo, fileContentTypeFull, retryCount + 1) @@ -264,8 +264,8 @@ const uploadFileToQwenOss = async (fileBuffer, originalFilename, authToken, acco try { // 参数验证 if (!fileBuffer || !originalFilename || !authToken) { - logger.error('缺少必要的上传参数', 'UPLOAD') - throw new Error('缺少必要的上传参数') + logger.error('Missing required upload parameters', 'UPLOAD') + throw new Error('Missing required upload parameters') } const filesize = fileBuffer.length @@ -274,11 +274,11 @@ const uploadFileToQwenOss = async (fileBuffer, originalFilename, authToken, acco // 文件大小验证 if (!validateFileSize(filesize)) { - logger.error(`文件大小超出限制,最大允许 ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`, 'UPLOAD') - throw new Error(`文件大小超出限制,最大允许 ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`) + logger.error(`File size exceeds limit, max allowed ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`, 'UPLOAD') + throw new Error(`File size exceeds limit, max allowed ${UPLOAD_CONFIG.maxFileSize / 1024 / 1024}MB`) } - logger.info(`开始上传文件: ${originalFilename} (${filesize} bytes, ${mimeType})`, 'UPLOAD', '📤') + logger.info(`Starting file upload: ${originalFilename} (${filesize} bytes, ${mimeType})`, 'UPLOAD', '📤') // 第一步:获取STS Token const { credentials, file_info } = await requestStsToken( @@ -293,7 +293,7 @@ const uploadFileToQwenOss = async (fileBuffer, originalFilename, authToken, acco // 第二步:上传到OSS await uploadToOssWithSts(fileBuffer, credentials, file_info, mimeType) - logger.success('文件上传流程完成', 'UPLOAD') + logger.success('File upload process complete', 'UPLOAD') return { status: 200, @@ -302,7 +302,7 @@ const uploadFileToQwenOss = async (fileBuffer, originalFilename, authToken, acco message: '文件上传成功' } } catch (error) { - logger.error('文件上传流程失败', 'UPLOAD', '', error) + logger.error('File upload process failed', 'UPLOAD', '', error) throw error } } @@ -316,7 +316,7 @@ const uploadFileToQwenOss = async (fileBuffer, originalFilename, authToken, acco * @param {Object} [options] */ const parseUploadedTextFile = async (fileId, authToken, account, options = {}) => { - if (!fileId || !authToken) throw new Error('解析文档缺少 fileId 或认证 Token') + if (!fileId || !authToken) throw new Error('Parsed document missing fileId or auth token') const baseUrl = getChatBaseUrl() const requestConfig = applyProxyToAxiosConfig({ @@ -341,12 +341,12 @@ const parseUploadedTextFile = async (fileId, authToken, account, options = {}) = if (status === 'success' || status === 'completed' || status === 'done') return true if (status === 'failed' || status === 'error') { - throw new Error(record?.error_msg || record?.message || 'Qwen 文档解析失败') + throw new Error(record?.error_msg || record?.message || 'Qwen document parsing failed') } if (attempt < maxAttempts) await delay(intervalMs) } - throw new Error(`Qwen 文档解析超时: ${fileId}`) + throw new Error(`Qwen document parsing timeout: ${fileId}`) } /** @@ -395,7 +395,7 @@ const buildChatFileDescriptor = ({ fileId, fileUrl, filename, size }) => { */ const uploadAgentContextFile = async (text, authToken, account, options = {}) => { const content = Buffer.from(String(text || ''), 'utf8') - if (content.length === 0) throw new Error('Agent 上下文为空') + if (content.length === 0) throw new Error('Agent context is empty') const filename = options.filename || `QWEN2API_AGENT_CONTEXT_${Date.now()}.txt` const uploaded = await uploadFileToQwenOss(content, filename, authToken, account) await parseUploadedTextFile(uploaded.file_id, authToken, account, options) From 4083031dbdc9110d4c4eefc40cc2db3a0c307ccc Mon Sep 17 00:00:00 2001 From: shabahmd Date: Wed, 26 Aug 2026 03:54:50 +0500 Subject: [PATCH 2/3] docs: update README-en.md for pnpm migration and temp chat mode - Remove PM2 references, replace npm with pnpm - Add ENABLE_TEMP_CHATS config option documentation - Update project structure (pnpm-workspace.yaml, no ecosystem.config.js) - Clean up stale references Co-authored-by: ByNara Model: agnes-2.0-flash --- README-en.md | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/README-en.md b/README-en.md index 81f43031..08b8a05c 100644 --- a/README-en.md +++ b/README-en.md @@ -91,11 +91,6 @@ SERVICE_PORT=3000 # Service port API_KEY=sk-123456,sk-456789 # API key (required, supports multiple keys) ACCOUNTS= # Account configuration (format: user1:pass1[|proxy_url],user2:pass2[|proxy_url]) -# 🚀 PM2 Multi-process Configuration -PM2_INSTANCES=1 # Number of PM2 processes (1/number/max) -PM2_MAX_MEMORY=1G # PM2 memory limit (100M/1G/2G, etc.) - # Note: All processes in PM2 cluster mode share the same port - # 🔍 Feature Configuration SEARCH_INFO_MODE=table # Search info display mode (table/text) OUTPUT_THINK=true # Whether to output thinking process (true/false) @@ -115,6 +110,12 @@ BATCH_LOGIN_CONCURRENCY=5 # Login concurrency during batch account addition # 📸 Cache Configuration CACHE_MODE=default # Image cache mode (default/file) + +# 🌐 Temp Chat Mode +ENABLE_TEMP_CHATS=false # Enable temporary local chat mode (true/false) + +# 🌐 Temp Chat Mode +ENABLE_TEMP_CHATS=false # Enable temporary local chat mode (true/false) ``` #### 📋 Configuration Description @@ -124,8 +125,6 @@ CACHE_MODE=default # Image cache mode (default/file) | `LISTEN_ADDRESS` | Service listen address | `localhost` or `0.0.0.0` | | [SERVICE_PORT](file://d:\Code\Qwen2API\src\start.js#L12-L12) | Service running port | `3000` | | `API_KEY` | API access key, supports multi-key configuration. The first is the admin key (can access frontend management page), others are regular keys (API calls only). Multiple keys separated by commas | `sk-admin123,sk-user456,sk-user789` | -| [PM2_INSTANCES](file://d:\Code\Qwen2API\src\start.js#L11-L11) | Number of PM2 processes | `1`/`4`/`max` | -| `PM2_MAX_MEMORY` | PM2 memory limit | `100M`/`1G`/`2G` | | `SEARCH_INFO_MODE` | Search result display format | `table` or [text](file://d:\Code\Qwen2API\src\utils\tool-prompt.js#L206-L206) | | `OUTPUT_THINK` | Whether to show AI thinking process | `true` or `false` | | `LEGACY_REASONING_IN_CONTENT` | Reasoning output format. Default `false` = reasoning goes to a separate `reasoning_content` field; `true` = legacy behavior (`` inside `content`) | `true` or `false` | @@ -140,6 +139,7 @@ CACHE_MODE=default # Image cache mode (default/file) | `REDIS_URL` | Redis database connection address, use `rediss://` protocol when using TLS encryption | `redis://localhost:6379` or `rediss://xxx.upstash.io` | | `BATCH_LOGIN_CONCURRENCY` | Login concurrency during batch account addition, can be adjusted dynamically in frontend system settings | `5` | | `CACHE_MODE` | Image cache storage method | `default`/`file` | +| `ENABLE_TEMP_CHATS` | Enable temporary local chat mode (bypasses persistent session creation) | `true` or `false` | | [LOG_LEVEL](file://d:\Code\Qwen2API\backend\core\config.py#L30-L30) | Log level | `DEBUG`/`INFO`/`WARN`/`ERROR` | | `ENABLE_FILE_LOG` | Enable file logging | `true` or `false` | | `LOG_DIR` | Log file directory | `./logs` | @@ -241,32 +241,22 @@ git clone https://github.com/Rfym21/Qwen2API.git cd Qwen2API # Install dependencies -npm install +pnpm install # Configure environment variables cp .env.example .env # Edit .env file -# Smart start (recommended - automatically determines single/multi-process) -npm start +# Start (recommended) +pnpm start # Development mode -npm run dev +pnpm dev ``` -### 🚀 PM2 Multi-Process Deployment - -Use PM2 for production environment multi-process deployment, providing better performance and stability. - -**Important Note**: In PM2 cluster mode, all processes share the same port, and PM2 automatically performs load balancing. - ### 🤖 Smart Start Mode -Using `npm start` can automatically determine the startup method: - -- When `PM2_INSTANCES=1`, uses single-process mode -- When `PM2_INSTANCES>1`, uses Node.js cluster mode -- Automatically limits process count to no more than CPU cores +Using `pnpm start` starts the server directly with Node.js: ### ☁️ Hugging Face Deployment @@ -300,8 +290,9 @@ DATA_SAVE_MODE=none Qwen2API/ ├── README.md ├── README-en.md -├── ecosystem.config.js # PM2 configuration file ├── package.json +├── pnpm-workspace.yaml # pnpm workspace configuration +├── pnpm-workspace.yaml # pnpm workspace configuration │ ├── docker/ # Docker configuration directory │ ├── Dockerfile @@ -317,7 +308,7 @@ Qwen2API/ │ ├── src/ # Backend source code directory │ ├── server.js # Main server file -│ ├── start.js # Smart start script (automatically determines single/multi-process) +│ ├── start.js # Server entry point │ ├── config/ │ │ └── index.js # Configuration file │ ├── controllers/ # Controllers directory @@ -375,7 +366,7 @@ Qwen2API/ │ ├── dashboard.vue # Dashboard page │ └── settings.vue # Settings page ├── package.json # Frontend dependency configuration - ├── package-lock.json + ├── pnpm-lock.yaml ├── index.html # Frontend entry HTML ├── postcss.config.js # PostCSS configuration ├── tailwind.config.js # TailwindCSS configuration From 551dff6aa4a156b9271dec3b5bae5d16ed9a0f11 Mon Sep 17 00:00:00 2001 From: shabahmd Date: Wed, 26 Aug 2026 06:38:22 +0500 Subject: [PATCH 3/3] fix(chat-mode): single source of truth for chat_mode and FE-shaped upstream payloads - resolveChatMode(): one decision point (explicit 'normal' > 'local'/ENABLE_TEMP_CHATS > default), used by both OpenAI middleware and Anthropic builder - generateChatID creates upstream chats with the resolved chat_mode; /c/local referer in temp mode - buildFeChatMessage(): shared FE 0.2.81 message factory; Anthropic path now sends a full-shape single message (+version/timestamp, -session_id/-id), fixing upstream WAF/captcha rejections (FAIL_SYS_USER_VALIDATE/RGV587) on /v1/messages - parserModel: omitted model now resolves dynamically from the upstream t2t list instead of hardcoded qwen3-coder-plus - ENABLE_TEMP_CHATS documented in env example; npm test uses --test-force-exit - tests: chat_mode precedence, middleware stamping, FE shape guard Co-authored-by: ByNara Model: agnes-2.0-flash --- .env.example | 8 +++ package.json | 2 +- src/controllers/anthropic.js | 24 +++++-- src/middlewares/chat-middleware.js | 38 +++-------- src/utils/chat-helpers.js | 65 ++++++++++++++++-- src/utils/request.js | 18 +++-- tests/chat-mode.test.js | 103 +++++++++++++++++++++++++++++ 7 files changed, 210 insertions(+), 48 deletions(-) create mode 100644 tests/chat-mode.test.js diff --git a/.env.example b/.env.example index a55e3fdb..5e8b2909 100644 --- a/.env.example +++ b/.env.example @@ -164,3 +164,11 @@ PROXY_URL= # disabled by default to avoid failure spam) # true: enable; false/unset: disabled (cli endpoints return 503) ENABLE_CLI=false + +# ========== Temporary (Local) Chat Mode ========== + +# 启用临时聊天模式(chat_mode=local,上游不创建持久会话) +# 可被单个请求的 chat_mode 字段覆盖(显式 'normal' 优先于本开关) +# Enable temporary local chat mode (chat_mode=local, bypasses persistent session creation) +# Can be overridden per-request via the chat_mode field (explicit 'normal' wins over this flag) +ENABLE_TEMP_CHATS=true diff --git a/package.json b/package.json index 3c9e93bd..67ba1338 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "node src/start.js", "dev": "nodemon src/server.js", - "test": "node --test tests/*.test.js" + "test": "node --test --test-force-exit tests/*.test.js" }, "keywords": [], "author": "", diff --git a/src/controllers/anthropic.js b/src/controllers/anthropic.js index 36f1410c..23b68671 100644 --- a/src/controllers/anthropic.js +++ b/src/controllers/anthropic.js @@ -2,7 +2,7 @@ const { isJson, generateUUID } = require('../utils/tools.js'); const { createUsageObject } = require('../utils/precise-tokenizer.js'); const { sendChatRequest } = require('../utils/request.js'); const accountManager = require('../utils/account.js'); -const { isChatType, isThinkingEnabled, parserModel, parserMessages, createUpstreamDeltaNormalizer } = require('../utils/chat-helpers.js'); +const { isChatType, isThinkingEnabled, parserModel, parserMessages, createUpstreamDeltaNormalizer, resolveChatMode, buildFeChatMessage } = require('../utils/chat-helpers.js'); const { buildToolSystemPrompt, foldToolMessages, @@ -213,7 +213,7 @@ const flattenAnthropicMessages = (messages) => { * @returns {Promise<{body: Object, hasTools: boolean, toolChoice: any, allowedToolNames: string[], enable_thinking: boolean, model: string}>} 转换结果 */ const buildInternalRequest = async (anthropicReq) => { - const { model, messages, system, tools, tool_choice, stream, thinking } = anthropicReq; + const { model, messages, system, tools, tool_choice, stream, thinking, chat_mode } = anthropicReq; const normalizedTools = normalizeAnthropicTools(tools); const internalToolChoice = normalizeAnthropicToolChoice(tool_choice); @@ -260,16 +260,26 @@ const buildInternalRequest = async (anthropicReq) => { } } + // 网页上游只接受一条当前消息(历史已由 parserMessages 折叠进 content), + // 且消息必须是完整 FE 形状,否则 WAF 按指纹判定为机器人并返回 captcha + const lastParsed = parsedMessages[parsedMessages.length - 1] || { role: 'user', content: '' }; const body = { stream: !!stream, + version: '2.1', incremental_output: true, chat_type: chatType, - sub_chat_type: chatType, - chat_mode: 'normal', + chat_mode: resolveChatMode(chat_mode), model: parsedModel, - messages: parsedMessages, - session_id: generateUUID(), - id: generateUUID() + messages: [ + buildFeChatMessage({ + role: lastParsed.role || 'user', + content: lastParsed.content || '', + chatType, + thinkingEnabled: thinkingCfg.thinking_enabled, + modelId: parsedModel + }) + ], + timestamp: Math.floor(Date.now() / 1000) }; return { diff --git a/src/middlewares/chat-middleware.js b/src/middlewares/chat-middleware.js index d6af9d9d..85451766 100644 --- a/src/middlewares/chat-middleware.js +++ b/src/middlewares/chat-middleware.js @@ -1,5 +1,4 @@ -const { generateUUID } = require('../utils/tools.js') -const { isChatType, isThinkingEnabled, parserModel, parserMessages } = require('../utils/chat-helpers.js') +const { isChatType, isThinkingEnabled, parserModel, parserMessages, resolveChatMode, buildFeChatMessage } = require('../utils/chat-helpers.js') const { buildToolSystemPrompt, foldToolMessages } = require('../utils/tool-prompt.js') const { buildAgentTurnDirective } = require('../utils/agent-turn.js') const { logger } = require('../utils/logger') @@ -56,8 +55,8 @@ const processRequestBody = async (req, res, next) => { } = req.body const now = Math.floor(Date.now() / 1000) - const fid = generateUUID() const thinkingConfig = await isThinkingEnabled(model, enable_thinking, thinking_budget) + const resolvedModelId = await parserModel(model) // 构建请求体 — 对齐 React 前端格式 const body = { @@ -66,36 +65,17 @@ const processRequestBody = async (req, res, next) => { incremental_output: true, chat_id: null, // 由 sendChatRequest 填充 chatId: null, - chat_mode: req.body.chat_mode === 'local' ? 'local' : (config.enableTempChats ? 'local' : 'normal'), - model: await parserModel(model), + chat_mode: resolveChatMode(req.body.chat_mode), + model: resolvedModelId, parent_id: null, parentId: null, - messages: [{ - id: null, - fid: fid, - parentId: null, - parent_id: null, - childrenIds: [generateUUID()], + messages: [buildFeChatMessage({ role: 'user', // 取最后一条消息的角色 content: '', // 由下方 parserMessages 填充 - user_action: 'chat', - files: [], - timestamp: now, - models: [await parserModel(model)], - model: '', - chat_type: isChatType(model), - feature_config: { - output_schema: 'phase', // 必需:缺失时上游不再返回 delta.phase,chat.js 会丢弃全部增量(completion=0) - thinking_enabled: thinkingConfig.thinking_enabled, - research_mode: 'normal', - auto_thinking: true, - thinking_mode: 'Auto', - thinking_format: 'summary', // 与官方 FE + Max 模型 meta 一致 - auto_search: true - }, - extra: { meta: { subChatType: isChatType(model) } }, - sub_chat_type: isChatType(model) - }], + chatType: isChatType(model), + thinkingEnabled: thinkingConfig.thinking_enabled, + modelId: resolvedModelId + })], timestamp: now } diff --git a/src/utils/chat-helpers.js b/src/utils/chat-helpers.js index bb0700d7..e2d776c8 100644 --- a/src/utils/chat-helpers.js +++ b/src/utils/chat-helpers.js @@ -1,9 +1,10 @@ const { logger } = require('./logger') const { sha256Encrypt, generateUUID } = require('./tools.js') const { uploadFileToQwenOss } = require('./upload.js') -const { getLatestModels } = require('../models/models-map.js') +const { getLatestModels, getDefaultModelByChatType } = require('../models/models-map.js') const accountManager = require('./account.js') const CacheManager = require('./img-caches.js') +const config = require('../config/index.js') const MODEL_SUFFIXES = [ '-thinking-search', @@ -244,20 +245,23 @@ const isThinkingEnabled = async (model, enable_thinking, thinking_budget) => { /** * 解析模型名称,移除特殊后缀 + * 未指定模型时动态取上游列表中首个支持 t2t 的模型,而非硬编码 * @param {string} model - 原始模型名称 * @returns {string} 解析后的模型名称 */ const parserModel = async (model) => { - if (!model) return 'qwen3-coder-plus' + const { baseModel } = splitModelSuffix(model) try { - const { baseModel } = splitModelSuffix(model) + if (!baseModel) { + // 客户端未指定模型 — 动态选择上游默认 t2t 模型 + return await getDefaultModelByChatType('t2t') || 'qwen3-coder-plus' + } const latestModels = await getLatestModels() const matchedModel = findMatchedModel(latestModels, baseModel) return matchedModel?.id || baseModel } catch (e) { - const { baseModel } = splitModelSuffix(model) return baseModel || 'qwen3-coder-plus' } } @@ -502,6 +506,57 @@ const processOriginalLogic = async (messages, thinking_config, chat_type, imgCac * @returns {boolean} */ const isThinkPhase = (phase) => phase === 'think' || phase === 'thinking' || phase === 'thinking_summary' + +/** + * 解析 chat_mode 的唯一决策点(single source of truth) + * 优先级:客户端显式 'normal' > 客户端 'local' 或全局 ENABLE_TEMP_CHATS > 默认 'normal' + * @param {string} [requested] - 请求中的 chat_mode + * @returns {'local'|'normal'} + */ +const resolveChatMode = (requested) => { + if (requested === 'normal') return 'normal' + if (requested === 'local' || config.enableTempChats) return 'local' + return 'normal' +} + +/** + * 构建对齐 Qwen React 前端格式的单条消息(FE 0.2.81 抓包形状)。 + * 上游 WAF 会按请求体指纹识别非浏览器客户端,所有入口必须用此工厂, + * 不要手写消息字面量。 + * @param {Object} params + * @param {string} [params.role] - 消息角色 + * @param {string|Array} [params.content] - 消息内容 + * @param {string} [params.chatType] - 聊天类型(t2t 等) + * @param {boolean} [params.thinkingEnabled] - 是否启用思考 + * @param {string} [params.modelId] - 解析后的模型 ID + * @returns {object} FE 格式消息对象 + */ +const buildFeChatMessage = ({ role = 'user', content = '', chatType = 't2t', thinkingEnabled = false, modelId = null } = {}) => ({ + id: null, + fid: generateUUID(), + parentId: null, + parent_id: null, + childrenIds: [generateUUID()], + role, + content, + user_action: 'chat', + files: [], + timestamp: Math.floor(Date.now() / 1000), + models: [modelId], + model: '', + chat_type: chatType, + feature_config: { + output_schema: 'phase', // 必需:缺失时上游不再返回 delta.phase + thinking_enabled: thinkingEnabled, + research_mode: 'normal', + auto_thinking: true, + thinking_mode: 'Auto', + thinking_format: 'summary', // 与官方 FE + Max 模型 meta 一致 + auto_search: true + }, + extra: { meta: { subChatType: chatType } }, + sub_chat_type: chatType +}) const ANSWER_PHASES = new Set(['answer', 'final', 'final_answer', 'response']) /** @@ -552,5 +607,7 @@ module.exports = { parserMessages, formatHistoryMessages, isThinkPhase, + resolveChatMode, + buildFeChatMessage, createUpstreamDeltaNormalizer } diff --git a/src/utils/request.js b/src/utils/request.js index dcec1a9f..fe1d94fe 100644 --- a/src/utils/request.js +++ b/src/utils/request.js @@ -392,7 +392,9 @@ const sendChatRequest = async (body, options = {}) => { } const chatType = body.chat_type || body.messages?.[0]?.chat_type || 't2t' - const chat_id = options.chatId || await generateChatID(currentToken, body.model, currentAccount, chatType) + // chat_mode 已由入口(middleware / anthropic builder)通过 resolveChatMode 解析,这里只读取 + const chatMode = body.chat_mode === 'local' ? 'local' : 'normal' + const chat_id = options.chatId || await generateChatID(currentToken, body.model, currentAccount, chatType, chatMode) if (!chat_id) { return { status: false, @@ -400,8 +402,8 @@ const sendChatRequest = async (body, options = {}) => { message: '无法创建或续接 Qwen 会话' } } - // 浏览器 referer 为 /c/(在 chat_id 生成后动态设置) - requestConfig.headers.referer = `${chatBaseUrl}/c/${chat_id}` + // 浏览器 referer 为 /c/(在 chat_id 生成后动态设置);临时聊天为 /c/local + requestConfig.headers.referer = `${chatBaseUrl}/c/${chatMode === 'local' ? 'local' : chat_id}` const url = `${chatBaseUrl}/api/v2/chat/completions?chat_id=` + chat_id // 对齐网页双写 chatId/parentId(FE 0.2.81) const parentId = options.parentId ?? body.parentId ?? body.parent_id ?? null @@ -517,9 +519,11 @@ const sendChatRequest = async (body, options = {}) => { * @param {string} currentToken * @param {string} model * @param {Object} [account] - 当前账户对象(用于解析账号级代理) + * @param {string} [chatType] - 聊天类型(t2t 等) + * @param {string} [chatMode] - 聊天模式:'local'(临时聊天)或 'normal' * @returns {Promise} 返回生成的chat_id,如果失败则返回null */ -const generateChatID = async (currentToken, model, account, chatType = 't2t') => { +const generateChatID = async (currentToken, model, account, chatType = 't2t', chatMode = 'normal') => { try { const chatBaseUrl = getChatBaseUrl() const proxyAgent = getProxyAgent(account) @@ -527,7 +531,7 @@ const generateChatID = async (currentToken, model, account, chatType = 't2t') => const requestConfig = { headers: { 'sec-ch-ua-platform': '"Windows"', - 'referer': `${chatBaseUrl}/c/new-chat`, + 'referer': `${chatBaseUrl}/c/${chatMode === 'local' ? 'local' : 'new-chat'}`, 'accept-language': 'zh-CN,zh;q=0.9', 'sec-ch-ua': '"Google Chrome";v="149", "Chromium";v="149", "Not)A;Brand";v="24"', 'sec-ch-ua-mobile': '?0', @@ -557,14 +561,14 @@ const generateChatID = async (currentToken, model, account, chatType = 't2t') => requestConfig.proxy = false } - // 对齐 chat.qwen.ai FE 0.2.81:chatId/project_id + normal 模式 + // 对齐 chat.qwen.ai FE 0.2.81:chatId/project_id + chat_mode const response_data = await axios.post(`${chatBaseUrl}/api/v2/chats/new`, { chatId: '', models: [model], project_id: '', timestamp: Date.now(), chat_type: chatType || 't2t', - chat_mode: 'normal' + chat_mode: chatMode === 'local' ? 'local' : 'normal' }, requestConfig) return response_data.data?.data?.id || null diff --git a/tests/chat-mode.test.js b/tests/chat-mode.test.js new file mode 100644 index 00000000..aa028efa --- /dev/null +++ b/tests/chat-mode.test.js @@ -0,0 +1,103 @@ +const test = require('node:test') +const assert = require('node:assert/strict') + +const { resolveChatMode, buildFeChatMessage } = require('../src/utils/chat-helpers.js') +const config = require('../src/config/index.js') +const { processRequestBody } = require('../src/middlewares/chat-middleware.js') + +const withFlag = (value, fn) => { + const original = config.enableTempChats + config.enableTempChats = value + try { + fn() + } finally { + config.enableTempChats = original + } +} + +test('resolveChatMode: explicit normal beats the global temp-chats flag', () => { + withFlag(true, () => { + assert.equal(resolveChatMode('normal'), 'normal') + }) +}) + +test('resolveChatMode: local requested wins regardless of flag', () => { + withFlag(false, () => { + assert.equal(resolveChatMode('local'), 'local') + }) + withFlag(true, () => { + assert.equal(resolveChatMode('local'), 'local') + }) +}) + +test('resolveChatMode: unset follows ENABLE_TEMP_CHATS', () => { + withFlag(true, () => { + assert.equal(resolveChatMode(undefined), 'local') + assert.equal(resolveChatMode(null), 'local') + assert.equal(resolveChatMode(''), 'local') + }) + withFlag(false, () => { + assert.equal(resolveChatMode(undefined), 'normal') + assert.equal(resolveChatMode('weird-value'), 'normal') + }) +}) + +const runMiddleware = async (body) => { + const req = { + body: { messages: [{ role: 'user', content: 'hi' }], model: 'qwen3.7-plus', stream: false, ...body } + } + const res = { + status(code) { + this.code = code + return { json: (payload) => ({ code: this.code, payload }) } + } + } + let nextCalled = false + await processRequestBody(req, res, () => { nextCalled = true }) + return { nextCalled, chatMode: req.body.chat_mode } +} + +test('middleware stamps chat_mode=local when flag enabled and client silent', async () => { + withFlag(true, async () => { + const { nextCalled, chatMode } = await runMiddleware({}) + assert.equal(nextCalled, true) + assert.equal(chatMode, 'local') + }) +}) + +test('middleware honors explicit client chat_mode over the flag', async () => { + withFlag(true, async () => { + const optOut = await runMiddleware({ chat_mode: 'normal' }) + assert.equal(optOut.chatMode, 'normal') + + const optIn = await runMiddleware({ chat_mode: 'local' }) + assert.equal(optIn.chatMode, 'local') + }) +}) + +test('middleware defaults to normal when flag disabled', async () => { + withFlag(false, async () => { + const { chatMode } = await runMiddleware({}) + assert.equal(chatMode, 'normal') + }) +}) + +test('buildFeChatMessage emits the full FE 0.2.81 shape (WAF fingerprint)', () => { + const message = buildFeChatMessage({ + role: 'assistant', + content: 'hello', + chatType: 't2t', + thinkingEnabled: true, + modelId: 'qwen3.7-plus' + }) + for (const key of ['id', 'fid', 'parentId', 'parent_id', 'childrenIds', 'role', 'content', + 'user_action', 'files', 'timestamp', 'models', 'model', 'chat_type', + 'feature_config', 'extra', 'sub_chat_type']) { + assert.ok(key in message, `missing FE field: ${key}`) + } + assert.equal(message.user_action, 'chat') + assert.deepEqual(message.models, ['qwen3.7-plus']) + assert.equal(message.feature_config.output_schema, 'phase') + assert.equal(message.feature_config.thinking_enabled, true) + assert.equal(message.extra.meta.subChatType, 't2t') +})